indexeddbshim 17.3.3 → 17.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.md +3 -146
  2. package/dist/CFG.d.ts +1 -0
  3. package/dist/CFG.d.ts.map +1 -1
  4. package/dist/DOMException.d.ts +4 -4
  5. package/dist/DOMException.d.ts.map +1 -1
  6. package/dist/IDBFactory.d.ts.map +1 -1
  7. package/dist/IDBTransaction.d.ts +8 -6
  8. package/dist/IDBTransaction.d.ts.map +1 -1
  9. package/dist/Key.d.ts.map +1 -1
  10. package/dist/Sca.d.ts.map +1 -1
  11. package/dist/indexeddbshim-Key.js +54 -14
  12. package/dist/indexeddbshim-Key.js.map +1 -1
  13. package/dist/indexeddbshim-Key.min.js +2 -2
  14. package/dist/indexeddbshim-Key.min.js.map +1 -1
  15. package/dist/indexeddbshim-UnicodeIdentifiers-node.cjs +212 -80
  16. package/dist/indexeddbshim-UnicodeIdentifiers-node.cjs.map +1 -1
  17. package/dist/indexeddbshim-UnicodeIdentifiers.js +201 -79
  18. package/dist/indexeddbshim-UnicodeIdentifiers.js.map +1 -1
  19. package/dist/indexeddbshim-UnicodeIdentifiers.min.js +3 -3
  20. package/dist/indexeddbshim-UnicodeIdentifiers.min.js.map +1 -1
  21. package/dist/indexeddbshim-node.cjs +212 -80
  22. package/dist/indexeddbshim-node.cjs.map +1 -1
  23. package/dist/indexeddbshim-noninvasive.js +201 -79
  24. package/dist/indexeddbshim-noninvasive.js.map +1 -1
  25. package/dist/indexeddbshim-noninvasive.min.js +3 -3
  26. package/dist/indexeddbshim-noninvasive.min.js.map +1 -1
  27. package/dist/indexeddbshim.js +201 -79
  28. package/dist/indexeddbshim.js.map +1 -1
  29. package/dist/indexeddbshim.min.js +3 -3
  30. package/dist/indexeddbshim.min.js.map +1 -1
  31. package/dist/nodeSQLiteDatabase.d.ts.map +1 -1
  32. package/dist/nodeWebSQL.d.ts.map +1 -1
  33. package/package.json +8 -9
  34. package/src/CFG.js +2 -0
  35. package/src/DOMException.js +21 -21
  36. package/src/IDBFactory.js +88 -39
  37. package/src/IDBObjectStore.js +1 -1
  38. package/src/IDBTransaction.js +7 -1
  39. package/src/Key.js +34 -12
  40. package/src/Sca.js +53 -3
  41. package/src/nodeSQLiteDatabase.js +10 -1
  42. package/src/nodeWebSQL.js +3 -0
  43. package/src/setGlobalVars.js +3 -3
@@ -1,4 +1,4 @@
1
- /*! indexeddbshim - v17.3.3 - 8/25/2026 */
1
+ /*! indexeddbshim - v17.4.0 - 8/27/2026 */
2
2
 
3
3
  'use strict';
4
4
 
@@ -1389,6 +1389,7 @@ EventTarget.EventTargetFactory = EventTargetFactory;
1389
1389
  * sqlBusyTimeout: number,
1390
1390
  * sqlTrace: () => void,
1391
1391
  * sqlProfile: () => void,
1392
+ * sqlMemoryQuota: number,
1392
1393
  * escapeNULForSQLiteStatements: boolean,
1393
1394
  * createIndexes: boolean
1394
1395
  * }} ConfigValues
