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) :
@@ -3541,6 +3541,32 @@
3541
3541
  }
3542
3542
 
3543
3543
  var _templateObject, _templateObject2;
3544
+ /**
3545
+ * @param {unknown[]} arr
3546
+ * @param {number} index
3547
+ * @param {unknown} val
3548
+ * @returns {void}
3549
+ */
3550
+ var setArrayValue = function setArrayValue(arr, index, val) {
3551
+ if (Reflect.has(Array.prototype, index)) {
3552
+ Object.defineProperty(arr, index, {
3553
+ value: val,
3554
+ enumerable: true,
3555
+ writable: true,
3556
+ configurable: true
3557
+ });
3558
+ } else {
3559
+ arr[index] = val;
3560
+ }
3561
+ };
3562
+ /**
3563
+ * @param {unknown[]} arr
3564
+ * @param {unknown} val
3565
+ * @returns {void}
3566
+ */
3567
+ var safePush = function safePush(arr, val) {
3568
+ return setArrayValue(arr, arr.length, val);
3569
+ };
3544
3570
 
3545
3571
  /**
3546
3572
  * @typedef {NodeJS.TypedArray|DataView} ArrayBufferView
@@ -3804,6 +3830,7 @@
3804
3830
  * @returns {string}
3805
3831
  */
3806
3832
  encode: function encode(key) {
3833
+ /** @type {(string|null)[]} */
3807
3834
  var encoded = [];
3808
3835
  var _iterator = _createForOfIteratorHelper(key.entries()),
3809
3836
  _step;
@@ -3813,14 +3840,14 @@
3813
3840
  i = _step$value[0],
3814
3841
  item = _step$value[1];
3815
3842
  var encodedItem = _encode(item, true); // encode the array item
3816
- encoded[i] = encodedItem;
3843
+ setArrayValue(encoded, i, encodedItem);
3817
3844
  }
3818
3845
  } catch (err) {
3819
3846
  _iterator.e(err);
3820
3847
  } finally {
3821
3848
  _iterator.f();
3822
3849
  }
3823
- encoded.push(keyTypeToEncodedChar.invalid + '-'); // append an extra item, so empty arrays sort correctly
3850
+ safePush(encoded, keyTypeToEncodedChar.invalid + '-'); // append an extra item, so empty arrays sort correctly
3824
3851
  var encodedKey = JSON.stringify(encoded);
3825
3852
  if (CFG.escapeNULForSQLiteStatements === false) {
3826
3853
  encodedKey = encodedKey.replaceAll(String.raw(_templateObject || (_templateObject = _taggedTemplateLiteral(["\0"], ["\\u0000"]))), '\0');
@@ -3841,7 +3868,7 @@
3841
3868
  for (var i = 0; i < decoded.length; i++) {
3842
3869
  var item = decoded[i];
3843
3870
  var decodedItem = _decode(item, true); // decode the item
3844
- decoded[i] = decodedItem;
3871
+ setArrayValue(decoded, i, decodedItem);
3845
3872
  }
3846
3873
  return decoded;
3847
3874
  }
@@ -4139,7 +4166,7 @@
4139
4166
  // May throw (from binary)
4140
4167
  var arr = /** @type {Array<any>} */input;
4141
4168
  var len = arr.length;
4142
- seen.push(input);
4169
+ safePush(seen, input);
4143
4170
 
4144
4171
  /** @type {(KeyValueObject|Value)[]} */
4145
4172
  var keys = [];
@@ -4174,7 +4201,7 @@
4174
4201
  }) || fullKeys && keys.every(function (k) {
4175
4202
  return cmp(k, key) !== 0;
4176
4203
  })) {
4177
- keys.push(fullKeys ? key : key.value);
4204
+ safePush(keys, fullKeys ? key : key.value);
4178
4205
  }
4179
4206
  } catch (err) {
4180
4207
  if (!multiEntry) {
@@ -4334,7 +4361,7 @@
4334
4361
  if (key.failure) {
4335
4362
  return true;
4336
4363
  }
4337
- result.push(key.value);
4364
+ safePush(result, key.value);
4338
4365
  return false;
4339
4366
  }) ? {
4340
4367
  failure: true
@@ -4395,11 +4422,21 @@
4395
4422
  identifiers.forEach(function (identifier) {
4396
4423
  var hop = Object.hasOwn(value, identifier);
4397
4424
  if (!hop) {
4398
- value[identifier] = {};
4425
+ Object.defineProperty(value, identifier, {
4426
+ value: {},
4427
+ enumerable: true,
4428
+ writable: true,
4429
+ configurable: true
4430
+ });
4399
4431
  }
4400
4432
  value = value[identifier];
4401
4433
  });
4402
- value[(/** @type {string} */last)] = key; // key is already a `keyValue` in our processing so no need to convert
4434
+ Object.defineProperty(value, /** @type {string} */last, {
4435
+ value: key,
4436
+ enumerable: true,
4437
+ writable: true,
4438
+ configurable: true
4439
+ }); // key is already a `keyValue` in our processing so no need to convert
4403
4440
  }
