indexeddbshim 17.0.0 → 17.1.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 (51) hide show
  1. package/README.md +62 -0
  2. package/badges/licenses-badge-dev.svg +1 -1
  3. package/badges/licenses-badge.svg +1 -1
  4. package/dist/CFG.d.ts +1 -0
  5. package/dist/CFG.d.ts.map +1 -1
  6. package/dist/IDBCursor.d.ts +9 -2
  7. package/dist/IDBCursor.d.ts.map +1 -1
  8. package/dist/IDBFactory.d.ts.map +1 -1
  9. package/dist/IDBIndex.d.ts.map +1 -1
  10. package/dist/IDBTransaction.d.ts +21 -0
  11. package/dist/IDBTransaction.d.ts.map +1 -1
  12. package/dist/Key.d.ts.map +1 -1
  13. package/dist/indexeddbshim-Key.js +25 -6
  14. package/dist/indexeddbshim-Key.js.map +1 -1
  15. package/dist/indexeddbshim-Key.min.js +2 -2
  16. package/dist/indexeddbshim-Key.min.js.map +1 -1
  17. package/dist/indexeddbshim-UnicodeIdentifiers-node.cjs +1402 -818
  18. package/dist/indexeddbshim-UnicodeIdentifiers-node.cjs.map +1 -1
  19. package/dist/indexeddbshim-UnicodeIdentifiers.js +467 -298
  20. package/dist/indexeddbshim-UnicodeIdentifiers.js.map +1 -1
  21. package/dist/indexeddbshim-UnicodeIdentifiers.min.js +4 -4
  22. package/dist/indexeddbshim-UnicodeIdentifiers.min.js.map +1 -1
  23. package/dist/indexeddbshim-node.cjs +1402 -818
  24. package/dist/indexeddbshim-node.cjs.map +1 -1
  25. package/dist/indexeddbshim-noninvasive.js +467 -298
  26. package/dist/indexeddbshim-noninvasive.js.map +1 -1
  27. package/dist/indexeddbshim-noninvasive.min.js +4 -4
  28. package/dist/indexeddbshim-noninvasive.min.js.map +1 -1
  29. package/dist/indexeddbshim.js +471 -295
  30. package/dist/indexeddbshim.js.map +1 -1
  31. package/dist/indexeddbshim.min.js +4 -4
  32. package/dist/indexeddbshim.min.js.map +1 -1
  33. package/dist/node-UnicodeIdentifiers.d.ts.map +1 -1
  34. package/dist/node.d.ts.map +1 -1
  35. package/dist/nodeSQLiteDatabase.d.ts +65 -0
  36. package/dist/nodeSQLiteDatabase.d.ts.map +1 -0
  37. package/dist/nodeWebSQL.d.ts.map +1 -1
  38. package/dist/util.d.ts +5 -0
  39. package/dist/util.d.ts.map +1 -1
  40. package/package.json +17 -13
  41. package/src/CFG.js +3 -0
  42. package/src/IDBCursor.js +136 -86
  43. package/src/IDBFactory.js +10 -6
  44. package/src/IDBIndex.js +1 -2
  45. package/src/IDBTransaction.js +32 -2
  46. package/src/Key.js +10 -2
  47. package/src/node-UnicodeIdentifiers.js +5 -1
  48. package/src/node.js +5 -1
  49. package/src/nodeSQLiteDatabase.js +174 -0
  50. package/src/nodeWebSQL.js +1 -3
  51. package/src/util.js +62 -3
@@ -1,4 +1,4 @@
1
- /*! indexeddbshim - v17.0.0 - 7/28/2026 */
1
+ /*! indexeddbshim - v17.1.0 - 8/6/2026 */
2
2
 
3
3
  (function (factory) {
4
4
  typeof define === 'function' && define.amd ? define(factory) :
@@ -58,7 +58,7 @@
58
58
  }
59
59
  }
60
60
  function _createClass(e, r, t) {
61
- return r && _defineProperties(e.prototype, r), Object.defineProperty(e, "prototype", {
61
+ return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", {
62
62
  writable: false
63
63
  }), e;
64
64
  }
@@ -301,6 +301,13 @@
301
301
  function _slicedToArray(r, e) {
302
302
  return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest();
303
303
  }
304
+ function _taggedTemplateLiteral(e, t) {
305
+ return t || (t = e.slice(0)), Object.freeze(Object.defineProperties(e, {
306
+ raw: {
307
+ value: Object.freeze(t)
308
+ }
309
+ }));
310
+ }
304
311
  function _toConsumableArray(r) {
305
312
  return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread();
306
313
  }
@@ -1307,6 +1314,7 @@
1307
1314
  * sqlBusyTimeout: number,
1308
1315
  * sqlTrace: () => void,
1309
1316
  * sqlProfile: () => void,
1317
+ * escapeNULForSQLiteStatements: boolean,
1310
1318
  * createIndexes: boolean
1311
1319
  * }} ConfigValues
1312
1320
  */
@@ -1461,8 +1469,8 @@
1461
1469
  // Callback not used by default
1462
1470
  'sqlProfile',
1463
1471
  // Callback not used by default