@@ -1545,7 +1546,7 @@ val => {
1545
1546
  'sqlProfile',
1546
1547
  // Callback not used by default
1547
1548
  // Defaults to true except in Node builds where we can preserve literal NUL with better-sqlite3
1548
- 'escapeNULForSQLiteStatements', 'createIndexes'].forEach(prop => {
1549
+ 'escapeNULForSQLiteStatements', 'sqlMemoryQuota', 'createIndexes'].forEach(prop => {
1549
1550
  /** @type {(val: any) => void} */
1550
1551
  let validator;
1551
1552
  if (Array.isArray(prop)) {
@@ -2564,41 +2565,40 @@ function findError(args) {
2564
2565
  * optional here and, in practice, always `undefined` today (the `case 4`/
2565
2566
  * `case 7` branches below are effectively unreachable pending upstream
2566
2567
  * support for surfacing a real error code).
2567
- * @param {Error & {code?: number}} webSQLErr
2568
+ * @param {Error & {code?: number|string}} webSQLErr
2568
2569
  * @returns {(DOMException|Error) & {
2569
- * sqlError: Error & {code?: number}
2570
+ * sqlError: Error & {code?: number|string}
2570
2571
  * }|QuotaExceededError}
2571
2572
  */
2572
2573
  function webSQLErrback(webSQLErr) {
2573
2574
  let name,
2574
2575
  message,
2575
2576
  useQuotaExceededError = false;
2576
- switch (webSQLErr.code) {
2577
- case 4:
2578
- {
2579
- // SQLError.QUOTA_ERR
2580
- name = 'QuotaExceededError';
2581
- message = 'The operation failed because there was not enough ' + 'remaining storage space, or the storage quota was reached ' + 'and the user declined to give more space to the database.';
2582
- if (typeof QuotaExceededError !== 'undefined') {
2583
- useQuotaExceededError = true;
2584
- }
2585
- break;
2586
- }
2587
- /*
2588
- // Should a WebSQL timeout treat as IndexedDB `TransactionInactiveError` or `UnknownError`?
2589
- case 7: { // SQLError.TIMEOUT_ERR
2590
- // All transaction errors abort later, so no need to mark inactive
2591
- name = 'TransactionInactiveError';
2592
- message = 'A request was placed against a transaction which is currently not active, or which is finished (Internal SQL Timeout).';
2593
- break;
2577
+ if (webSQLErr.code === 4 || webSQLErr.code === 'SQLITE_FULL') {
2578
+ // SQLError.QUOTA_ERR or better-sqlite3 SQLITE_FULL
2579
+ name = 'QuotaExceededError';
2580
+ message = 'The operation failed because there was not enough ' + 'remaining storage space, or the storage quota was reached ' + 'and the user declined to give more space to the database.';
2581
+ if (typeof QuotaExceededError !== 'undefined') {
2582
+ useQuotaExceededError = true;
2594
2583
  }
2595
- */
2596
- default:
2597
- {
2598
- name = 'UnknownError';
2599
- message = 'The operation failed for reasons unrelated to the database itself and not covered by any other errors.';
2600
- break;
2584
+ } else {
2585
+ switch (webSQLErr.code) {
2586
+ /*
2587
+ // Should a WebSQL timeout treat as IndexedDB `TransactionInactiveError` or `UnknownError`?
2588
+ case 7: { // SQLError.TIMEOUT_ERR
2589
+ // All transaction errors abort later, so no need to mark inactive
2590
+ name = 'TransactionInactiveError';
2591
+ message = 'A request was placed against a transaction which is currently not active, or which is finished (Internal SQL Timeout).';
2592
+ break;
2601
2593
  }
2594
+ */
2595
+ default:
2596
+ {
2597
+ name = 'UnknownError';
2598
+ message = 'The operation failed for reasons unrelated to the database itself and not covered by any other errors.';
2599
+ break;
2600
+ }
2601
+ }
2602
2602
  }
2603
2603
  message += ' (' + webSQLErr.message + ')--(' + webSQLErr.code + ')';
2604
2604
  if (useQuotaExceededError) {
@@ -2607,7 +2607,7 @@ function webSQLErrback(webSQLErr) {
2607
2607
  const err =
2608
2608
  /**
2609
2609
  * @type {(Error | DOMException) & {
2610
- * sqlError: Error & {code?: number}
2610
+ * sqlError: Error & {code?: number|string}
2611
2611
  * }}
2612
2612
  */
2613
2613
  createDOMException(name, message);
@@ -3159,6 +3159,31 @@ function cmp(first, second) {
3159
3159
  return result;
3160
3160
  }
3161
3161
 
3162
+ /**
3163
+ * @param {unknown[]} arr
3164
+ * @param {number} index
3165
+ * @param {unknown} val
3166
+ * @returns {void}
3167
+ */
3168
+ const setArrayValue = (arr, index, val) => {
3169
+ if (Reflect.has(Array.prototype, index)) {
3170
+ Object.defineProperty(arr, index, {
3171
+ value: val,
3172
+ enumerable: true,
3173
+ writable: true,
3174
+ configurable: true
3175
+ });
3176
+ } else {
3177
+ arr[index] = val;
3178
+ }
3179
+ };
3180
+ /**
3181
+ * @param {unknown[]} arr
3182
+ * @param {unknown} val
3183
+ * @returns {void}
3184
+ */
3185
+ const safePush = (arr, val) => setArrayValue(arr, arr.length, val);
3186
+
3162
3187
  /**
3163
3188
  * @typedef {NodeJS.TypedArray|DataView} ArrayBufferView
3164
3189
  */
@@ -3421,12 +3446,13 @@ const types = {
3421
3446
  * @returns {string}
3422
3447
  */
3423
3448
  encode(key) {
3449
+ /** @type {(string|null)[]} */
3424
3450
  const encoded = [];
3425
3451
  for (const [i, item] of key.entries()) {
3426
3452
  const encodedItem = encode$1(item, true); // encode the array item
3427
- encoded[i] = encodedItem;
3453
+ setArrayValue(encoded, i, encodedItem);
3428
3454
  }
3429
- encoded.push(keyTypeToEncodedChar.invalid + '-'); // append an extra item, so empty arrays sort correctly
3455
+ safePush(encoded, keyTypeToEncodedChar.invalid + '-'); // append an extra item, so empty arrays sort correctly
3430
3456
  let encodedKey = JSON.stringify(encoded);
3431
3457
  if (CFG.escapeNULForSQLiteStatements === false) {
3432
3458
  encodedKey = encodedKey.replaceAll(String.raw`\u0000`, '\0');
@@ -3447,7 +3473,7 @@ const types = {
3447
3473
  for (let i = 0; i < decoded.length; i++) {
3448
3474
  const item = decoded[i];
3449
3475
  const decodedItem = decode$1(item, true); // decode the item
3450
- decoded[i] = decodedItem;
3476
+ setArrayValue(decoded, i, decodedItem);
3451
3477
  }
3452
3478
  return decoded;
3453
3479
  }
@@ -3733,7 +3759,7 @@ function convertValueToKeyValueDecoded(input, seen, multiEntry, fullKeys) {
3733
3759
  // May throw (from binary)
3734
3760
  const arr = /** @type {Array<any>} */input;
3735
3761
  const len = arr.length;
3736
- seen.push(input);
3762
+ safePush(seen, input);
3737
3763
 
3738
3764
  /** @type {(KeyValueObject|Value)[]} */
3739
3765
  const keys = [];
@@ -3760,7 +3786,7 @@ function convertValueToKeyValueDecoded(input, seen, multiEntry, fullKeys) {
3760
3786
  };
3761
3787
  }
3762
3788
  if (!multiEntry || !fullKeys && keys.every(k => cmp(k, key.value) !== 0) || fullKeys && keys.every(k => cmp(k, key) !== 0)) {
3763
- keys.push(fullKeys ? key : key.value);
3789
+ safePush(keys, fullKeys ? key : key.value);
3764
3790
  }
3765
3791
  } catch (err) {
3766
3792
  if (!multiEntry) {
@@ -3914,7 +3940,7 @@ function evaluateKeyPathOnValueToDecodedValue(value, keyPath, multiEntry, fullKe
3914
3940
  if (key.failure) {
3915
3941
  return true;
3916
3942
  }
3917
- result.push(key.value);
3943
+ safePush(result, key.value);
3918
3944
  return false;
3919
3945
  }) ? {
3920
3946
  failure: true
@@ -3975,11 +4001,21 @@ function injectKeyIntoValueUsingKeyPath(value, key, keyPath) {
3975
4001
  identifiers.forEach(identifier => {
3976
4002
  const hop = Object.hasOwn(value, identifier);
3977
4003
  if (!hop) {
3978
- value[identifier] = {};
4004
+ Object.defineProperty(value, identifier, {
4005
+ value: {},
4006
+ enumerable: true,
4007
+ writable: true,
4008
+ configurable: true
4009
+ });
3979
4010
  }
3980
4011
  value = value[identifier];
3981
4012
  });
3982
- value[(/** @type {string} */last)] = key; // key is already a `keyValue` in our processing so no need to convert
4013
+ Object.defineProperty(value, /** @type {string} */last, {
4014
+ value: key,
4015
+ enumerable: true,
4016
+ writable: true,
4017
+ configurable: true
4018
+ }); // key is already a `keyValue` in our processing so no need to convert
3983
4019
  }
3984
4020
 
3985
4021
  /**
@@ -4048,6 +4084,7 @@ function isMultiEntryMatch(encodedEntry, encodedKey) {
4048
4084
  * @returns {Key[]}
4049
4085
  */
4050
4086
  function findMultiEntryMatches(keyEntry, range) {
4087
+ /** @type {unknown[]} */
4051
4088
  const matches = [];
4052
4089
  if (Array.isArray(keyEntry)) {
4053
4090
  for (let key of keyEntry) {
@@ -4060,17 +4097,17 @@ function findMultiEntryMatches(keyEntry, range) {
4060
4097
  } else {
4061
4098
  const nested = findMultiEntryMatches(key, range);
4062
4099
  if (nested.length > 0) {
4063
- matches.push(key);
4100
+ safePush(matches, key);
4064
4101
  }
4065
4102
  continue;
4066
4103
  }
4067
4104
  }
4068
4105
  if (isNullish(range) || isKeyInRange(key, range, true)) {
4069
- matches.push(key);
4106
+ safePush(matches, key);
4070
4107
  }
4071
4108
  }
4072
4109
  } else if (isNullish(range) || isKeyInRange(keyEntry, range, true)) {
4073
- matches.push(keyEntry);
4110
+ safePush(matches, keyEntry);
4074
4111
  }
4075
4112
  return matches;
4076
4113
  }
@@ -4094,12 +4131,13 @@ function convertKeyToValue(key) {
4094
4131
  }
4095
4132
  case 'array':
4096
4133
  {
4134
+ /** @type {ValueType[]} */
4097
4135
  const array = [];
4098
4136
  const len = value.length;
4099
4137
  let index = 0;
4100
4138
  while (index < len) {
4101
4139
  const entry = convertKeyToValue(value[index]);
4102
- array[index] = entry;
4140
+ setArrayValue(array, index, entry);
4103
4141
  index++;
4104
4142
  }
4105
4143
  return array;
@@ -4862,6 +4900,7 @@ if (cleanInterface) {
4862
4900
  }
4863
4901
 
4864
4902
  let uniqueID = 0;
4903
+ const activeTransactions = new Set();
4865
4904
  const listeners$1 = ['onabort', 'oncomplete', 'onerror'];
4866
4905
  const readonlyProperties$4 = ['objectStoreNames', 'mode', 'durability', 'db', 'error'];
4867
4906
 
@@ -4991,6 +5030,7 @@ IDBTransaction.__createInstance = function (db, storeNames, mode, durability = '
4991
5030
  me.__mode = mode;
4992
5031
  me.__durability = durability;
4993
5032
  me.__db = db;
5033
+ activeTransactions.add(me);
4994
5034
  me.__error = null;
4995
5035
  // @ts-expect-error Part of `ShimEventTarget`
4996
5036
  me.__setOptions({
@@ -5498,6 +5538,7 @@ IDBTransaction.prototype.__executeRequests = function () {
5498
5538
  me.__errored = true;
5499
5539
  throw e;
5500
5540
  } finally {
5541
+ activeTransactions.delete(me);
5501
5542
  me.__storeHandles = {};
5502
5543
  }
5503
5544
  }
@@ -5704,7 +5745,7 @@ IDBTransaction.prototype.__abortTransaction = function (err) {
5704
5745
  });
5705
5746
  }
5706
5747
  me.__active = false; // Setting here and in requestsFinished for https://github.com/w3c/IndexedDB/issues/87
5707
-
5748
+ activeTransactions.delete(me);
5708
5749
  if (err !== null) {
5709
5750
  me.__error = err;
5710
5751
  }
@@ -5923,7 +5964,7 @@ IDBTransaction.__assertNotFinishedObjectStoreMethod = function (tx) {
5923
5964
  * @returns {void}
5924
5965
  */
5925
5966
  IDBTransaction.__assertActive = function (tx) {
5926
- if (!tx || !tx.__active || tx.__committed) {
5967
+ if (!tx || !tx.__active || !tx.__handlerActive || tx.__committed) {
5927
5968
  throw createDOMException('TransactionInactiveError', 'A request was placed against a transaction which is currently not active, or which is finished');
5928
5969
  }
5929
5970
  };
@@ -5950,6 +5991,9 @@ Object.defineProperty(IDBTransaction.prototype, 'constructor', {
5950
5991
  Object.defineProperty(IDBTransaction, 'prototype', {
5951
5992
  writable: false
5952
5993
  });
5994
+ /* eslint-enable unicorn/no-top-level-side-effects -- Would be good */
5995
+
5996
+ IDBTransaction.activeTransactions = activeTransactions;
5953
5997
 
5954
5998
  class TypesonPromise {
5955
5999
  constructor(e) {
@@ -7095,14 +7139,14 @@ const B = {
7095
7139
  revive: () => 1 / 0
7096
7140
  }
7097
7141
  },
7098
- k = {
7142
+ D = {
7099
7143
  map: {
7100
7144
  test: e => "Map" === toStringTag(e),
7101
7145
  replace: e => e.entries().toArray(),
7102
7146
  revive: e => new Map(e)
7103
7147
  }
7104
7148
  },
7105
- D = {
7149
+ R = {
7106
7150
  nan: {
7107
7151
  test: e => Number.isNaN(e),
7108
7152
  replace: () => "NaN",
@@ -7357,8 +7401,8 @@ const Y = {
7357
7401
  revive() {}
7358
7402
  }
7359
7403
  }],
7360
- ne = [D, M, L, F],
7361
- 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 : []);
7404
+ ne = [R, M, L, F],
7405
+ 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 : []);
7362
7406
  const ue = ce.concat({
7363
7407
  checkDataCloneException: {
7364
7408
  test(e) {
@@ -7375,11 +7419,53 @@ const ue = ce.concat({
7375
7419
  return false;
7376
7420
  }
7377
7421
  }
7422
+ }),
7423
+ ye = ue.concat({
7424
+ checkSharedArrayBufferException: {
7425
+ test(e) {
7426
+ if ("SharedArrayBuffer" === {}.toString.call(e).slice(8, -1)) throw new DOMException("The object cannot be cloned.", "DataCloneError");
7427
+ return false;
7428
+ }
7429
+ }
7378
7430
  });
7379
7431
 
7380
7432
  // See: https://stackoverflow.com/questions/42170826/categories-for-rejection-by-the-structured-cloning-algorithm
7381
7433
 
7382
- let typeson = new Typeson().register(ue);
7434
+ // Although typeson-registry already has a FileList type in its structured cloning presets,
7435
+ // we need to override it so it works with our tests
7436
+
7437
+ const specSet = ye.flatMap(preset => Array.isArray(preset) ? preset : [preset]).find(preset => preset && !Array.isArray(preset) && 'filelist' in preset);
7438
+ const origFileList = specSet && !Array.isArray(specSet) && 'filelist' in specSet ? specSet.filelist : undefined;
7439
+ const origTest = origFileList && typeof origFileList === 'object' && 'test' in origFileList && typeof origFileList.test === 'function' ? origFileList.test : undefined;
7440
+ const origRevive = origFileList && typeof origFileList === 'object' && 'revive' in origFileList && typeof origFileList.revive === 'function' ? origFileList.revive : undefined;
7441
+ const customFileList = origFileList ? {
7442
+ ...origFileList,
7443
+ /**
7444
+ * @param {unknown} x
7445
+ * @param {import('typeson').StateObject} state
7446
+ * @returns {boolean}
7447
+ */
7448
+ test(x, state) {
7449
+ if (typeof FileList !== 'undefined') {
7450
+ return x instanceof FileList;
7451
+ }
7452
+ return typeof origTest === 'function' ? origTest(x, state) : false;
7453
+ },
7454
+ /**
7455
+ * @param {unknown} x
7456
+ * @param {import('typeson').StateObject} state
7457
+ * @returns {unknown}
7458
+ */
7459
+ revive(x, state) {
7460
+ if (typeof FileList !== 'undefined') {
7461
+ return Reflect.construct(FileList, [x]);
7462
+ }
7463
+ return typeof origRevive === 'function' ? origRevive(x, state) : undefined;
7464
+ }
7465
+ } : undefined;
7466
+ let typeson = new Typeson().register([ye, customFileList ? {
7467
+ filelist: customFileList
7468
+ } : {}]);
7383
7469
 
7384
7470
  /**
7385
7471
  * @param {(preset: import('typeson-registry').Preset) =>
@@ -7388,7 +7474,7 @@ let typeson = new Typeson().register(ue);
7388
7474
  */
7389
7475
  function register(func) {
7390
7476
  // eslint-disable-next-line unicorn/no-top-level-assignment-in-function -- Should be one-time cache
7391
- typeson = new Typeson().register(func(ue));
7477
+ typeson = new Typeson().register(func(ye));
7392
7478
  }
7393
7479
 
7394
7480
  /**
@@ -8676,7 +8762,7 @@ IDBObjectStore.__createObjectStore = function (db, store) {
8676
8762
  tx.executeSql('INSERT INTO __sys__ VALUES (?,?,?,?,?)', [escapeSQLiteStatement(storeName), encodedKeyPath,
8677
8763
  // For why converting here, see comment and following
8678
8764
  // discussion at:
8679
- // https://github.com/axemclion/IndexedDBShim/issues/313#issuecomment-590086778
8765
+ // https://github.com/indexeddbshim/IndexedDBShim/issues/313#issuecomment-590086778
8680
8766
  Number(store.autoIncrement), '{}', 1], function () {
8681
8767
  delete store.__pendingCreate;
8682
8768
  delete store.__deleted;
@@ -10449,6 +10535,7 @@ IDBFactory.prototype.open = function (name /* , version */) {
10449
10535
  }
10450
10536
  const req = IDBOpenDBRequest.__createInstance();
10451
10537
  let calledDbCreateError = false;
10538
+ let isRevertingSysdb = false;
10452
10539
  if (CFG.autoName && name === '') {
10453
10540
  // eslint-disable-next-line unicorn/no-top-level-assignment-in-function -- Necessary?
10454
10541
  name = 'autoNamedDatabase_' + nameCounter++;
@@ -10475,7 +10562,7 @@ IDBFactory.prototype.open = function (name /* , version */) {
10475
10562
  * @returns {boolean}
10476
10563
  */
10477
10564
  function dbCreateError(tx, err) {
10478
- if (calledDbCreateError) {
10565
+ if (calledDbCreateError || isRevertingSysdb) {
10479
10566
  return false;
10480
10567
  }
10481
10568
  // Defensive cleanup: `pendingVersionChanges.set(name, ...)` (see
@@ -10539,36 +10626,46 @@ IDBFactory.prototype.open = function (name /* , version */) {
10539
10626
  */
10540
10627
  let sysdbFinishedCb = function (systx, err, cb) {
10541
10628
  if (err) {
10542
- try {
10543
- systx.executeSql('ROLLBACK', [], cb, cb);
10544
- } catch (err) {
10545
- // Browser may fail with expired transaction above so
10546
- // no choice but to manually revert
10629
+ /**
10630
+ * @param {any} [errorToShow]
10631
+ * @returns {void}
10632
+ */
10633
+ const manualRevert = function (errorToShow) {
10634
+ /**
10635
+ * @param {string} [msg]
10636
+ * @throws {Error}
10637
+ * @returns {never}
10638
+ */
10639
+ function reportError(msg) {
10640
+ throw new Error('Unable to roll back upgrade transaction!' + (msg || ''));
10641
+ }
10547
10642
  sysdb.transaction(function (systx) {
10548
- /**
10549
- *
10550
- * @param {string} msg
10551
- * @throws {Error}
10552
- * @returns {never}
10553
- */
10554
- function reportError(msg) {
10555
- throw new Error('Unable to roll back upgrade transaction!' + (msg || ''));
10556
- }
10557
-
10558
10643
  // Attempt to revert
10559
10644
  if (oldVersion === 0) {
10560
- systx.executeSql('DELETE FROM dbVersions WHERE "name" = ?', [sqlSafeName], function () {
10561
- // @ts-expect-error Force to work
10562
- cb(reportError); // eslint-disable-line promise/no-callback-in-promise -- Convenient
10563
- },
10564
- // @ts-expect-error Force to work
10565
- reportError);
10645
+ systx.executeSql('DELETE FROM dbVersions WHERE "name" = ?', [sqlSafeName]);
10566
10646
  } else {
10567
- systx.executeSql('UPDATE dbVersions SET "version" = ? WHERE "name" = ?', [oldVersion, sqlSafeName], cb,
10568
- // @ts-expect-error Force to work
10569
- reportError);
10647
+ systx.executeSql('UPDATE dbVersions SET "version" = ? WHERE "name" = ?', [oldVersion, sqlSafeName]);
10570
10648
  }
10649
+ }, function (sqlErr) {
10650
+ isRevertingSysdb = false;
10651
+ cb(sqlErr); // eslint-disable-line promise/no-callback-in-promise -- Convenient
10652
+ }, function () {
10653
+ isRevertingSysdb = false;
10654
+ cb(errorToShow || reportError); // eslint-disable-line promise/no-callback-in-promise -- Convenient
10571
10655
  });
10656
+ };
10657
+ try {
10658
+ systx.executeSql('ROLLBACK', [], function () {
10659
+ cb(); // eslint-disable-line promise/no-callback-in-promise -- Convenient
10660
+ }, function (tx, sqlErr) {
10661
+ // Browser/Node may fail with expired transaction, so manually revert
10662
+ manualRevert(sqlErr);
10663
+ return false;
10664
+ });
10665
+ } catch (e) {
10666
+ // Browser may fail with expired transaction above so
10667
+ // no choice but to manually revert
10668
+ manualRevert(e);
10572
10669
  }
10573
10670
  return;
10574
10671
  }
@@ -10669,6 +10766,7 @@ IDBFactory.prototype.open = function (name /* , version */) {
10669
10766
 
10670
10767
  // eslint-disable-next-line camelcase -- Clear API
10671
10768
  req.transaction.on__preabort = function () {
10769
+ isRevertingSysdb = true;
10672
10770
  pendingVersionChanges.delete(name);
10673
10771
  connection.__upgradeTransaction = null;
10674
10772
  // We ensure any cache is deleted before any request error events fire and try to reopen
@@ -10686,6 +10784,7 @@ IDBFactory.prototype.open = function (name /* , version */) {
10686
10784
  req.__result = undefined;
10687
10785
  req.__done = false;
10688
10786
  connection.close();
10787
+ isRevertingSysdb = true;
10689
10788
  setTimeout(() => {
10690
10789
  const err = createDOMException('AbortError', 'The upgrade transaction was aborted.');
10691
10790
  sysdbFinishedCb(systx, err, function (reportError) {
@@ -10747,7 +10846,26 @@ IDBFactory.prototype.open = function (name /* , version */) {
10747
10846
  }
10748
10847
  sysdbFinishedCb = function (systx, err, cb) {
10749
10848
  if (err) {
10750
- rollback(err, cb);
10849
+ rollback(err,
10850
+ /**
10851
+ * @param {Error} [reportError]
10852
+ * @returns {void}
10853
+ */
10854
+ function (reportError) {
10855
+ sysdb.transaction(function (systx) {
10856
+ if (oldVersion === 0) {
10857
+ systx.executeSql('DELETE FROM dbVersions WHERE "name" = ?', [sqlSafeName]);
10858
+ } else {
10859
+ systx.executeSql('UPDATE dbVersions SET "version" = ? WHERE "name" = ?', [oldVersion, sqlSafeName]);
10860
+ }
10861
+ }, function (sqlErr) {
10862
+ isRevertingSysdb = false;
10863
+ cb(sqlErr); // eslint-disable-line promise/no-callback-in-promise -- Convenient
10864
+ }, function () {
10865
+ isRevertingSysdb = false;
10866
+ cb(reportError); // eslint-disable-line promise/no-callback-in-promise -- Convenient
10867
+ });
10868
+ });
10751
10869
  } else {
10752
10870
  commit(cb);
10753
10871
  }
@@ -12827,7 +12945,7 @@ function setGlobalVars(idb, initialConfig) {
12827
12945
  // Ignore Node or other environments
12828
12946
  // Bad non-Chrome Android support
12829
12947
  /Android (?:2|3|4\.[0-3])/u.test(navigator.userAgent) && !navigator.userAgent.includes('Chrome') ||
12830
- // Bad non-Safari iOS9 support (see <https://github.com/axemclion/IndexedDBShim/issues/252>)
12948
+ // Bad non-Safari iOS9 support (see <https://github.com/indexeddbshim/IndexedDBShim/issues/252>)
12831
12949
  (!navigator.userAgent.includes('Safari') || navigator.userAgent.includes('Chrome')) &&
12832
12950
  // Exclude genuine Safari: https://stackoverflow.com/a/7768006/271577
12833
12951
  // Detect iOS: https://stackoverflow.com/questions/9038625/detect-if-device-is-ios/9039885#9039885
@@ -12837,8 +12955,8 @@ function setGlobalVars(idb, initialConfig) {
12837
12955
  if (!CFG.DEFAULT_DB_SIZE) {
12838
12956
  CFG.DEFAULT_DB_SIZE = (
12839
12957
  // Safari currently requires larger size: (We don't need a larger size for Node as node-websql doesn't use this info)
12840
- // https://github.com/axemclion/IndexedDBShim/issues/41
12841
- // https://github.com/axemclion/IndexedDBShim/issues/115
12958
+ // https://github.com/indexeddbshim/IndexedDBShim/issues/41
12959
+ // https://github.com/indexeddbshim/IndexedDBShim/issues/115
12842
12960
  typeof navigator !== 'undefined' &&
12843
12961
  // React Native
12844
12962
  navigator.userAgent && navigator.userAgent.includes('Safari') && !navigator.userAgent.includes('Chrome') ? 25 : 4) * 1024 * 1024;
@@ -14624,7 +14742,7 @@ function SQLiteDatabase(name, opts = {}) {
14624
14742
  _db: db,
14625
14743
  /**
14626
14744
  * Compatibility with node-sqlite3's configure API.
14627
- * @param {'busyTimeout'|'trace'|'profile'} option
14745
+ * @param {'busyTimeout'|'trace'|'profile'|'memoryQuota'} option
14628
14746
  * @param {number|((sql: string, duration?: number) => void)} value
14629
14747
  * @returns {void}
14630
14748
  */
@@ -14639,6 +14757,17 @@ function SQLiteDatabase(name, opts = {}) {
14639
14757
  }
14640
14758
  if (option === 'profile') {
14641
14759
  profile = /** @type {SQLProfileCallback} */value;
14760
+ return;
14761
+ }
14762
+ if (option === 'memoryQuota') {
14763
+ const bytes = Number(value);
14764
+ // `better-sqlite3` doesn't have a direct `sqlite3_quota_set` binding,
14765
+ // but we can limit the max pages:
14766
+ const pageSizeObj = db.pragma('page_size', {
14767
+ simple: true
14768
+ });
14769
+ const pageSize = Number(pageSizeObj);
14770
+ db.pragma('max_page_count = ' + Math.ceil(bytes / pageSize));
14642
14771
  }
14643
14772
  },
14644
14773
  /**
@@ -14873,6 +15002,9 @@ function wrappedSQLiteDatabase(name) {
14873
15002
  if (CFG.sqlProfile) {
14874
15003
  db._db.configure('profile', CFG.sqlProfile);
14875
15004
  }
15005
+ if (CFG.sqlMemoryQuota) {
15006
+ db._db.configure('memoryQuota', /** @type {number} */CFG.sqlMemoryQuota);
15007
+ }
14876
15008
  return db;
14877
15009
  }
14878
15010