4404
4441
 
4405
4442
  /**
@@ -4477,6 +4514,7 @@
4477
4514
  * @returns {Key[]}
4478
4515
  */
4479
4516
  function findMultiEntryMatches(keyEntry, range) {
4517
+ /** @type {unknown[]} */
4480
4518
  var matches = [];
4481
4519
  if (Array.isArray(keyEntry)) {
4482
4520
  var _iterator4 = _createForOfIteratorHelper(keyEntry),
@@ -4493,13 +4531,13 @@
4493
4531
  } else {
4494
4532
  var nested = findMultiEntryMatches(key, range);
4495
4533
  if (nested.length > 0) {
4496
- matches.push(key);
4534
+ safePush(matches, key);
4497
4535
  }
4498
4536
  continue;
4499
4537
  }
4500
4538
  }
4501
4539
  if (isNullish(range) || isKeyInRange(key, range, true)) {
4502
- matches.push(key);
4540
+ safePush(matches, key);
4503
4541
  }
4504
4542
  }
4505
4543
  } catch (err) {
@@ -4508,7 +4546,7 @@
4508
4546
  _iterator4.f();
4509
4547
  }
4510
4548
  } else if (isNullish(range) || isKeyInRange(keyEntry, range, true)) {
4511
- matches.push(keyEntry);
4549
+ safePush(matches, keyEntry);
4512
4550
  }
4513
4551
  return matches;
4514
4552
  }
@@ -4530,12 +4568,13 @@
4530
4568
  }
4531
4569
  case 'array':
4532
4570
  {
4571
+ /** @type {ValueType[]} */
4533
4572
  var array = [];
4534
4573
  var len = value.length;
4535
4574
  var index = 0;
4536
4575
  while (index < len) {
4537
4576
  var entry = convertKeyToValue(value[index]);
4538
- array[index] = entry;
4577
+ setArrayValue(array, index, entry);
4539
4578
  index++;
4540
4579
  }
4541
4580
  return array;
@@ -5295,6 +5334,7 @@
5295
5334
  }
5296
5335
 
5297
5336
  var uniqueID = 0;
5337
+ var activeTransactions = new Set();
5298
5338
  var listeners$1 = ['onabort', 'oncomplete', 'onerror'];
5299
5339
  var readonlyProperties$4 = ['objectStoreNames', 'mode', 'durability', 'db', 'error'];
5300
5340
 
@@ -5426,6 +5466,7 @@
5426
5466
  me.__mode = mode;
5427
5467
  me.__durability = durability;
5428
5468
  me.__db = db;
5469
+ activeTransactions.add(me);
5429
5470
  me.__error = null;
5430
5471
  // @ts-expect-error Part of `ShimEventTarget`
5431
5472
  me.__setOptions({
@@ -5938,6 +5979,7 @@
5938
5979
  me.__errored = true;
5939
5980
  throw e;
5940
5981
  } finally {
5982
+ activeTransactions.delete(me);
5941
5983
  me.__storeHandles = {};
5942
5984
  }
5943
5985
  }
@@ -6144,13 +6186,19 @@
6144
6186
  });
6145
6187
  }
6146
6188
  me.__active = false; // Setting here and in requestsFinished for https://github.com/w3c/IndexedDB/issues/87
6147
-
6189
+ activeTransactions.delete(me);
6148
6190
  if (err !== null) {
6149
6191
  me.__error = err;
6150
6192
  }