1464
-
1465
- 'createIndexes'].forEach(function (prop) {
1472
+ // Defaults to true except in Node builds where we can preserve literal NUL with better-sqlite3
1473
+ 'escapeNULForSQLiteStatements', 'createIndexes'].forEach(function (prop) {
1466
1474
  /** @type {(val: any) => void} */
1467
1475
  var validator;
1468
1476
  if (Array.isArray(prop)) {
@@ -1543,7 +1551,8 @@
1543
1551
  * @returns {string}
1544
1552
  */
1545
1553
  function escapeSQLiteStatement(arg) {
1546
- return escapeUnmatchedSurrogates(arg.replaceAll('^', '^^').replaceAll('\0', '^0'));
1554
+ var escaped = arg.replaceAll('^', '^^');
1555
+ return escapeUnmatchedSurrogates(CFG.escapeNULForSQLiteStatements === false ? escaped : escaped.replaceAll('\0', '^0'));
1547
1556
  }
1548
1557
 
1549
1558
  /**
@@ -1551,7 +1560,11 @@
1551
1560
  * @returns {string}
1552
1561
  */
1553
1562
  function unescapeSQLiteResponse(arg) {
1554
- return unescapeUnmatchedSurrogates(arg).replaceAll(/(\^+)0/g, function (_, esc) {
1563
+ var unescaped = unescapeUnmatchedSurrogates(arg);
1564
+ if (CFG.escapeNULForSQLiteStatements === false) {
1565
+ return unescaped.replaceAll('^^', '^');
1566
+ }
1567
+ return unescaped.replaceAll(/(\^+)0/g, function (_, esc) {
1555
1568
  return esc.length % 2 ? esc.slice(1) + '\0' : _;
1556
1569
  }).replaceAll('^^', '^');
1557
1570
  }
@@ -1952,6 +1965,56 @@
1952
1965
  return v === null || v === undefined;
1953
1966
  }
1954
1967
 
1968
+ /**
1969
+ * Cursor/request continuation chains can call each other synchronously
1970
+ * (e.g. walking a large prefetched buffer, or advancing many interleaved
1971
+ * cursors in one transaction) which, for large enough record counts, can
1972
+ * exceed the JS engine's call stack ("Maximum call stack size exceeded").
1973
+ *
1974
+ * This can't be fixed by deferring continuations to a microtask/macrotask:
1975
+ * the transaction machinery synchronously checks (once the current call
1976
+ * stack unwinds) whether there are any pending requests left in order to
1977
+ * decide it's safe to commit; deferring a continuation opens a real gap in
1978
+ * which that check runs first and the transaction completes prematurely,
1979
+ * with the deferred continuation then acting on an already-finished
1980
+ * transaction (a hang, since it can never resolve).
1981
+ *
1982
+ * Instead, this implements a true trampoline: the first call starts a
1983
+ * synchronous work queue and drains it in a flat loop; any call made while
1984
+ * already draining (i.e. a continuation triggering another continuation)
1985
+ * is simply appended to that same queue and returns immediately instead of
1986
+ * recursing. This keeps everything synchronous (so no completion-check
1987
+ * race is introduced) while preventing the call stack from growing with
1988
+ * each successive continuation.
1989
+ */
1990
+ var continuationState = {
1991
+ /** @type {Array<() => void>|null} */
1992
+ queue: null
1993
+ };
1994
+
1995
+ /**
1996
+ * @param {() => void} fn
1997
+ * @returns {void}
1998
+ */
1999
+ function runContinuationSafely(fn) {
2000
+ if (continuationState.queue) {
2001
+ continuationState.queue.push(fn);
2002
+ return;
2003
+ }
2004
+ var queue = [fn];
2005
+ continuationState.queue = queue;
2006
+ try {
2007
+ while (queue.length) {
2008
+ var next = /** @type {() => void} */queue.shift();
2009
+ next();
2010
+ }
2011
+ } finally {
2012
+ // Ensure a thrown exception can't leave the queue permanently
2013
+ // stuck (which would silently swallow all future continuations).
2014
+ continuationState.queue = null;
2015
+ }
2016
+ }
2017
+
1955
2018
  /**
1956
2019
  * @typedef {Error} DebuggingError
1957
2020
  */
@@ -2633,16 +2696,15 @@
2633
2696
  /* eslint-disable promise/prefer-await-to-callbacks -- Needed for API */
2634
2697
  /* eslint-disable promise/catch-or-return, n/callback-return,
2635
2698
  promise/always-return -- Not needed */
2636
- /* eslint-disable unicorn/no-this-assignment -- Clarity */
2637
2699
  // Since [immediate](https://github.com/calvinmetcalf/immediate) is
2638
2700
  // not doing the trick for our WebSQL transactions (at least in Node),
2639
2701
  // we are forced to make the promises run fully synchronously.
2640
2702
 
2641
- // Todo: Use ES6 classes
2642
-
2703
+ /* eslint-disable jsdoc/reject-any-type -- Truly arbitrary */
2643
2704
  /**
2644
2705
  * @typedef {any} ArbitraryValue
2645
2706
  */
2707
+ /* eslint-enable jsdoc/reject-any-type -- Truly arbitrary */
2646
2708
 
2647
2709
  /**
2648
2710
  * @callback Resolve
@@ -2702,209 +2764,235 @@
2702
2764
  REJECTED = 1;
2703
2765
 
2704
2766
  /**
2705
- * @class
2706
- * @param {(
2707
- * resolve: (value: ArbitraryValue | PromiseLike<ArbitraryValue>) => void,
2708
- * reject: (reason?: any) => void
2709
- * ) => void} fn
2710
- */
2711
- function SyncPromise(fn) {
2712
- var that = this;
2713
- // Value, this will be set to either a resolved value or rejected reason
2714
- that.v = 0;
2715
- // State of the promise
2716
- that.s = PENDING;
2717
- // Callbacks c[0] is fulfillment and c[1] contains rejection callbacks
2718
- /** @type {Callbacks|null} */
2719
- that.c = [[], []];
2767
+ *
2768
+ */
2769
+ var SyncPromise = /*#__PURE__*/function () {
2720
2770
  /**
2721
- *
2722
- * @param {ArbitraryValue} val
2723
- * @param {0|1} state
2724
- * @returns {void}
2771
+ * @param {(
2772
+ * resolve: (value: ArbitraryValue | PromiseLike<ArbitraryValue>) => void,
2773
+ * reject: (reason?: ArbitraryValue) => void
2774
+ * ) => void} fn
2725
2775
  */
2726
- function transist(val, state) {
2727
- that.v = val;
2728
- that.s = state;
2729
-
2730
- // console.log('state', state);
2731
- /** @type {Callbacks} */
2732
- that.c[state].forEach(function (func) {
2733
- func(val);
2734
- });
2735
- // Release memory, but if no handlers have been added, as we
2736
- // assume that we will resolve/reject (truly) synchronously
2737
- // and thus we avoid flagging checks about whether we've
2738
- // already resolved/rejected.
2739
- if (/** @type {Callbacks} */that.c[state].length) {
2740
- that.c = null;
2741
- }
2742
- }
2776
+ function SyncPromise(fn) {
2777
+ var _this = this;
2778
+ _classCallCheck(this, SyncPromise);
2779
+ // Value, this will be set to either a resolved value or rejected reason
2780
+ _defineProperty(this, "v", 0);
2781
+ // State of the promise
2782
+ this.s = PENDING;
2783
+ // Callbacks c[0] is fulfillment and c[1] contains rejection callbacks
2784
+ /** @type {Callbacks|null} */
2785
+ this.c = [[], []];
2786
+ /**
2787
+ *
2788
+ * @param {ArbitraryValue} val
2789
+ * @param {0|1} state
2790
+ * @returns {void}
2791
+ */
2792
+ var transist = function transist(val, state) {
2793
+ _this.v = val;
2794
+ _this.s = state;
2743
2795
 
2744
- /** @type {Resolve} */
2745
- function resolve(val) {
2746
- if (!that.c) ; else if (isPromise(val)) {
2747
- addReject(val.then(resolve), reject);
2748
- } else {
2749
- transist(val, FULFILLED);
2750
- }
2751
- }
2796
+ // console.log('state', state);
2797
+ /** @type {Callbacks} */
2798
+ _this.c[state].forEach(function (func) {
2799
+ func(val);
2800
+ });
2801
+ // Release memory, but if no handlers have been added, as we
2802
+ // assume that we will resolve/reject (truly) synchronously
2803
+ // and thus we avoid flagging checks about whether we've
2804
+ // already resolved/rejected.
2805
+ if (/** @type {Callbacks} */_this.c[state].length) {
2806
+ _this.c = null;
2807
+ }
2808
+ };
2752
2809
 
2753
- /** @type {Reject} */
2754
- function reject(reason) {
2755
- if (!that.c) ; else if (isPromise(reason)) {
2756
- addReject(reason.then(reject), reject);
2757
- } else {
2758
- transist(reason, REJECTED);
2810
+ /** @type {Resolve} */
2811
+ var _resolve = function resolve(val) {
2812
+ if (!_this.c) ; else if (isPromise(val)) {
2813
+ addReject(val.then(_resolve), _reject);
2814
+ } else {
2815
+ transist(val, FULFILLED);
2816
+ }
2817
+ };
2818
+
2819
+ /** @type {Reject} */
2820
+ var _reject = function reject(reason) {
2821
+ if (!_this.c) ; else if (isPromise(reason)) {
2822
+ addReject(reason.then(_reject), _reject);
2823
+ } else {
2824
+ transist(reason, REJECTED);
2825
+ }
2826
+ };
2827
+ try {
2828
+ fn(_resolve, _reject);
2829
+ } catch (err) {
2830
+ _reject(err);
2759
2831
  }
2760
2832
  }
2761
- try {
2762
- fn(resolve, reject);
2763
- } catch (err) {
2764
- reject(err);
2765
- }
2766
- }
2767
2833
 
2768
- /* eslint-disable unicorn/no-thenable -- Promise API */
2769
- /**
2770
- * @param {((value: ArbitraryValue) => ArbitraryValue)|null|undefined} [cb]
2771
- * @param {(reason: any) => PromiseLike<never>} [errBack]
2772
- * @returns {SyncPromise}
2773
- */
2774
- SyncPromise.prototype.then = function (cb, errBack) {
2775
- /* eslint-enable unicorn/no-thenable -- Promise API */
2776
- var that = this;
2777
- return new SyncPromise(/** @type {ResolveReject} */
2778
- function (resolve, reject) {
2779
- var rej = typeof errBack === 'function' ? errBack : reject;
2834
+ /* eslint-disable unicorn/no-thenable -- Promise API */
2835
+ /**
2836
+ * @param {((value: ArbitraryValue) => ArbitraryValue)|null|undefined} [cb]
2837
+ * @param {(reason: ArbitraryValue) => PromiseLike<never>} [errBack]
2838
+ * @returns {SyncPromise}
2839
+ */
2840
+ return _createClass(SyncPromise, [{
2841
+ key: "then",
2842
+ value: function then(cb, errBack) {
2843
+ var _this2 = this;
2844
+ /* eslint-enable unicorn/no-thenable -- Promise API */
2845
+ return new SyncPromise(/** @type {ResolveReject} */
2846
+ function (resolve, reject) {
2847
+ var rej = typeof errBack === 'function' ? errBack : reject;
2780
2848
 
2781
- /** @type {Settle} */
2782
- function settle() {
2783
- try {
2784
- resolve(cb ? cb(that.v) : that.v);
2785
- } catch (e) {
2786
- rej(e);
2787
- }
2788
- }
2789
- if (that.s === FULFILLED) {
2790
- settle();
2791
- } else if (that.s === REJECTED) {
2792
- rej(that.v);
2793
- } else {
2794
- /** @type {Callbacks} */that.c[FULFILLED].push(settle);
2795
- /** @type {Callbacks} */
2796
- that.c[REJECTED].push(rej);
2849
+ /** @type {Settle} */
2850
+ var settle = function settle() {
2851
+ try {
2852
+ resolve(cb ? cb(_this2.v) : _this2.v);
2853
+ } catch (e) {
2854
+ rej(e);
2855
+ }
2856
+ };
2857
+ if (_this2.s === FULFILLED) {
2858
+ settle();
2859
+ } else if (_this2.s === REJECTED) {
2860
+ rej(_this2.v);
2861
+ } else {
2862
+ /** @type {Callbacks} */_this2.c[FULFILLED].push(settle);
2863
+ /** @type {Callbacks} */
2864
+ _this2.c[REJECTED].push(rej);
2865
+ }
2866
+ });
2797
2867
  }
2798
- });
2799
- };
2800
2868
 
2801
- /**
2802
- * @param {(reason: any) => PromiseLike<never>|null|undefined} cb
2803
- * @returns {SyncPromise}
2804
- */
2805
- SyncPromise.prototype.catch = function (cb) {
2806
- var that = this;
2807
- return new SyncPromise(/** @type {ResolveReject} */
2808
- function (resolve, reject) {
2809
2869
  /**
2810
- * @returns {void}
2870
+ * @param {(reason: ArbitraryValue) => PromiseLike<never>|null|undefined} cb
2871
+ * @returns {SyncPromise}
2811
2872
  */
2812
- function settle() {
2813
- try {
2814
- resolve(cb(that.v));
2815
- } catch (e) {
2816
- reject(e);
2817
- }
2818
- }
2819
- if (that.s === REJECTED) {
2820
- settle();
2821
- } else if (that.s === FULFILLED) {
2822
- resolve(that.v);
2823
- } else {
2824
- /** @type {Callbacks} */that.c[REJECTED].push(settle);
2825
- /** @type {Callbacks} */
2826
- that.c[FULFILLED].push(resolve);
2873
+ }, {
2874
+ key: "catch",
2875
+ value: function _catch(cb) {
2876
+ var _this3 = this;
2877
+ return new SyncPromise(/** @type {ResolveReject} */
2878
+ function (resolve, reject) {
2879
+ /**
2880
+ * @returns {void}
2881
+ */
2882
+ var settle = function settle() {
2883
+ try {
2884
+ resolve(cb(_this3.v));
2885
+ } catch (e) {
2886
+ reject(e);
2887
+ }
2888
+ };
2889
+ if (_this3.s === REJECTED) {
2890
+ settle();
2891
+ } else if (_this3.s === FULFILLED) {
2892
+ resolve(_this3.v);
2893
+ } else {
2894
+ /** @type {Callbacks} */_this3.c[REJECTED].push(settle);
2895
+ /** @type {Callbacks} */
2896
+ _this3.c[FULFILLED].push(resolve);
2897
+ }
2898
+ });
2827
2899
  }
2828
- });
2829
- };
2830
-
2831
- /**
2832
- * @param {unknown[]|[]} promises
2833
- * @returns {SyncPromise}
2834
- */
2835
- SyncPromise.all = function (promises) {
2836
- return new SyncPromise(/** @type {ResolveReject} */
2837
- function (resolve, reject) {
2838
- var l = promises.length;
2839
- /** @type {ArbitraryValue[]} */
2840
- var newPromises = [];
2841
- if (!l) {
2842
- resolve(newPromises);
2843
- return;
2900
+ }], [{
2901
+ key: "all",
2902
+ value:
2903
+ /**
2904
+ * @param {unknown[]|[]} promises
2905
+ * @returns {SyncPromise}
2906
+ */
2907
+ function all(promises) {
2908
+ return new SyncPromise(/** @type {ResolveReject} */
2909
+ function (resolve, reject) {
2910
+ var l = promises.length;
2911
+ /** @type {ArbitraryValue[]} */
2912
+ var newPromises = [];
2913
+ if (!l) {
2914
+ resolve(newPromises);
2915
+ return;
2916
+ }
2917
+ promises.forEach(function (p, i) {
2918
+ if (isPromise(/** @type {PromiseLike<ArbitraryValue>} */p)) {
2919
+ addReject(/** @type {PromiseLike<ArbitraryValue>} */p.then(/** @type {OnFulfilled} */
2920
+ function (res) {
2921
+ newPromises[i] = res;
2922
+ --l;
2923
+ if (!l) {
2924
+ resolve(newPromises);
2925
+ }
2926
+ }), reject);
2927
+ } else {
2928
+ newPromises[i] = p;
2929
+ --l;
2930
+ if (!l) {
2931
+ resolve(promises);
2932
+ }
2933
+ }
2934
+ });
2935
+ });
2844
2936
  }
2845
- promises.forEach(function (p, i) {
2846
- if (isPromise(/** @type {PromiseLike<any>} */p)) {
2847
- addReject(/** @type {PromiseLike<any>} */p.then(/** @type {OnFulfilled} */
2848
- function (res) {
2849
- newPromises[i] = res;
2850
- --l || resolve(newPromises);
2851
- }), reject);
2852
- } else {
2853
- newPromises[i] = p;
2854
- --l || resolve(promises);
2855
- }
2856
- });
2857
- });
2858
- };
2859
2937
 
2860
- /**
2861
- * @param {unknown[]|[]} promises
2862
- * @returns {SyncPromise}
2863
- */
2864
- SyncPromise.race = function (promises) {
2865
- var resolved = false;
2866
- return new SyncPromise(/** @type {ResolveReject} */
2867
- function (resolve, reject) {
2868
- promises.some(function (p, i) {
2869
- if (isPromise(/** @type {PromiseLike<any>} */p)) {
2870
- addReject(/** @type {PromiseLike<any>} */p.then(/** @type {OnFulfilled} */
2871
- function (res) {
2872
- if (resolved) {
2873
- return;
2938
+ /**
2939
+ * @param {unknown[]|[]} promises
2940
+ * @returns {SyncPromise}
2941
+ */
2942
+ }, {
2943
+ key: "race",
2944
+ value: function race(promises) {
2945
+ var resolved = false;
2946
+ return new SyncPromise(/** @type {ResolveReject} */
2947
+ function (resolve, reject) {
2948
+ // eslint-disable-next-line @stylistic/max-len -- Long
2949
+ // eslint-disable-next-line unicorn/no-unused-array-method-return -- Shortcuts
2950
+ promises.some(function (p) {
2951
+ if (isPromise(/** @type {PromiseLike<ArbitraryValue>} */p)) {
2952
+ addReject(/** @type {PromiseLike<ArbitraryValue>} */p.then(/** @type {OnFulfilled} */
2953
+ function (res) {
2954
+ if (resolved) {
2955
+ return;
2956
+ }
2957
+ resolve(res);
2958
+ resolved = true;
2959
+ }), reject);
2960
+ return false;
2874
2961
  }
2875
- resolve(res);
2962
+ resolve(p);
2876
2963
  resolved = true;
2877
- }), reject);
2878
- return false;
2879
- }
2880
- resolve(p);
2881
- resolved = true;
2882
- return true;
2883
- });
2884
- });
2885
- };
2964
+ return true;
2965
+ });
2966
+ });
2967
+ }
2886
2968
 
2887
- /**
2888
- * @param {ArbitraryValue} val
2889
- * @returns {SyncPromise}
2890
- */
2891
- SyncPromise.resolve = function (val) {
2892
- return new SyncPromise(/** @type {ResolveReject} */
2893
- function (resolve, reject) {
2894
- resolve(val);
2895
- });
2896
- };
2969
+ /**
2970
+ * @param {ArbitraryValue} val
2971
+ * @returns {SyncPromise}
2972
+ */
2973
+ }, {
2974
+ key: "resolve",
2975
+ value: function resolve(val) {
2976
+ return new SyncPromise(/** @type {ResolveReject} */
2977
+ function (resolve) {
2978
+ resolve(val);
2979
+ });
2980
+ }
2897
2981
 
2898
- /**
2899
- * @param {ArbitraryValue} val
2900
- * @returns {SyncPromise}
2901
- */
2902
- SyncPromise.reject = function (val) {
2903
- return new SyncPromise(/** @type {ResolveReject} */
2904
- function (resolve, reject) {
2905
- reject(val);
2906
- });
2907
- };
2982
+ /**
2983
+ * @param {ArbitraryValue} val
2984
+ * @returns {SyncPromise}
2985
+ */
2986
+ }, {
2987
+ key: "reject",
2988
+ value: function reject(val) {
2989
+ return new SyncPromise(/** @type {ResolveReject} */
2990
+ function (resolve, reject) {
2991
+ reject(val);
2992
+ });
2993
+ }
2994
+ }]);
2995
+ }();
2908
2996
 
2909
2997
  /**
2910
2998
  * Compares two keys.
@@ -2941,6 +3029,8 @@
2941
3029
  return result;
2942
3030
  }
2943
3031
 
3032
+ var _templateObject, _templateObject2;
3033
+
2944
3034
  /**
2945
3035
  * @typedef {NodeJS.TypedArray|DataView} ArrayBufferView
2946
3036
  */
@@ -3216,14 +3306,22 @@
3216
3306
  _iterator.f();
3217
3307
  }
3218
3308
  encoded.push(keyTypeToEncodedChar.invalid + '-'); // append an extra item, so empty arrays sort correctly
3219
- return keyTypeToEncodedChar.array + '-' + JSON.stringify(encoded);
3309
+ var encodedKey = JSON.stringify(encoded);
3310
+ if (CFG.escapeNULForSQLiteStatements === false) {
3311
+ encodedKey = encodedKey.replaceAll(String.raw(_templateObject || (_templateObject = _taggedTemplateLiteral(["\0"], ["\\u0000"]))), '\0');
3312
+ }
3313
+ return keyTypeToEncodedChar.array + '-' + encodedKey;
3220
3314
  },
3221
3315
  /**
3222
3316
  * @param {string} key
3223
3317
  * @returns {ValueTypeArray}
3224
3318
  */
3225
3319
  decode: function decode(key) {
3226
- var decoded = JSON.parse(key.slice(2));
3320
+ var decodedKey = key.slice(2);
3321
+ if (CFG.escapeNULForSQLiteStatements === false) {
3322
+ decodedKey = decodedKey.replaceAll('\0', String.raw(_templateObject2 || (_templateObject2 = _taggedTemplateLiteral(["\0"], ["\\u0000"]))));
3323
+ }
3324
+ var decoded = JSON.parse(decodedKey);
3227
3325
  decoded.pop(); // remove the extra item
3228
3326
  for (var i = 0; i < decoded.length; i++) {
3229
3327
  var item = decoded[i];
@@ -4687,6 +4785,7 @@
4687
4785
  * },
4688
4786
  * __requestsFinished: boolean,
4689
4787
  * __transFinishedCb: (err: boolean, cb: ((bool?: boolean) => void)) => void,
4788
+ * __callTransFinishedCb: (err: boolean, cb: ((bool?: boolean) => void)) => void,
4690
4789
  * __transactionEndCallback: () => void,
4691
4790
  * __transactionFinished: boolean,
4692
4791
  * __completed: boolean,
@@ -4796,6 +4895,35 @@
4796
4895
  IDBTransaction.prototype.__transFinishedCb = function (err, cb) {
4797
4896
  cb(Boolean(err));
4798
4897
  };
4898
+
4899
+ /**
4900
+ * In Node, the real (SQL-commit-capable) `__transFinishedCb` is only
4901
+ * installed once the underlying WebSQL driver's own SQL-queue-idle check
4902
+ * has fired at least once for this transaction (asynchronously, via the
4903
+ * `nonstandardTransCb` passed to `db.transaction`/`.readTransaction`).
4904
+ * Since our own request processing can now finish synchronously (e.g., a
4905
+ * trivial upgrade using a synchronous SQL driver), it is possible to reach
4906
+ * transaction completion here before that has happened, in which case
4907
+ * `__transFinishedCb` is still the non-committing default above. Calling
4908
+ * that default directly would silently skip the actual SQL commit and
4909
+ * leave the underlying WebSQL transaction "running" forever, hanging any
4910
+ * later transaction on that same database connection. So, if the real
4911
+ * callback isn't installed yet, defer and retry until it is.
4912
+ * @this {IDBTransactionFull}
4913
+ * @param {boolean} err
4914
+ * @param {(bool?: boolean) => void} cb
4915
+ * @returns {void}
4916
+ */
4917
+ IDBTransaction.prototype.__callTransFinishedCb = function (err, cb) {
4918
+ var me = this;
4919
+ if (me.__transFinishedCb === IDBTransaction.prototype.__transFinishedCb) {
4920
+ setTimeout(function () {
4921
+ me.__callTransFinishedCb(err, cb);
4922
+ }, 0);
4923
+ return;
4924
+ }
4925
+ me.__transFinishedCb(err, cb);
4926
+ };
4799
4927
  /**
4800
4928
  * @this {IDBTransactionFull}
4801
4929
  * @returns {void}
@@ -4855,7 +4983,7 @@
4855
4983
  me.__abortTransaction(createDOMException('AbortError', 'A request was aborted (in user handler after success).'));
4856
4984
  return;
4857
4985
  }
4858
- executeNextRequest();
4986
+ runContinuationSafely(executeNextRequest);
4859
4987
  }
4860
4988
 
4861
4989
  /**
@@ -4930,7 +5058,9 @@
4930
5058
  try {
4931
5059
  q = me.__requests[i];
4932
5060
  if (!q.req) {
4933
- q.op(tx, q.args, executeNextRequest, error);
5061
+ q.op(tx, q.args, function () {
5062
+ return runContinuationSafely(executeNextRequest);
5063
+ }, error);
4934
5064
  return;
4935
5065
  }
4936
5066
  if (q.req.__done) {
@@ -7626,7 +7756,7 @@
7626
7756
  reject(err);
7627
7757
  });
7628
7758
  }));
7629
- SyncPromise.all(indexCreations).then(finish, /** @type {(reason: any) => PromiseLike<never>} */
7759
+ SyncPromise.all(indexCreations).then(finish).catch(/** @type {(reason: any) => PromiseLike<never>} */
7630
7760
  error).catch(function (err) {
7631
7761
  console.log('Index rename error');
7632
7762
  throw err;
@@ -10010,7 +10140,7 @@
10010
10140
  /** @type {import('./IDBDatabase.js').IDBDatabaseFull} */
10011
10141
  req.__result.__versionTransaction = null;
10012
10142
  sysdbFinishedCb(systx, false, function () {
10013
- req.transaction.__transFinishedCb(false, function () {
10143
+ req.transaction.__callTransFinishedCb(false, function () {
10014
10144
  ev.complete();
10015
10145
  req.__transaction = null;
10016
10146
  });
@@ -10121,6 +10251,13 @@
10121
10251
  function openDB(oldVersion) {
10122
10252
  /** @type {DatabaseFull} */
10123
10253
  var db;
10254
+ if (version === undefined) {
10255
+ // Resolve before use as a cache key below, or a `open(name)` call
10256
+ // (no explicit version) would cache/look up under `undefined`
10257
+ // instead of the actual version, causing a second, separate
10258
+ // connection to be opened for the same database on reopen.
10259
+ version = oldVersion || 1;
10260
+ }
10124
10261
  if ((useMemoryDatabase || useDatabaseCache) && Object.hasOwn(websqlDBCache, name) && Object.hasOwn(websqlDBCache[name], version)) {
10125
10262
  db = websqlDBCache[name][version];
10126
10263
  } else {
@@ -10132,9 +10269,6 @@
10132
10269
  websqlDBCache[name][version] = db;
10133
10270
  }
10134
10271
  }
10135
- if (version === undefined) {
10136
- version = oldVersion || 1;
10137
- }
10138
10272
  if (oldVersion > version) {
10139
10273
  var err = createDOMException('VersionError', 'An attempt was made to open a database using a lower version than the existing version.', version);
10140
10274
  if (useDatabaseCache) {
@@ -10302,7 +10436,6 @@
10302
10436
  version = _data$rows$item.version;
10303
10437
  var openConnections = me.__connections[name] || [];
10304
10438
  triggerAnyVersionChangeAndBlockedEvents(openConnections, req, version, null).then(function () {
10305
- // eslint-disable-line promise/catch-or-return -- Sync promise
10306
10439
  // Since we need two databases which can't be in a single transaction, we
10307
10440
  // do this deleting from `dbVersions` first since the `__sys__` deleting
10308
10441
  // only impacts file memory whereas this one is critical for avoiding it
@@ -10331,7 +10464,7 @@
10331
10464
  });
10332
10465
  return undefined;
10333
10466
  // @ts-expect-error It's ok
10334
- }, dbError);
10467
+ }).catch(dbError);
10335
10468
  return undefined;
10336
10469
  }, dbError);
10337
10470
  });
@@ -10513,6 +10646,9 @@
10513
10646
  * __unique: boolean,
10514
10647
  * __sqlDirection: "DESC"|"ASC",
10515
10648
  * __matchedKeys: {[key: string]: true},
10649
+ * __continuationKey: import('./Key.js').Key|undefined,
10650
+ * __continuationPrimaryKey: import('./Key.js').Key|undefined,
10651
+ * __multiEntryExhausted: boolean,
10516
10652
  * __invalidateCache: () => void
10517
10653
  * }} IDBCursorFull
10518
10654
  */
@@ -10586,6 +10722,9 @@
10586
10722
  this.__valueDecoder = this.__keyOnly ? Key : Sca;
10587
10723
  this.__count = count;
10588
10724
  this.__prefetchedIndex = -1;
10725
+ this.__continuationKey = undefined;
10726
+ this.__continuationPrimaryKey = undefined;
10727
+ this.__multiEntryExhausted = false;
10589
10728
  this.__multiEntryIndex = this.__indexSource ? 'multiEntry' in source && source.multiEntry : false;
10590
10729
  this.__unique = this.direction.includes('unique');
10591
10730
  this.__sqlDirection = ['prev', 'prevunique'].includes(this.direction) ? 'DESC' : 'ASC';
@@ -10749,73 +10888,97 @@
10749
10888
  * @param {SQLTransaction} tx
10750
10889
  * @param {KeySuccess} success
10751
10890
  * @param {FindError} error
10752
- * @param {Integer|undefined} recordsToLoad
10891
+ * @param {Integer} [recordsToLoad]
10753
10892
  * @this {IDBCursorFull}
10754
10893
  * @returns {void}
10755
10894
  */
10756
- IDBCursor.prototype.__findMultiEntry = function (key, primaryKey, tx, success, error, recordsToLoad) {
10895
+ IDBCursor.prototype.__findMultiEntry = function (key, primaryKey, tx, success, error) {
10896
+ var recordsToLoad = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : 1;
10757
10897
  var me = this;
10758
- if (me.__prefetchedData && me.__prefetchedData.length === me.__prefetchedIndex) {
10898
+ if (me.__multiEntryExhausted) {
10759
10899
  if (CFG.DEBUG) {
10760
- console.log('Reached end of multiEntry cursor');
10900
+ console.log('[multiEntry] Reached end of multiEntry cursor (already exhausted)');
10761
10901
  }
10762
10902
  success(undefined, undefined, undefined);
10763
10903
  return;
10764
10904
  }
10765
10905
  var quotedKeyColumnName = sqlQuote(me.__keyColumnName);
10766
- var sql = ['SELECT * FROM', escapeStoreNameForSQL(me.__store.__currentName)];
10767
- /** @type {string[]} */
10768
- var sqlValues = [];
10769
- sql.push('WHERE', quotedKeyColumnName, 'NOT NULL');
10770
- if (me.__range && me.__range.lower !== undefined && Array.isArray(me.__range.upper)) {
10771
- if (me.__range.upper.indexOf(me.__range.lower) === 0) {
10772
- sql.push('AND', quotedKeyColumnName, "LIKE ? ESCAPE '^'");
10773
- sqlValues.push('%' + sqlLIKEEscape(/** @type {string} */me.__range.__lowerCached.slice(0, -1)) + '%');
10774
- }
10775
- }
10906
+ var quotedKey = sqlQuote('key');
10776
10907
 
10777
10908
  // Determine the ORDER BY direction based on the cursor.
10778
10909
  var direction = me.__sqlDirection;
10779
10910
  var op = direction === 'ASC' ? '>' : '<';
10780
- var quotedKey = sqlQuote('key');
10781
- if (primaryKey !== undefined) {
10782
- sql.push('AND', quotedKey, op + '= ?');
10783
- // Key.convertValueToKey(primaryKey); // Already checked by `continuePrimaryKey`
10784
- sqlValues.push(/** @type {string} */_encode(primaryKey));
10785
- }
10786
- if (key !== undefined) {
10787
- sql.push('AND', quotedKeyColumnName, op + '= ?');
10788
- // Key.convertValueToKey(key); // Already checked by `continue` or `continuePrimaryKey`
10789
- sqlValues.push(/** @type {string} */_encode(key));
10790
- } else if (me.__key !== undefined) {
10791
- sql.push('AND', quotedKeyColumnName, op + ' ?');
10792
- // Key.convertValueToKey(me.__key); // Already checked when entered
10793
- sqlValues.push(/** @type {string} */_encode(me.__key));
10794
- }
10795
- if (!me.__count) {
10796
- // 1. Sort by key
10797
- sql.push('ORDER BY', quotedKeyColumnName, direction);
10798
10911
 
10799
- // 2. Sort by primaryKey (if defined and not unique)
10800
- if (!me.__unique && me.__keyColumnName !== 'key') {
10801
- // Avoid adding 'key' twice
10802
- sql.push(',', sqlQuote('key'), direction);
10912
+ /**
10913
+ * Runs (and, if a batch of underlying rows produces no matching
10914
+ * multi-entry values, repeatedly re-runs) the query for the next batch
10915
+ * of underlying rows, advancing `me.__continuationKey`/
10916
+ * `me.__continuationPrimaryKey` (tracking the last *physical* row
10917
+ * scanned) each time, until either some matches are found or the
10918
+ * underlying table is exhausted.
10919
+ * @returns {void}
10920
+ */
10921
+ function runQuery() {
10922
+ var sql = ['SELECT * FROM', escapeStoreNameForSQL(me.__store.__currentName)];
10923
+ /** @type {string[]} */
10924
+ var sqlValues = [];
10925
+ sql.push('WHERE', quotedKeyColumnName, 'NOT NULL');
10926
+ if (me.__range && me.__range.lower !== undefined && Array.isArray(me.__range.upper)) {
10927
+ if (me.__range.upper.indexOf(me.__range.lower) === 0) {
10928
+ sql.push('AND', quotedKeyColumnName, "LIKE ? ESCAPE '^'");
10929
+ sqlValues.push('%' + sqlLIKEEscape(/** @type {string} */me.__range.__lowerCached.slice(0, -1)) + '%');
10930
+ }
10931
+ }
10932
+ if (primaryKey !== undefined) {
10933
+ sql.push('AND', quotedKey, op + '= ?');
10934
+ // Key.convertValueToKey(primaryKey); // Already checked by `continuePrimaryKey`
10935
+ sqlValues.push(/** @type {string} */_encode(primaryKey));
10803
10936
  }
10804
-
10805
- // 3. Sort by position (if defined)
10806
-
10807
- if (!me.__unique && me.__indexSource) {
10808
- // 4. Sort by object store position (if defined and not unique)
10809
- sql.push(',', sqlQuote(me.__valueColumnName), direction);
10937
+ if (key !== undefined) {
10938
+ sql.push('AND', quotedKeyColumnName, op + '= ?');
10939
+ // Key.convertValueToKey(key); // Already checked by `continue` or `continuePrimaryKey`
10940
+ sqlValues.push(/** @type {string} */_encode(key));
10941
+ } else if (me.__continuationKey !== undefined) {
10942
+ // Resume from the last underlying (physical) row we scanned, not
10943
+ // from the last *matching* multi-entry value (which lives in a
10944
+ // different key-space than the raw index column and cannot be
10945
+ // reliably compared against it, e.g. for array index keys).
10946
+ sql.push('AND (', quotedKeyColumnName, op, '?', 'OR (', quotedKeyColumnName, '= ?', 'AND', quotedKey, op, '?))');
10947
+ var encodedContinuationKey = /** @type {string} */
10948
+ _encode(me.__continuationKey, true);
10949
+ sqlValues.push(encodedContinuationKey, encodedContinuationKey, /** @type {string} */_encode(me.__continuationPrimaryKey));
10950
+ }
10951
+ if (!me.__count) {
10952
+ // 1. Sort by key
10953
+ sql.push('ORDER BY', quotedKeyColumnName, direction);
10954
+
10955
+ // 2. Sort by primaryKey (if defined and not unique)
10956
+ if (!me.__unique && me.__keyColumnName !== 'key') {
10957
+ // Avoid adding 'key' twice
10958
+ sql.push(',', sqlQuote('key'), direction);
10959
+ }
10960
+
10961
+ // 3. Sort by position (if defined)
10962
+
10963
+ if (!me.__unique && me.__indexSource) {
10964
+ // 4. Sort by object store position (if defined and not unique)
10965
+ sql.push(',', sqlQuote(me.__valueColumnName), direction);
10966
+ }
10967
+ sql.push('LIMIT', String(recordsToLoad));
10968
+ }
10969
+ var sqlStr = sql.join(' ');
10970
+ if (CFG.DEBUG) {
10971
+ console.log('[multiEntry] query', sqlStr, sqlValues);
10810
10972
  }
10811
- sql.push('LIMIT', String(recordsToLoad));
10812
- }
10813
- var sqlStr = sql.join(' ');
10814
- if (CFG.DEBUG) {
10815
- console.log(sqlStr, sqlValues);
10816
- }
10817
- tx.executeSql(sqlStr, sqlValues, function (tx, data) {
10818
- if (data.rows.length > 0) {
10973
+ tx.executeSql(sqlStr, sqlValues, function (tx, data) {
10974
+ if (data.rows.length === 0) {
10975
+ me.__multiEntryExhausted = true;
10976
+ if (CFG.DEBUG) {
10977
+ console.log('[multiEntry] Reached end of multiEntry cursor (no more rows)');
10978
+ }
10979
+ success(undefined, undefined, undefined);
10980
+ return;
10981
+ }
10819
10982
  if (me.__count) {
10820
10983
  // Avoid caching and other processing below
10821
10984
  var ct = 0;
@@ -10828,6 +10991,16 @@
10828
10991
  success(undefined, ct, undefined);
10829
10992
  return;
10830
10993
  }
10994
+
10995
+ // Track how far we've physically scanned, regardless of whether
10996
+ // this batch produced any matches, so the next batch (if any)
10997
+ // resumes after this one instead of re-scanning or stopping early.
10998
+ var lastRawRow = data.rows.item(data.rows.length - 1);
10999
+ me.__continuationKey = _decode(lastRawRow[me.__keyColumnName], true);
11000
+ me.__continuationPrimaryKey = _decode(lastRawRow.key);
11001
+ if (data.rows.length < recordsToLoad) {
11002
+ me.__multiEntryExhausted = true;
11003
+ }
10831
11004
  var rows = [];
10832
11005
  for (var _i = 0; _i < data.rows.length; _i++) {
10833
11006
  var _rowItem = data.rows.item(_i);
@@ -10854,6 +11027,20 @@
10854
11027
  _iterator.f();
10855
11028
  }
10856
11029
  }
11030
+ if (rows.length === 0) {
11031
+ if (me.__multiEntryExhausted) {
11032
+ if (CFG.DEBUG) {
11033
+ console.log('[multiEntry] Reached end of multiEntry cursor (last batch had no matches)');
11034
+ }
11035
+ success(undefined, undefined, undefined);
11036
+ return;
11037
+ }
11038
+ if (CFG.DEBUG) {
11039
+ console.log('[multiEntry] batch had no matches; fetching next batch');
11040
+ }
11041
+ runQuery();
11042
+ return;
11043
+ }
10857
11044
  var reverse = me.direction.indexOf('prev') === 0;
10858
11045
  rows.sort(function (a, b) {
10859
11046
  if (a.matchingKey.replaceAll(leftBracketRegex, 'z') < b.matchingKey.replaceAll(leftBracketRegex, 'z')) {
@@ -10870,47 +11057,31 @@
10870
11057
  }
10871
11058
  return 0;
10872
11059
  });
10873
- if (rows.length > 1) {
10874
- me.__prefetchedIndex = 0;
10875
- me.__prefetchedData = {
10876
- data: rows,
10877
- length: rows.length,
10878
- /**
10879
- * @param {Integer} index
10880
- * @returns {RowItemNonNull}
10881
- */
10882
- item: function item(index) {
10883
- return this.data[index];
10884
- }
10885
- };
10886
- if (CFG.DEBUG) {
10887
- console.log('Preloaded ' + me.__prefetchedData.length + ' records for multiEntry cursor');
10888
- }
10889
- me.__decode(rows[0], success);
10890
- } else if (rows.length === 1) {
10891
- if (CFG.DEBUG) {
10892
- console.log('Reached end of multiEntry cursor');
10893
- }
10894
- me.__decode(rows[0], success);
10895
- } else {
10896
- if (CFG.DEBUG) {
10897
- console.log('Reached end of multiEntry cursor');
11060
+ me.__prefetchedIndex = 0;
11061
+ me.__prefetchedData = {
11062
+ data: rows,
11063
+ length: rows.length,
11064
+ /**
11065
+ * @param {Integer} index
11066
+ * @returns {RowItemNonNull}
11067
+ */
11068
+ item: function item(index) {
11069
+ return this.data[index];
10898
11070
  }
10899
- success(undefined, undefined, undefined);
11071
+ };
11072
+ if (CFG.DEBUG) {
11073
+ console.log('[multiEntry] Preloaded ' + me.__prefetchedData.length + ' records for multiEntry cursor');
10900
11074
  }
10901
- } else {
11075
+ me.__decode(rows[0], success);
11076
+ }, function (tx, err) {
10902
11077
  if (CFG.DEBUG) {
10903
- console.log('Reached end of multiEntry cursor');
11078
+ console.log('[multiEntry] Could not execute Cursor.continue', sqlStr, sqlValues);
10904
11079
  }
10905
- success(undefined, undefined, undefined);
10906
- }
10907
- }, function (tx, err) {
10908
- if (CFG.DEBUG) {
10909
- console.log('Could not execute Cursor.continue', sqlStr, sqlValues);
10910
- }
10911
- error(err);
10912
- return false;
10913
- });
11080
+ error(err);
11081
+ return false;
11082
+ });
11083
+ }
11084
+ runQuery();
10914
11085
  };
10915
11086
 
10916
11087
  /**
@@ -11019,6 +11190,7 @@
11019
11190
  IDBCursor.prototype.__invalidateCache = function () {
11020
11191
  // @ts-expect-error Why is this not being found?
11021
11192
  this.__prefetchedData = null;
11193
+ this.__multiEntryExhausted = false;
11022
11194
  };
11023
11195
 
11024
11196
  /**
@@ -11074,8 +11246,8 @@
11074
11246
  me.__advanceCount--;
11075
11247
  me.__key = k;
11076
11248
  me.__continue(undefined, true);
11077
- /** @type {() => void} */
11078
- executeNextRequest(); // We don't call success yet but do need to advance the transaction queue
11249
+ // We don't call success yet but do need to advance the transaction queue
11250
+ runContinuationSafely(/** @type {() => void} */executeNextRequest);
11079
11251
  return;
11080
11252
  }
11081
11253
  me.__advanceCount = undefined;
@@ -11097,11 +11269,15 @@
11097
11269
  return;
11098
11270
  }
11099
11271
  // @ts-expect-error Todo: Our bug to fix
11100
- cursorContinue(tx, args, success, error);
11272
+ runContinuationSafely(function () {
11273
+ return cursorContinue(tx, args, success, error);
11274
+ });
11101
11275
  }
11102
11276
  if (me.__unique && !me.__multiEntryIndex && encKey === _encode(me.key, me.__multiEntryIndex)) {
11103
11277
  // @ts-expect-error Todo: Our bug to fix
11104
- cursorContinue(tx, args, success, error);
11278
+ runContinuationSafely(function () {
11279
+ return cursorContinue(tx, args, success, error);
11280
+ });
11105
11281
  return;
11106
11282
  }
11107
11283
  checkKey();