6151
- if (me.__requestsFinished) {
6193
+ if (me.__requestsFinished && err !== null) {
6152
6194
  // The transaction has already completed, so we can't call "onerror" or "onabort".
6153
- // So throw the error instead.
6195
+ // So throw the error instead. `err` is only ever `null` here via
6196
+ // `IDBTransaction.prototype.abort`'s own `__abortTransaction(null)`
6197
+ // call, which now checks `__requestsFinished` itself first and
6198
+ // throws `InvalidStateError` synchronously before ever reaching
6199
+ // this point -- so this guard is just defense in depth against
6200
+ // `err` somehow being `null` some other way, not something this
6201
+ // path should see in practice.
6154
6202
  setTimeout(function () {
6155
6203
  throw err;
6156
6204
  }, 0);
@@ -6270,6 +6318,14 @@
6270
6318
  if (me.__committed) {
6271
6319
  throw createDOMException('InvalidStateError', 'The transaction has already been committed');
6272
6320
  }
6321
+ if (me.__requestsFinished) {
6322
+ // All requests have already finished and the transaction is
6323
+ // auto-committing (the async SQL commit round trip just hasn't
6324
+ // resolved yet) -- too late to abort per spec, even though
6325
+ // `__committed` itself isn't set until that round trip actually
6326
+ // finishes.
6327
+ throw createDOMException('InvalidStateError', 'The transaction is already committing');
6328
+ }
6273
6329
  me.__abortTransaction(null);
6274
6330
  };
6275
6331
 
@@ -6351,7 +6407,7 @@
6351
6407
  * @returns {void}
6352
6408
  */
6353
6409
  IDBTransaction.__assertActive = function (tx) {
6354
- if (!tx || !tx.__active || tx.__committed) {
6410
+ if (!tx || !tx.__active || !tx.__handlerActive || tx.__committed) {
6355
6411
  throw createDOMException('TransactionInactiveError', 'A request was placed against a transaction which is currently not active, or which is finished');
6356
6412
  }
6357
6413
  };
@@ -6378,6 +6434,9 @@
6378
6434
  Object.defineProperty(IDBTransaction, 'prototype', {
6379
6435
  writable: false
6380
6436
  });
6437
+ /* eslint-enable unicorn/no-top-level-side-effects -- Would be good */
6438
+
6439
+ IDBTransaction.activeTransactions = activeTransactions;
6381
6440
 
6382
6441
  var TypesonPromise = /*#__PURE__*/_createClass(function TypesonPromise(e) {
6383
6442
  _classCallCheck(this, TypesonPromise);
@@ -7764,7 +7823,7 @@
7764
7823
  }
7765
7824
  }
7766
7825
  },
7767
- k = {
7826
+ D = {
7768
7827
  map: {
7769
7828
  test: function test(e) {
7770
7829
  return "Map" === toStringTag(e);
@@ -7777,7 +7836,7 @@
7777
7836
  }
7778
7837
  }
7779
7838
  },
7780
- D = {
7839
+ R = {
7781
7840
  nan: {
7782
7841
  test: function test(e) {
7783
7842
  return Number.isNaN(e);
@@ -8126,8 +8185,8 @@
8126
8185
  revive: function revive() {}
8127
8186
  }
8128
8187
  }],
8129
- ne = [D, M, L, F],
8130
- 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 : []);
8188
+ ne = [R, M, L, F],
8189
+ 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 : []);
8131
8190
  var ue = ce.concat({
8132
8191
  checkDataCloneException: {
8133
8192
  test: function test(e) {
@@ -8144,11 +8203,56 @@
8144
8203
  return false;
8145
8204
  }
8146
8205
  }
8206
+ }),
8207
+ ye = ue.concat({
8208
+ checkSharedArrayBufferException: {
8209
+ test: function test(e) {
8210
+ if ("SharedArrayBuffer" === {}.toString.call(e).slice(8, -1)) throw new DOMException("The object cannot be cloned.", "DataCloneError");
8211
+ return false;
8212
+ }
8213
+ }
8147
8214
  });
8148
8215
 
8149
8216
  // See: https://stackoverflow.com/questions/42170826/categories-for-rejection-by-the-structured-cloning-algorithm
8150
8217
 
8151
- var typeson = new Typeson().register(ue);
8218
+ // Although typeson-registry already has a FileList type in its structured cloning presets,
8219
+ // we need to override it so it works with our tests
8220
+
8221
+ var specSet = ye.flatMap(function (preset) {
8222
+ return Array.isArray(preset) ? preset : [preset];
8223
+ }).find(function (preset) {
8224
+ return preset && !Array.isArray(preset) && 'filelist' in preset;
8225
+ });
8226
+ var origFileList = specSet && !Array.isArray(specSet) && 'filelist' in specSet ? specSet.filelist : undefined;
8227
+ var origTest = origFileList && _typeof(origFileList) === 'object' && 'test' in origFileList && typeof origFileList.test === 'function' ? origFileList.test : undefined;
8228
+ var origRevive = origFileList && _typeof(origFileList) === 'object' && 'revive' in origFileList && typeof origFileList.revive === 'function' ? origFileList.revive : undefined;
8229
+ var customFileList = origFileList ? _objectSpread2(_objectSpread2({}, origFileList), {}, {
8230
+ /**
8231
+ * @param {unknown} x
8232
+ * @param {import('typeson').StateObject} state
8233
+ * @returns {boolean}
8234
+ */
8235
+ test: function test(x, state) {
8236
+ if (typeof FileList !== 'undefined') {
8237
+ return x instanceof FileList;
8238
+ }
8239
+ return typeof origTest === 'function' ? origTest(x, state) : false;
8240
+ },
8241
+ /**
8242
+ * @param {unknown} x
8243
+ * @param {import('typeson').StateObject} state
8244
+ * @returns {unknown}
8245
+ */
8246
+ revive: function revive(x, state) {
8247
+ if (typeof FileList !== 'undefined') {
8248
+ return Reflect.construct(FileList, [x]);
8249
+ }
8250
+ return typeof origRevive === 'function' ? origRevive(x, state) : undefined;
8251
+ }
8252
+ }) : undefined;
8253
+ var typeson = new Typeson().register([ye, customFileList ? {
8254
+ filelist: customFileList
8255
+ } : {}]);
8152
8256
 
8153
8257
  /**
8154
8258
  * @param {(preset: import('typeson-registry').Preset) =>
@@ -8157,7 +8261,7 @@
8157
8261
  */
8158
8262
  function register(func) {
8159
8263
  // eslint-disable-next-line unicorn/no-top-level-assignment-in-function -- Should be one-time cache
8160
- typeson = new Typeson().register(func(ue));
8264
+ typeson = new Typeson().register(func(ye));
8161
8265
  }
8162
8266
 
8163
8267
  /**
@@ -11084,7 +11188,29 @@
11084
11188
  return new SyncPromise(function (resolve) {
11085
11189
  setTimeout(function () {
11086
11190
  entry.dispatchEvent(e); // No need to catch errors
11087
- resolve(undefined);
11191
+ // Unlike a native `Promise`, `SyncPromise#then` chains
11192
+ // synchronously off `resolve()` (verified directly:
11193
+ // `SyncPromise.resolve().then(cb)` runs `cb` before
11194
+ // even the calling code below it finishes) -- so
11195
+ // resolving immediately here would let the
11196
+ // `connectionsClosed()` check below run before a
11197
+ // same-task-but-microtask-later continuation of a
11198
+ // `versionchange` listener above (e.g. an `await`-
11199
+ // based one that then calls `db.close()`, as in
11200
+ // `transaction-lifetime.any.js`) ever gets a turn,
11201
+ // incorrectly firing `blocked` even though the
11202
+ // connection was about to close in time. Give real
11203
+ // microtask-deferred continuations a small, bounded
11204
+ // number of turns first -- same pattern as
11205
+ // `IDBTransaction.js`'s `checkQueueEntry`.
11206
+ var attemptsLeft = 10;
11207
+ (function wait() {
11208
+ if (attemptsLeft-- <= 0) {
11209
+ resolve(undefined);
11210
+ return;
11211
+ }
11212
+ queueMicrotask(wait);
11213
+ })();
11088
11214
  }, 0);
11089
11215
  });
11090
11216
  });
@@ -11144,6 +11270,23 @@
11144
11270
  */
11145
11271
  var websqlDBCache = {};
11146
11272
 
11273
+ /**
11274
+ * Tracks databases with a creation/upgrade currently in flight but not yet
11275
+ * committed or aborted -- keyed by (unescaped) database name. The
11276
+ * `dbVersions` row in `sysdb` these entries shadow is written *before*
11277
+ * `upgradeneeded` is even dispatched to user code (as the success
11278
+ * callback of that very `INSERT`/`UPDATE`), on the same shared `sysdb`
11279
+ * connection `databases()` itself reads from -- so, absent this, a
11280
+ * `databases()` call made while an upgrade is still pending would see
11281
+ * the new row (or new version) immediately, rather than only once the
11282
+ * corresponding `versionchange` transaction has genuinely committed, as
11283
+ * required by `get-databases.any.js`'s "doesn't pick up changes that
11284
+ * haven't committed" test. `IDBFactory.prototype.databases` filters
11285
+ * its SQL results against this map.
11286
+ * @type {Map<string, {oldVersion: Integer, newVersion: Integer}>}
11287
+ */
11288
+ var pendingVersionChanges = new Map();
11289
+
11147
11290
  /** @type {import('websql-configurable/lib/websql/WebSQLDatabase.js').default} */
11148
11291
  var sysdb;
11149
11292
  var nameCounter = 0;
@@ -11417,6 +11560,7 @@
11417
11560
  }
11418
11561
  var req = IDBOpenDBRequest.__createInstance();
11419
11562
  var calledDbCreateError = false;
11563
+ var isRevertingSysdb = false;
11420
11564
  if (CFG.autoName && name === '') {
11421
11565
  // eslint-disable-next-line unicorn/no-top-level-assignment-in-function -- Necessary?
11422
11566
  name = 'autoNamedDatabase_' + nameCounter++;
@@ -11443,9 +11587,22 @@
11443
11587
  * @returns {boolean}
11444
11588
  */
11445
11589
  function dbCreateError(tx, err) {
11446
- if (calledDbCreateError) {
11590
+ if (calledDbCreateError || isRevertingSysdb) {
11447
11591
  return false;
11448
11592
  }
11593
+ // Defensive cleanup: `pendingVersionChanges.set(name, ...)` (see
11594
+ // below) runs *before* the `dbVersions` `INSERT`/`UPDATE` it
11595
+ // guards is even attempted, but only `versionSet`'s own
11596
+ // `on__beforecomplete`/`on__preabort` handlers ever clear it --
11597
+ // and `versionSet` never runs at all if that `INSERT`/`UPDATE`,
11598
+ // or an earlier step in this same `open()` flow, fails first.
11599
+ // Without this, such a failure would leave the entry orphaned
11600
+ // forever, silently corrupting `databases()` for any *other*,
11601
+ // unrelated later `open()` call that happens to reuse the same
11602
+ // database name (common in WPT tests, e.g. generic names like
11603
+ // "DB1"/"TestDatabase" reused across different test files in
11604
+ // the same process). A no-op if no entry was ever set.
11605
+ pendingVersionChanges.delete(name);
11449
11606
  var er = err ? webSQLErrback(err) : (/** @type {Error} */tx);
11450
11607
  calledDbCreateError = true;
11451
11608
  // Re: why bubbling here (and how cancelable is only really relevant for `window.onerror`) see: https://github.com/w3c/IndexedDB/issues/86
@@ -11494,36 +11651,46 @@
11494
11651
  */
11495
11652
  var sysdbFinishedCb = function sysdbFinishedCb(systx, err, cb) {
11496
11653
  if (err) {
11497
- try {
11498
- systx.executeSql('ROLLBACK', [], cb, cb);
11499
- } catch (err) {
11500
- // Browser may fail with expired transaction above so
11501
- // no choice but to manually revert
11654
+ /**
11655
+ * @param {any} [errorToShow]
11656
+ * @returns {void}
11657
+ */
11658
+ var manualRevert = function manualRevert(errorToShow) {
11659
+ /**
11660
+ * @param {string} [msg]
11661
+ * @throws {Error}
11662
+ * @returns {never}
11663
+ */
11664
+ function reportError(msg) {
11665
+ throw new Error('Unable to roll back upgrade transaction!' + (msg || ''));
11666
+ }
11502
11667
  sysdb.transaction(function (systx) {
11503
- /**
11504
- *
11505
- * @param {string} msg
11506
- * @throws {Error}
11507
- * @returns {never}
11508
- */
11509
- function reportError(msg) {
11510
- throw new Error('Unable to roll back upgrade transaction!' + (msg || ''));
11511
- }
11512
-
11513
11668
  // Attempt to revert
11514
11669
  if (oldVersion === 0) {
11515
- systx.executeSql('DELETE FROM dbVersions WHERE "name" = ?', [sqlSafeName], function () {
11516
- // @ts-expect-error Force to work
11517
- cb(reportError); // eslint-disable-line promise/no-callback-in-promise -- Convenient
11518
- },
11519
- // @ts-expect-error Force to work
11520
- reportError);
11670
+ systx.executeSql('DELETE FROM dbVersions WHERE "name" = ?', [sqlSafeName]);
11521
11671
  } else {
11522
- systx.executeSql('UPDATE dbVersions SET "version" = ? WHERE "name" = ?', [oldVersion, sqlSafeName], cb,
11523
- // @ts-expect-error Force to work
11524
- reportError);
11672
+ systx.executeSql('UPDATE dbVersions SET "version" = ? WHERE "name" = ?', [oldVersion, sqlSafeName]);
11525
11673
  }
11674
+ }, function (sqlErr) {
11675
+ isRevertingSysdb = false;
11676
+ cb(sqlErr); // eslint-disable-line promise/no-callback-in-promise -- Convenient
11677
+ }, function () {
11678
+ isRevertingSysdb = false;
11679
+ cb(errorToShow || reportError); // eslint-disable-line promise/no-callback-in-promise -- Convenient
11526
11680
  });
11681
+ };
11682
+ try {
11683
+ systx.executeSql('ROLLBACK', [], function () {
11684
+ cb(); // eslint-disable-line promise/no-callback-in-promise -- Convenient
11685
+ }, function (tx, sqlErr) {
11686
+ // Browser/Node may fail with expired transaction, so manually revert
11687
+ manualRevert(sqlErr);
11688
+ return false;
11689
+ });
11690
+ } catch (e) {
11691
+ // Browser may fail with expired transaction above so
11692
+ // no choice but to manually revert
11693
+ manualRevert(e);
11527
11694
  }
11528
11695
  return;
11529
11696
  }
@@ -11557,22 +11724,43 @@
11557
11724
  // open/close the transaction's active-handler window itself.
11558
11725
  req.transaction.__handlerActive = true;
11559
11726
  req.dispatchEvent(e);
11560
- // Give any same-tick microtask scheduled from within the
11727
+ // Give any microtask-scheduled continuation of the
11561
11728
  // `upgradeneeded` handler (e.g. a plain
11562
- // `Promise.resolve().then(...)`) a chance to run -- and still
11563
- // observe the transaction as active -- before we deactivate it
11564
- // again, per https://github.com/w3c/IndexedDB/issues/87. Only
11565
- // the flag reset itself is deferred here -- unlike
11566
- // `IDBTransaction.js`'s `advanceAfterDispatch`, `finished()`
11567
- // (this transaction's own queue-advancement/completion signal)
11568
- // still runs synchronously, at exactly its previous timing: an
11569
- // earlier attempt at deferring `finished()` too raced against
11570
- // unrelated test setup that assumed a freshly deleted/created
11571
- // database's upgrade transaction had already fully completed by
11572
- // the time this function returns.
11573
- queueMicrotask(function () {
11574
- req.transaction.__handlerActive = false;
11575
- });
11729
+ // `Promise.resolve().then(...)`, or an `await`-based
11730
+ // continuation of a promise resolved from within the handler,
11731
+ // such as testharness.js's own `EventWatcher`) a chance to
11732
+ // run -- and still observe the transaction as active -- before
11733
+ // we deactivate it again, per
11734
+ // https://github.com/w3c/IndexedDB/issues/87. A single deferred
11735
+ // tick isn't always enough: an `await`-based continuation can
11736
+ // take more than one microtask turn to resume (e.g.
11737
+ // `transaction-lifetime.any.js`'s `EventWatcher`-based
11738
+ // `await eventWatcher.wait_for('upgradeneeded')`), so retry a
11739
+ // small, bounded number of times first -- same pattern as
11740
+ // `IDBTransaction.js`'s `checkQueueEntry` -- rather than
11741
+ // resetting after only one. Only the flag reset itself is
11742
+ // deferred here -- unlike `IDBTransaction.js`'s
11743
+ // `advanceAfterDispatch`, `finished()` (this transaction's own
11744
+ // queue-advancement/completion signal) still runs
11745
+ // synchronously, at exactly its previous timing: an earlier
11746
+ // attempt at deferring `finished()` too raced against unrelated
11747
+ // test setup that assumed a freshly deleted/created database's
11748
+ // upgrade transaction had already fully completed by the time
11749
+ // this function returns.
11750
+ /**
11751
+ * @param {Integer} attemptsLeft
11752
+ * @returns {void}
11753
+ */
11754
+ function deferHandlerActiveReset(attemptsLeft) {
11755
+ if (attemptsLeft <= 0) {
11756
+ req.transaction.__handlerActive = false;
11757
+ return;
11758
+ }
11759
+ queueMicrotask(function () {
11760
+ deferHandlerActiveReset(attemptsLeft - 1);
11761
+ });
11762
+ }
11763
+ deferHandlerActiveReset(10);
11576
11764
  if (e.__legacyOutputDidListenersThrowError) {
11577
11765
  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
11578
11766
  req.transaction.__abortTransaction(createDOMException('AbortError', 'A request was aborted.'));
@@ -11588,6 +11776,7 @@
11588
11776
  * @returns {void}
11589
11777
  */
11590
11778
  function (ev) {
11779
+ pendingVersionChanges.delete(name);
11591
11780
  connection.__upgradeTransaction = null;
11592
11781
  /** @type {import('./IDBDatabase.js').IDBDatabaseFull} */
11593
11782
  req.__result.__versionTransaction = null;
@@ -11602,6 +11791,8 @@
11602
11791
 
11603
11792
  // eslint-disable-next-line camelcase -- Clear API
11604
11793
  req.transaction.on__preabort = function () {
11794
+ isRevertingSysdb = true;
11795
+ pendingVersionChanges.delete(name);
11605
11796
  connection.__upgradeTransaction = null;
11606
11797
  // We ensure any cache is deleted before any request error events fire and try to reopen
11607
11798
  if (useDatabaseCache) {
@@ -11618,6 +11809,7 @@
11618
11809
  req.__result = undefined;
11619
11810
  req.__done = false;
11620
11811
  connection.close();
11812
+ isRevertingSysdb = true;
11621
11813
  setTimeout(function () {
11622
11814
  var err = createDOMException('AbortError', 'The upgrade transaction was aborted.');
11623
11815
  sysdbFinishedCb(systx, err, function (reportError) {
@@ -11664,6 +11856,10 @@
11664
11856
  // });
11665
11857
  };
11666
11858
  }
11859
+ pendingVersionChanges.set(name, {
11860
+ oldVersion: oldVersion,
11861
+ newVersion: version
11862
+ });
11667
11863
  if (oldVersion === 0) {
11668
11864
  systx.executeSql('INSERT INTO dbVersions VALUES (?,?)', [sqlSafeName, version], versionSet, dbCreateError);
11669
11865
  } else {
@@ -11675,7 +11871,26 @@
11675
11871
  }
11676
11872
  sysdbFinishedCb = function sysdbFinishedCb(systx, err, cb) {
11677
11873
  if (err) {
11678
- rollback(err, cb);
11874
+ rollback(err,
11875
+ /**
11876
+ * @param {Error} [reportError]
11877
+ * @returns {void}
11878
+ */
11879
+ function (reportError) {
11880
+ sysdb.transaction(function (systx) {
11881
+ if (oldVersion === 0) {
11882
+ systx.executeSql('DELETE FROM dbVersions WHERE "name" = ?', [sqlSafeName]);
11883
+ } else {
11884
+ systx.executeSql('UPDATE dbVersions SET "version" = ? WHERE "name" = ?', [oldVersion, sqlSafeName]);
11885
+ }
11886
+ }, function (sqlErr) {
11887
+ isRevertingSysdb = false;
11888
+ cb(sqlErr); // eslint-disable-line promise/no-callback-in-promise -- Convenient
11889
+ }, function () {
11890
+ isRevertingSysdb = false;
11891
+ cb(reportError); // eslint-disable-line promise/no-callback-in-promise -- Convenient
11892
+ });
11893
+ });
11679
11894
  } else {
11680
11895
  commit(cb);
11681
11896
  }
@@ -11966,6 +12181,15 @@
11966
12181
  IDBFactory.prototype.databases = function () {
11967
12182
  var me = this;
11968
12183
  var calledDbCreateError = false;
12184
+ // Snapshotted *now*, synchronously, at call time -- not read later
12185
+ // from inside the SQL query's callback below, which runs on a
12186
+ // deferred macrotask (see `nodeSQLiteDatabase.js`'s `exec`) and so
12187
+ // could otherwise race against (and lose to) an in-flight upgrade's
12188
+ // own commit/abort handler clearing its `pendingVersionChanges`
12189
+ // entry in the meantime -- which would make this method incorrectly
12190
+ // reflect a since-committed change that hadn't committed yet when
12191
+ // it was actually called.
12192
+ var pendingVersionChangesSnapshot = new Map(pendingVersionChanges);
11969
12193
  return new Promise(function (resolve, reject) {
11970
12194
  // eslint-disable-line promise/avoid-new -- Own polyfill
11971
12195
  if (!(me instanceof IDBFactory)) {
@@ -11995,10 +12219,29 @@
11995
12219
  var dbNames = [];
11996
12220
  for (var i = 0; i < data.rows.length; i++) {
11997
12221
  var _data$rows$item2 = /** @type {{name: string, version: Integer}} */data.rows.item(i),
11998
- name = _data$rows$item2.name,
12222
+ encodedName = _data$rows$item2.name,
11999
12223
  version = _data$rows$item2.version;
12224
+ var name = unescapeSQLiteResponse(encodedName);
12225
+ // A row for a database whose creation/upgrade hasn't
12226
+ // committed yet (see `pendingVersionChanges`) must
12227
+ // not reflect that in-flight change: a brand new
12228
+ // database (`oldVersion === 0`) isn't reported at
12229
+ // all until its creation commits, and an existing
12230
+ // database being upgraded is still reported, but
12231
+ // with its pre-upgrade version.
12232
+ var pending = pendingVersionChangesSnapshot.get(name);
12233
+ if (pending) {
12234
+ if (pending.oldVersion === 0) {
12235
+ continue;
12236
+ }
12237
+ dbNames.push({
12238
+ name: name,
12239
+ version: pending.oldVersion
12240
+ });
12241
+ continue;
12242
+ }
12000
12243
  dbNames.push({
12001
- name: unescapeSQLiteResponse(name),
12244
+ name: name,
12002
12245
  version: version
12003
12246
  });
12004
12247
  }
@@ -12418,9 +12661,26 @@
12418
12661
  // Key.convertValueToKey(key); // Already checked by `continue` or `continuePrimaryKey`
12419
12662
  sqlValues.push(/** @type {string} */_encode(key));
12420
12663
  } else if (continueCall && me.__key !== undefined) {
12421
- sql.push('AND', quotedKeyColumnName, op + ' ?');
12422
12664
  // Key.convertValueToKey(me.__key); // Already checked when stored
12423
- sqlValues.push(/** @type {string} */_encode(me.__key));
12665
+ if (!me.__unique && me.__keyColumnName !== 'key' && me.__primaryKey !== undefined) {
12666
+ // A plain `continue()` on a non-unique index cursor must find
12667
+ // the next record strictly after the (key, primaryKey) pair
12668
+ // this cursor last returned -- a scalar `key > lastKey` alone
12669
+ // would wrongly exclude a *different*, not-yet-visited record
12670
+ // that's still tied on key with the last one (e.g. another
12671
+ // record that always had that same indexed value), and would
12672
+ // also wrongly re-admit the *same* record forever if a
12673
+ // same-transaction `update()` bumped its own key back above
12674
+ // the threshold, since a scalar comparison can't distinguish
12675
+ // "some other record newly tied" from "this record moved
12676
+ // past its own last position." Comparing the full tuple (via
12677
+ // this OR) against both key and primary key resolves both.
12678
+ sql.push('AND (', quotedKeyColumnName, op, '?', 'OR (', quotedKeyColumnName, '= ?', 'AND', quotedKey, op, '?))');
12679
+ sqlValues.push(/** @type {string} */_encode(me.__key), /** @type {string} */_encode(me.__key), /** @type {string} */_encode(me.__primaryKey));
12680
+ } else {
12681
+ sql.push('AND', quotedKeyColumnName, op + ' ?');
12682
+ sqlValues.push(/** @type {string} */_encode(me.__key));
12683
+ }
12424
12684
  }
12425
12685
  if (!me.__count) {
12426
12686
  // 1. Sort by key
@@ -13050,9 +13310,15 @@
13050
13310
  * @returns {void}
13051
13311
  */
13052
13312
  function addToQueue(clonedValue) {
13053
- // We set the `invalidateCache` argument to `false` since the old value shouldn't be accessed
13313
+ // `invalidateCache: true` so any cursor on this store (including
13314
+ // this one) drops its prefetched row batch, forcing its next
13315
+ // `continue()` to re-query live rather than serve rows snapshotted
13316
+ // before this update -- needed for the compound-tuple
13317
+ // continuation logic in `__findBasic` to see this update's
13318
+ // effect on ordering (see "Modify records during cursor
13319
+ // iteration" in idbcursor_update_index.any.js).
13054
13320
  // @ts-ignore -- API (not erring in TS 6)
13055
- IDBObjectStore.__storingRecordObjectStore(request, me.__store, false, clonedValue, false, key);
13321
+ IDBObjectStore.__storingRecordObjectStore(request, me.__store, true, clonedValue, false, key);
13056
13322
  }
13057
13323
  if (me.__store.keyPath !== null) {
13058
13324
  var _me$__store$__validat = me.__store.__validateKeyAndValueAndCloneValue(valueToUpdate, undefined, true),