indexeddbshim 17.2.2 → 17.3.1

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 (92) hide show
  1. package/README.md +8 -5
  2. package/dist/CFG.d.ts +1 -1
  3. package/dist/CFG.d.ts.map +1 -1
  4. package/dist/DOMException.d.ts +21 -7
  5. package/dist/DOMException.d.ts.map +1 -1
  6. package/dist/DOMStringList.d.ts.map +1 -1
  7. package/dist/Event.d.ts +12 -6
  8. package/dist/Event.d.ts.map +1 -1
  9. package/dist/IDBCursor.d.ts +125 -7
  10. package/dist/IDBCursor.d.ts.map +1 -1
  11. package/dist/IDBDatabase.d.ts +17 -10
  12. package/dist/IDBDatabase.d.ts.map +1 -1
  13. package/dist/IDBFactory.d.ts +5 -0
  14. package/dist/IDBFactory.d.ts.map +1 -1
  15. package/dist/IDBIndex.d.ts +43 -13
  16. package/dist/IDBIndex.d.ts.map +1 -1
  17. package/dist/IDBKeyRange.d.ts +6 -2
  18. package/dist/IDBKeyRange.d.ts.map +1 -1
  19. package/dist/IDBObjectStore.d.ts +66 -17
  20. package/dist/IDBObjectStore.d.ts.map +1 -1
  21. package/dist/IDBRecord.d.ts +44 -0
  22. package/dist/IDBRecord.d.ts.map +1 -0
  23. package/dist/IDBRequest.d.ts +2 -2
  24. package/dist/IDBRequest.d.ts.map +1 -1
  25. package/dist/IDBTransaction.d.ts +43 -15
  26. package/dist/IDBTransaction.d.ts.map +1 -1
  27. package/dist/IDBVersionChangeEvent.d.ts +23 -12
  28. package/dist/IDBVersionChangeEvent.d.ts.map +1 -1
  29. package/dist/Key.d.ts +7 -6
  30. package/dist/Key.d.ts.map +1 -1
  31. package/dist/Sca.d.ts +15 -0
  32. package/dist/Sca.d.ts.map +1 -1
  33. package/dist/indexeddbshim-Key.js +59 -23
  34. package/dist/indexeddbshim-Key.js.map +1 -1
  35. package/dist/indexeddbshim-Key.min.js +2 -2
  36. package/dist/indexeddbshim-Key.min.js.map +1 -1
  37. package/dist/indexeddbshim-UnicodeIdentifiers-node.cjs +3090 -1168
  38. package/dist/indexeddbshim-UnicodeIdentifiers-node.cjs.map +1 -1
  39. package/dist/indexeddbshim-UnicodeIdentifiers.js +2660 -988
  40. package/dist/indexeddbshim-UnicodeIdentifiers.js.map +1 -1
  41. package/dist/indexeddbshim-UnicodeIdentifiers.min.js +4 -4
  42. package/dist/indexeddbshim-UnicodeIdentifiers.min.js.map +1 -1
  43. package/dist/indexeddbshim-node.cjs +3090 -1168
  44. package/dist/indexeddbshim-node.cjs.map +1 -1
  45. package/dist/indexeddbshim-noninvasive.js +2660 -988
  46. package/dist/indexeddbshim-noninvasive.js.map +1 -1
  47. package/dist/indexeddbshim-noninvasive.min.js +4 -4
  48. package/dist/indexeddbshim-noninvasive.min.js.map +1 -1
  49. package/dist/indexeddbshim.js +2660 -988
  50. package/dist/indexeddbshim.js.map +1 -1
  51. package/dist/indexeddbshim.min.js +4 -4
  52. package/dist/indexeddbshim.min.js.map +1 -1
  53. package/dist/node-UnicodeIdentifiers.d.ts +2 -2
  54. package/dist/node-UnicodeIdentifiers.d.ts.map +1 -1
  55. package/dist/node.d.ts +2 -2
  56. package/dist/node.d.ts.map +1 -1
  57. package/dist/nodeSQLiteDatabase.d.ts +13 -14
  58. package/dist/nodeSQLiteDatabase.d.ts.map +1 -1
  59. package/dist/nodeWebSQL.d.ts +17 -1
  60. package/dist/nodeWebSQL.d.ts.map +1 -1
  61. package/dist/setGlobalVars.d.ts +2 -2
  62. package/dist/setGlobalVars.d.ts.map +1 -1
  63. package/dist/util.d.ts +15 -0
  64. package/dist/util.d.ts.map +1 -1
  65. package/examples/IDBKeyUtils.html +0 -1
  66. package/package.json +27 -21
  67. package/src/CFG.js +2 -1
  68. package/src/DOMException.js +38 -12
  69. package/src/DOMStringList.js +21 -2
  70. package/src/Event.js +9 -4
  71. package/src/IDBCursor.js +346 -18
  72. package/src/IDBDatabase.js +33 -16
  73. package/src/IDBFactory.js +118 -56
  74. package/src/IDBIndex.js +78 -45
  75. package/src/IDBKeyRange.js +8 -2
  76. package/src/IDBObjectStore.js +89 -65
  77. package/src/IDBRecord.js +96 -0
  78. package/src/IDBRequest.js +7 -7
  79. package/src/IDBTransaction.js +335 -67
  80. package/src/IDBVersionChangeEvent.js +37 -30
  81. package/src/Key.js +36 -18
  82. package/src/Sca.js +22 -1
  83. package/src/browser-UnicodeIdentifiers.js +3 -1
  84. package/src/browser-noninvasive.js +3 -1
  85. package/src/browser.js +3 -1
  86. package/src/node-UnicodeIdentifiers.js +1 -1
  87. package/src/node.js +1 -1
  88. package/src/nodeSQLiteDatabase.js +67 -2
  89. package/src/nodeWebSQL.js +22 -2
  90. package/src/setGlobalVars.js +23 -7
  91. package/src/util.js +29 -1
  92. package/typings/shimIndexedDB.d.ts +1 -1
package/src/IDBCursor.js CHANGED
@@ -9,11 +9,18 @@ import * as Key from './Key.js';
9
9
  import * as Sca from './Sca.js';
10
10
  import {IDBIndex} from './IDBIndex.js';
11
11
  import CFG from './CFG.js';
12
+ import IDBRecord from './IDBRecord.js';
13
+
14
+ const cursorDirections = ['next', 'prev', 'nextunique', 'prevunique'];
12
15
 
13
16
  /**
14
17
  * @typedef {number} Integer
15
18
  */
16
19
 
20
+ /**
21
+ * @typedef {import('websql-configurable/lib/websql/WebSQLTransaction.js').default} WebSQLTransaction
22
+ */
23
+
17
24
  /**
18
25
  * @typedef {IDBCursor & {
19
26
  * primaryKey: import('./Key.js').Key,
@@ -49,13 +56,49 @@ import CFG from './CFG.js';
49
56
  * __continuationKey: import('./Key.js').Key|undefined,
50
57
  * __continuationPrimaryKey: import('./Key.js').Key|undefined,
51
58
  * __multiEntryExhausted: boolean,
52
- * __invalidateCache: () => void
59
+ * __invalidateCache: () => void,
60
+ * __gotValue: boolean,
61
+ * __find: (...args: any[]) => void,
62
+ * __findBasic: (
63
+ * key: import('./Key.js').Key|undefined,
64
+ * primaryKey: import('./Key.js').Key|undefined,
65
+ * tx: WebSQLTransaction,
66
+ * success: KeySuccess,
67
+ * error: FindError,
68
+ * recordsToLoad: Integer|undefined
69
+ * ) => void,
70
+ * __findMultiEntry: (
71
+ * key: import('./Key.js').Key|undefined,
72
+ * primaryKey: import('./Key.js').Key|undefined,
73
+ * tx: WebSQLTransaction,
74
+ * success: KeySuccess,
75
+ * error: FindError,
76
+ * recordsToLoad?: Integer
77
+ * ) => void,
78
+ * __onsuccess: (success: SuccessArg) => SuccessCallback,
79
+ * __decode: (
80
+ * rowItem: RowItemNonNull,
81
+ * callback: (
82
+ * key: import('./Key.js').Key,
83
+ * val: import('./Key.js').Value,
84
+ * primaryKey: import('./Key.js').Key,
85
+ * encKey?: string
86
+ * ) => void
87
+ * ) => void,
88
+ * __sourceOrEffectiveObjStoreDeleted: () => void,
89
+ * __continue: (key?: import('./Key.js').Key, advanceContinue?: boolean) => void,
90
+ * __continueFinish: (
91
+ * key: import('./Key.js').Key,
92
+ * primaryKey: import('./Key.js').Key,
93
+ * advanceState: boolean
94
+ * ) => void
53
95
  * }} IDBCursorFull
54
96
  */
55
97
 
56
98
  /**
57
99
  * @typedef {IDBCursorFull & {
58
100
  * __request: import('./IDBRequest.js').IDBRequestFull,
101
+ * value: import('./Key.js').Value,
59
102
  * }} IDBCursorWithValueFull
60
103
  */
61
104
 
@@ -97,7 +140,7 @@ IDBCursor.__super = function IDBCursor (query, direction, store, source, keyColu
97
140
  }
98
141
  IDBTransaction.__assertActive(store.transaction);
99
142
  const range = convertValueToKeyRange(query);
100
- if (direction !== undefined && !(['next', 'prev', 'nextunique', 'prevunique'].includes(direction))) {
143
+ if (direction !== undefined && !cursorDirections.includes(direction)) {
101
144
  throw new TypeError(direction + 'is not a valid cursor direction');
102
145
  }
103
146
 
@@ -175,14 +218,14 @@ IDBCursor.prototype.__find = function (...args /* key, tx, success, error, recor
175
218
  */
176
219
 
177
220
  /**
178
- * @typedef {(tx: SQLTransaction|Error|DOMException|SQLError, err?: SQLError) => void} FindError
221
+ * @typedef {(tx: WebSQLTransaction|Error|DOMException|(Error & {code?: number}), err?: (Error & {code?: number})) => void} FindError
179
222
  */
180
223
 
181
224
  /**
182
225
  *
183
226
  * @param {undefined|import('./Key.js').Key} key
184
227
  * @param {undefined|import('./Key.js').Key} primaryKey
185
- * @param {SQLTransaction} tx
228
+ * @param {WebSQLTransaction} tx
186
229
  * @param {KeySuccess} success
187
230
  * @param {FindError} error
188
231
  * @param {Integer|undefined} recordsToLoad
@@ -253,9 +296,9 @@ IDBCursor.prototype.__findBasic = function (key, primaryKey, tx, success, error,
253
296
  me.__prefetchedIndex = 0;
254
297
  me.__prefetchedData = data.rows;
255
298
  if (CFG.DEBUG) { console.log('Preloaded ' + me.__prefetchedData.length + ' records for cursor'); }
256
- me.__decode(data.rows.item(0), success);
299
+ me.__decode(/** @type {RowItemNonNull} */ (data.rows.item(0)), success);
257
300
  } else if (data.rows.length === 1) {
258
- me.__decode(data.rows.item(0), success);
301
+ me.__decode(/** @type {RowItemNonNull} */ (data.rows.item(0)), success);
259
302
  } else {
260
303
  if (CFG.DEBUG) { console.log('Reached end of cursors'); }
261
304
  success(undefined, undefined, undefined);
@@ -273,7 +316,7 @@ const leftBracketRegex = /\[/gu;
273
316
  *
274
317
  * @param {undefined|import('./Key.js').Key} key
275
318
  * @param {undefined|import('./Key.js').Key} primaryKey
276
- * @param {SQLTransaction} tx
319
+ * @param {WebSQLTransaction} tx
277
320
  * @param {KeySuccess} success
278
321
  * @param {FindError} error
279
322
  * @param {Integer} [recordsToLoad]
@@ -380,7 +423,7 @@ IDBCursor.prototype.__findMultiEntry = function (key, primaryKey, tx, success, e
380
423
  if (me.__count) { // Avoid caching and other processing below
381
424
  let ct = 0;
382
425
  for (let i = 0; i < data.rows.length; i++) {
383
- const rowItem = data.rows.item(i);
426
+ const rowItem = /** @type {RowItemNonNull} */ (data.rows.item(i));
384
427
  const rowKey = Key.decode(rowItem[me.__keyColumnName], true);
385
428
  const matches = Key.findMultiEntryMatches(rowKey, me.__range);
386
429
  ct += matches.length;
@@ -392,7 +435,7 @@ IDBCursor.prototype.__findMultiEntry = function (key, primaryKey, tx, success, e
392
435
  // Track how far we've physically scanned, regardless of whether
393
436
  // this batch produced any matches, so the next batch (if any)
394
437
  // resumes after this one instead of re-scanning or stopping early.
395
- const lastRawRow = data.rows.item(data.rows.length - 1);
438
+ const lastRawRow = /** @type {RowItemNonNull} */ (data.rows.item(data.rows.length - 1));
396
439
  me.__continuationKey = Key.decode(lastRawRow[me.__keyColumnName], true);
397
440
  me.__continuationPrimaryKey = Key.decode(lastRawRow.key);
398
441
  if (data.rows.length < recordsToLoad) {
@@ -401,7 +444,7 @@ IDBCursor.prototype.__findMultiEntry = function (key, primaryKey, tx, success, e
401
444
 
402
445
  const rows = [];
403
446
  for (let i = 0; i < data.rows.length; i++) {
404
- const rowItem = data.rows.item(i);
447
+ const rowItem = /** @type {RowItemNonNull} */ (data.rows.item(i));
405
448
  const rowKey = Key.decode(rowItem[me.__keyColumnName], true);
406
449
  const matches = Key.findMultiEntryMatches(rowKey, me.__range);
407
450
 
@@ -455,6 +498,7 @@ IDBCursor.prototype.__findMultiEntry = function (key, primaryKey, tx, success, e
455
498
  length: rows.length,
456
499
  /**
457
500
  * @param {Integer} index
501
+ * @this {{data: RowItemNonNull[]}}
458
502
  * @returns {RowItemNonNull}
459
503
  */
460
504
  item (index) {
@@ -588,7 +632,7 @@ IDBCursor.prototype.__sourceOrEffectiveObjStoreDeleted = function () {
588
632
  * @returns {void}
589
633
  */
590
634
  IDBCursor.prototype.__invalidateCache = function () {
591
- // @ts-expect-error Why is this not being found?
635
+ // @ts-ignore Why is this not being found?; needed under some TS versions
592
636
  this.__prefetchedData = null;
593
637
  this.__multiEntryExhausted = false;
594
638
  };
@@ -649,7 +693,29 @@ IDBCursor.prototype.__continueFinish = function (key, primaryKey, advanceState)
649
693
  if (me.__advanceCount && me.__advanceCount >= 2 && k !== undefined) {
650
694
  me.__advanceCount--;
651
695
  me.__key = k;
652
- me.__continue(undefined, true);
696
+ me.__sourceOrEffectiveObjStoreDeleted();
697
+ // This is an internal continuation step within a single
698
+ // `advance()` call, not a fresh user-facing dispatch --
699
+ // it's already part of an in-flight, already-validated
700
+ // operation, so briefly reopen the active-handler
701
+ // window `__pushToQueue` (via `__continueFinish`)
702
+ // checks, rather than routing back through the public
703
+ // `__continue()` (which would apply that check as if
704
+ // this were new activity from user code).
705
+ const {transaction} = /** @type {{transaction: import('./IDBTransaction.js').IDBTransactionFull}} */ (
706
+ me.__store
707
+ );
708
+ // `runQueuedRequest` (in `IDBTransaction.js`) resets both
709
+ // `__active` and `__handlerActive` to `false` right
710
+ // before invoking this op, so both -- not just
711
+ // `__handlerActive` -- need reopening here for
712
+ // `__pushToQueue`'s `__assertActive()` check below to
713
+ // pass.
714
+ transaction.__active = true;
715
+ transaction.__handlerActive = true;
716
+ me.__continueFinish(undefined, undefined, true);
717
+ transaction.__active = false;
718
+ transaction.__handlerActive = false;
653
719
  // We don't call success yet but do need to advance the transaction queue
654
720
  util.runContinuationSafely(/** @type {() => void} */ (executeNextRequest));
655
721
  return;
@@ -667,10 +733,25 @@ IDBCursor.prototype.__continueFinish = function (key, primaryKey, advanceState)
667
733
  * @returns {void}
668
734
  */
669
735
  function checkKey () {
670
- const cmpResult = Number(key === undefined) || cmp(k, key);
671
- if (cmpResult > 0 || (
672
- cmpResult === 0 && (
673
- me.__unique || primaryKey === undefined || cmp(primKey, primaryKey) >= 0
736
+ if (key === undefined) {
737
+ // No target key to compare against -- always accept the next
738
+ // prefetched record, regardless of direction.
739
+ triggerSuccess(k, val, primKey);
740
+ return;
741
+ }
742
+ // `cmp(k, key) > 0` ("has this record passed the target key?")
743
+ // only holds for an ascending (`next`) cursor; a descending
744
+ // (`prev`) one passes the target once `k` has dropped *below*
745
+ // it, i.e. `cmp(k, key) < 0` -- matching `continuePrimaryKey`'s
746
+ // own direction-aware comparisons above.
747
+ const isPrev = me.direction.includes('prev');
748
+ const keyCmp = cmp(k, key);
749
+ const passedKey = isPrev ? keyCmp < 0 : keyCmp > 0;
750
+ const atKey = keyCmp === 0;
751
+ if (passedKey || (
752
+ atKey && (
753
+ me.__unique || primaryKey === undefined ||
754
+ (isPrev ? cmp(primKey, primaryKey) <= 0 : cmp(primKey, primaryKey) >= 0)
674
755
  )
675
756
  )) {
676
757
  triggerSuccess(k, val, primKey);
@@ -679,6 +760,16 @@ IDBCursor.prototype.__continueFinish = function (key, primaryKey, advanceState)
679
760
  // @ts-expect-error Todo: Our bug to fix
680
761
  util.runContinuationSafely(() => cursorContinue(tx, args, success, error));
681
762
  }
763
+ if (me.__multiEntryIndex && me.__unique && encKey === undefined) {
764
+ // `__decode` found this row's matching key already seen
765
+ // (tracked via `__matchedKeys`) and signaled a skip by
766
+ // omitting `encKey`; move on to the next prefetched
767
+ // row instead of treating the missing key as the end
768
+ // of the cursor.
769
+ // @ts-expect-error Todo: Our bug to fix
770
+ util.runContinuationSafely(() => cursorContinue(tx, args, success, error));
771
+ return;
772
+ }
682
773
  if (me.__unique && !me.__multiEntryIndex &&
683
774
  encKey === Key.encode(me.key, me.__multiEntryIndex)) {
684
775
  // @ts-expect-error Todo: Our bug to fix
@@ -822,6 +913,7 @@ IDBCursor.prototype.update = function (valueToUpdate) {
822
913
  */
823
914
  function addToQueue (clonedValue) {
824
915
  // We set the `invalidateCache` argument to `false` since the old value shouldn't be accessed
916
+ // @ts-ignore -- API (not erring in TS 6)
825
917
  IDBObjectStore.__storingRecordObjectStore(request, me.__store, false, clonedValue, false, key);
826
918
  }
827
919
  if (me.__store.keyPath !== null) {
@@ -832,7 +924,10 @@ IDBCursor.prototype.update = function (valueToUpdate) {
832
924
  // eslint-disable-next-line unicorn/prefer-hoisting-branch-code -- Different
833
925
  addToQueue(clonedValue);
834
926
  } else {
835
- const clonedValue = Sca.clone(valueToUpdate);
927
+ const clonedValue = Sca.cloneWithInactiveTransaction(
928
+ /** @type {import('./IDBTransaction.js').IDBTransactionFull} */ (me.__store.transaction),
929
+ valueToUpdate
930
+ );
836
931
  addToQueue(clonedValue);
837
932
  }
838
933
  return request;
@@ -892,6 +987,7 @@ util.defineReadonlyOuterInterface(
892
987
  IDBCursor.prototype,
893
988
  ['source', 'direction', 'key', 'primaryKey', 'request']
894
989
  );
990
+ util.setOperationNames(IDBCursor.prototype);
895
991
  Object.defineProperty(IDBCursor, 'prototype', {
896
992
  writable: false
897
993
  });
@@ -949,4 +1045,236 @@ Object.defineProperty(IDBCursorWithValue, 'prototype', {
949
1045
  });
950
1046
  /* eslint-enable unicorn/no-top-level-side-effects -- Would be good */
951
1047
 
952
- export {IDBCursor, IDBCursorWithValue};
1048
+ /**
1049
+ * Validates `direction` and fills in defaults for the `{query, count,
1050
+ * direction}` shape shared by `getAll`/`getAllKeys`/`getAllRecords`.
1051
+ * @param {AnyValue} options
1052
+ * @throws {TypeError}
1053
+ * @returns {{query: AnyValue, count: Integer|undefined, direction: string}}
1054
+ */
1055
+ function normalizeGetAllOptions (options) {
1056
+ const {query, count, direction = 'next'} = /** @type {AnyValue} */ (options ?? {});
1057
+ if (!cursorDirections.includes(direction)) {
1058
+ throw new TypeError('`' + direction + '` is not a valid direction');
1059
+ }
1060
+ return {query, count, direction};
1061
+ }
1062
+
1063
+ /**
1064
+ * `getAll`/`getAllKeys` accept either the legacy `(query, count)` signature
1065
+ * or, per the IndexedDB 3 draft's `getAllRecords` options shape, a single
1066
+ * `{query, count, direction}` options object -- including an *empty*
1067
+ * `{}` (WPT's own tests call `getAll({})` expecting every record back,
1068
+ * the same as `getAll()`, not a `DataError` from treating `{}` as an
1069
+ * invalid key). A plain object is never a valid IndexedDB key (or key
1070
+ * range), so the two forms can be told apart unambiguously by the shape
1071
+ * of the first argument alone -- WPT's own `get_all_with_options_and_count_test`
1072
+ * deliberately calls `getAll(options, count)` (an extra, non-overload-matching
1073
+ * second argument, which JS simply ignores) to confirm the options-shaped
1074
+ * first argument wins regardless of how many arguments were actually passed.
1075
+ * @param {IArguments} args
1076
+ * @throws {TypeError}
1077
+ * @returns {{query: AnyValue, count: Integer|undefined, direction: string}}
1078
+ */
1079
+ function parseGetAllArgs (args) {
1080
+ const arg0 = args[0];
1081
+ if (util.isObj(arg0) && !Array.isArray(arg0) && !util.isDate(arg0) &&
1082
+ !util.isBinary(arg0) &&
1083
+ !('upper' in arg0 && 'lowerOpen' in arg0 && typeof arg0.lowerOpen === 'boolean') // IDBKeyRange-like
1084
+ ) {
1085
+ return normalizeGetAllOptions(arg0);
1086
+ }
1087
+ const [query, count] = args;
1088
+ return normalizeGetAllOptions({query, count});
1089
+ }
1090
+
1091
+ /**
1092
+ * `getAllRecords` (IndexedDB 3.0) takes a single, optional `IDBGetAllOptions`
1093
+ * dictionary -- `{query, count, direction}` -- with no legacy positional
1094
+ * form to disambiguate against.
1095
+ * @param {IArguments} args
1096
+ * @throws {TypeError}
1097
+ * @returns {{query: AnyValue, count: Integer|undefined, direction: string}}
1098
+ */
1099
+ function parseGetAllRecordsArgs (args) {
1100
+ return normalizeGetAllOptions(args[0]);
1101
+ }
1102
+
1103
+ /**
1104
+ * Shared implementation backing `getAll`/`getAllKeys`/`getAllRecords` on both
1105
+ * `IDBObjectStore` and `IDBIndex`. This walks a `find`/`decode` state
1106
+ * object using the exact same low-level primitives
1107
+ * (`__find`/`__findBasic`/`__findMultiEntry`/`__decode`) a real cursor's
1108
+ * `continue()` uses internally, so ordering and uniqueness semantics --
1109
+ * including `nextunique`/`prevunique` over `multiEntry` indexes -- always
1110
+ * match what iterating that same cursor manually would produce.
1111
+ *
1112
+ * Unlike a real cursor, none of the intermediate steps go through the
1113
+ * transaction's shared request queue (`__pushToQueue`): each step re-enters
1114
+ * the same queue slot's op function via a plain synchronous/callback chain
1115
+ * and only calls the queue's real `success` once, when every record has
1116
+ * been collected. This makes the whole operation occupy exactly one slot,
1117
+ * at its true issuance position, so its result event can't be reordered
1118
+ * relative to sibling requests queued around the same time -- which
1119
+ * driving this through the public, queue-based `openCursor()`/`continue()`
1120
+ * API (as this used to) cannot guarantee, since each subsequent `continue()`
1121
+ * step is appended to the end of the (by-then-longer) queue rather than
1122
+ * staying next to the steps before it.
1123
+ * @param {import('./IDBObjectStore.js').IDBObjectStoreFull|
1124
+ * import('./IDBIndex.js').IDBIndexFull} source
1125
+ * @param {import('./Key.js').Value} query
1126
+ * @param {Integer|undefined} count
1127
+ * @param {string} direction
1128
+ * @param {"value"|"key"|"record"} mode
1129
+ * @throws {TypeError}
1130
+ * @returns {import('./IDBRequest.js').IDBRequestFull}
1131
+ */
1132
+ function collectAll (source, query, count, direction, mode) {
1133
+ const isIndexSource = util.instanceOf(source, IDBIndex);
1134
+ if (!isIndexSource && !util.instanceOf(source, IDBObjectStore)) {
1135
+ // Per Web IDL, an operation invoked on a `this` that isn't an
1136
+ // instance of the interface it's defined on must throw a
1137
+ // TypeError ("illegal invocation") -- checked here, rather than
1138
+ // left to fall through to `store.transaction`/`__assertActive`
1139
+ // below, since those instead throw `TransactionInactiveError`
1140
+ // for a `this` with no real `transaction` property.
1141
+ throw new TypeError('Illegal invocation');
1142
+ }
1143
+ if (count !== undefined) {
1144
+ count = util.enforceRange(count, 'unsigned long');
1145
+ }
1146
+ const indexSource = /** @type {import('./IDBIndex.js').IDBIndexFull} */ (source);
1147
+ const store = /** @type {import('./IDBObjectStore.js').IDBObjectStoreFull} */ (
1148
+ isIndexSource ? indexSource.objectStore : source
1149
+ );
1150
+ const tx = /** @type {import('./IDBTransaction.js').IDBTransactionFull} */ (store.transaction);
1151
+
1152
+ IDBObjectStore.__invalidStateIfDeleted(store);
1153
+ if (isIndexSource) {
1154
+ IDBIndex.__invalidStateIfDeleted(indexSource);
1155
+ }
1156
+ IDBTransaction.__assertActive(tx);
1157
+ const range = convertValueToKeyRange(query);
1158
+ const multiEntryIndex = isIndexSource && Boolean(indexSource.multiEntry);
1159
+ if (range !== undefined) {
1160
+ // Encode the key range and cache the encoded values, matching what a
1161
+ // real cursor's constructor does, since `__findMultiEntry` reads
1162
+ // these cached values back off the range.
1163
+ range.__lowerCached = range.lower !== undefined && Key.encode(range.lower, multiEntryIndex);
1164
+ range.__upperCached = range.upper !== undefined && Key.encode(range.upper, multiEntryIndex);
1165
+ }
1166
+
1167
+ // `getAllRecords` needs each record's value, so it (like `getAll`) must
1168
+ // decode the `value` column rather than the lighter-weight `key`-only
1169
+ // reads `getAllKeys` can get away with.
1170
+ const needsValue = mode !== 'key';
1171
+ const keyColumnName = isIndexSource ? util.escapeIndexNameForSQLKeyColumn(indexSource.name) : 'key';
1172
+ const valueColumnName = needsValue ? 'value' : 'key';
1173
+
1174
+ // Inherits from `IDBCursor.prototype` (rather than being a plain object)
1175
+ // because `__find` dispatches to `this.__findBasic`/`this.__findMultiEntry`
1176
+ // as methods on `this`, not as functions looked up separately.
1177
+ /** @type {IDBCursorFull} */
1178
+ const state = /** @type {IDBCursorFull} */ (/** @type {unknown} */ (Object.assign(Object.create(IDBCursor.prototype), {
1179
+ __store: store,
1180
+ __indexSource: isIndexSource,
1181
+ __range: range,
1182
+ __keyColumnName: keyColumnName,
1183
+ __valueColumnName: valueColumnName,
1184
+ __valueDecoder: valueColumnName === 'key' ? Key : Sca,
1185
+ __count: false,
1186
+ __prefetchedIndex: -1,
1187
+ __prefetchedData: null,
1188
+ __continuationKey: undefined,
1189
+ __continuationPrimaryKey: undefined,
1190
+ __multiEntryExhausted: false,
1191
+ __multiEntryIndex: multiEntryIndex,
1192
+ __unique: direction.includes('unique'),
1193
+ __sqlDirection: ['prev', 'prevunique'].includes(direction) ? 'DESC' : 'ASC',
1194
+ __key: undefined
1195
+ })));
1196
+ // `direction` (unlike the `__`-prefixed internals above) is exposed as a
1197
+ // readonly getter on `IDBCursor.prototype` (via `defineReadonlyOuterInterface`)
1198
+ // that throws unless shadowed by an own property, exactly as a real
1199
+ // cursor's constructor shadows it -- a plain assignment would instead
1200
+ // hit that inherited accessor, which has no setter.
1201
+ Object.defineProperty(state, 'direction', {writable: false, value: direction});
1202
+
1203
+ return tx.__addToTransactionQueue(function collectAllOp (sqlTx, args, success, error) {
1204
+ /** @type {AnyValue[]} */
1205
+ const results = [];
1206
+ const recordsToLoad = count || CFG.cursorPreloadPackSize || 100;
1207
+
1208
+ /**
1209
+ * @param {import('./Key.js').Key} k
1210
+ * @param {import('./Key.js').Value} val
1211
+ * @param {import('./Key.js').Key} primKey
1212
+ * @returns {void}
1213
+ */
1214
+ function acceptOrFinish (k, val, primKey) {
1215
+ if (k === undefined) {
1216
+ success(results);
1217
+ return;
1218
+ }
1219
+ state.__key = k;
1220
+ switch (mode) {
1221
+ case 'key':
1222
+ results.push(primKey);
1223
+ break;
1224
+ case 'record':
1225
+ results.push(IDBRecord.__createInstance(k, primKey, val));
1226
+ break;
1227
+ default:
1228
+ results.push(val);
1229
+ }
1230
+ if (count && results.length >= count) {
1231
+ success(results);
1232
+ return;
1233
+ }
1234
+ util.runContinuationSafely(step);
1235
+ }
1236
+
1237
+ /**
1238
+ * @returns {void}
1239
+ */
1240
+ function step () {
1241
+ if (state.__prefetchedData) {
1242
+ state.__prefetchedIndex++;
1243
+ if (state.__prefetchedIndex < state.__prefetchedData.length) {
1244
+ IDBCursor.prototype.__decode.call(
1245
+ state, state.__prefetchedData.item(state.__prefetchedIndex),
1246
+ /**
1247
+ * @param {import('./Key.js').Key} k
1248
+ * @param {import('./Key.js').Value} val
1249
+ * @param {import('./Key.js').Key} primKey
1250
+ * @param {string} [encKey]
1251
+ * @returns {void}
1252
+ */
1253
+ function (k, val, primKey, encKey) {
1254
+ if (state.__multiEntryIndex && state.__unique && encKey === undefined) {
1255
+ // `__decode` already saw this matching key (tracked
1256
+ // via `__matchedKeys`) and signaled a skip.
1257
+ util.runContinuationSafely(step);
1258
+ return;
1259
+ }
1260
+ if (state.__unique && !state.__multiEntryIndex &&
1261
+ encKey === Key.encode(state.__key, state.__multiEntryIndex)) {
1262
+ util.runContinuationSafely(step);
1263
+ return;
1264
+ }
1265
+ acceptOrFinish(k, val, primKey);
1266
+ }
1267
+ );
1268
+ return;
1269
+ }
1270
+ }
1271
+ IDBCursor.prototype.__find.call(
1272
+ state, undefined, undefined, sqlTx, acceptOrFinish, error, recordsToLoad
1273
+ );
1274
+ }
1275
+
1276
+ step();
1277
+ }, undefined, source);
1278
+ }
1279
+
1280
+ export {IDBCursor, IDBCursorWithValue, parseGetAllArgs, parseGetAllRecordsArgs, collectAll};
@@ -25,11 +25,12 @@ const readonlyProperties = ['name', 'version', 'objectStoreNames'];
25
25
  * IDB Database Object.
26
26
  * @see https://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#database-interface
27
27
  * @class
28
+ * @this {IDBDatabaseFull}
28
29
  * @throws {TypeError}
29
30
  */
30
31
  function IDBDatabase () {
31
32
  this.__versionTransaction = null;
32
- this.__objectStores = null;
33
+ this.__objectStores = {};
33
34
  /** @type {import('./IDBTransaction.js').IDBTransactionFull[]} */
34
35
  this.__transactions = [];
35
36
  throw new TypeError('Illegal constructor');
@@ -51,27 +52,28 @@ const IDBDatabaseAlias = IDBDatabase;
51
52
  * name: string,
52
53
  * __forceClose: (msg: string) => void,
53
54
  * __db: import('websql-configurable/lib/websql/WebSQLDatabase.js').default,
55
+ * __closePending: boolean,
54
56
  * __oldVersion: Integer,
55
57
  * __version: Integer,
56
58
  * __name: string,
57
59
  * __upgradeTransaction: null|import('./IDBTransaction.js').IDBTransactionFull,
58
- * __versionTransaction: import('./IDBTransaction.js').IDBTransactionFull,
60
+ * __versionTransaction: null|import('./IDBTransaction.js').IDBTransactionFull,
59
61
  * __transactions: import('./IDBTransaction.js').IDBTransactionFull[],
60
- * __objectStores: {[key: string]: IDBObjectStore},
62
+ * __objectStores: {[key: string]: import('./IDBObjectStore.js').IDBObjectStoreFull},
61
63
  * __objectStoreNames: import('./DOMStringList.js').DOMStringListFull,
62
64
  * __oldObjectStoreNames: import('./DOMStringList.js').DOMStringListFull,
63
65
  * __unblocking: {
64
66
  * check: () => void
65
- * }
67
+ * }[]
66
68
  * }} IDBDatabaseFull
67
69
  */
68
70
 
69
71
  /**
70
- * @param {import('websql-configurable').default} db
72
+ * @param {import('websql-configurable/lib/websql/WebSQLDatabase.js').default} db
71
73
  * @param {string} name
72
74
  * @param {Integer} oldVersion
73
75
  * @param {Integer} version
74
- * @param {SQLResultSet} storeProperties
76
+ * @param {import('websql-configurable/lib/websql/WebSQLResultSet.js').default} storeProperties
75
77
  * @returns {IDBDatabaseFull}
76
78
  */
77
79
  IDBDatabase.__createInstance = function (db, name, oldVersion, version, storeProperties) {
@@ -85,6 +87,8 @@ IDBDatabase.__createInstance = function (db, name, oldVersion, version, storePro
85
87
  util.defineReadonlyProperties(this, readonlyProperties);
86
88
  this.__db = db;
87
89
  this.__closePending = false;
90
+ /** @type {{check: () => void}[]} */
91
+ this.__unblocking = [];
88
92
  this.__oldVersion = oldVersion;
89
93
  this.__version = version;
90
94
  this.__name = name;
@@ -102,12 +106,9 @@ IDBDatabase.__createInstance = function (db, name, oldVersion, version, storePro
102
106
  this.__objectStores = {};
103
107
  this.__objectStoreNames = DOMStringList.__createInstance();
104
108
 
105
- /**
106
- * @type {IDBObjectStoreProperties}
107
- */
108
- const itemCopy = {};
109
+ const itemCopy = /** @type {IDBObjectStoreProperties} */ ({});
109
110
  for (let i = 0; i < storeProperties.rows.length; i++) {
110
- const item = storeProperties.rows.item(i);
111
+ const item = /** @type {{name: string, keyPath: string, autoInc: string, indexList: string}} */ (storeProperties.rows.item(i));
111
112
  // Safari implements `item` getter return object's properties
112
113
  // as readonly, so we copy all its properties (except our
113
114
  // custom `currNum` which we don't need) onto a new object
@@ -223,9 +224,14 @@ IDBDatabase.prototype.close = function () {
223
224
  throw new TypeError('Illegal invocation');
224
225
  }
225
226
  this.__closePending = true;
226
- if (this.__unblocking) {
227
- this.__unblocking.check();
228
- }
227
+ // Multiple, separately-issued `open`/`deleteDatabase` requests can each
228
+ // be waiting (concurrently) on this same connection to close -- e.g.
229
+ // when an earlier one is still "blocked" but has already let the
230
+ // connection queue move on to the next request -- so every registered
231
+ // waiter must be notified, not just the most recently registered one.
232
+ this.__unblocking.forEach(function (/** @type {{check: () => void}} */ u) { u.check(); });
233
+ /** @type {{check: () => void}[]} */
234
+ this.__unblocking = [];
229
235
  this.__transactions = [];
230
236
  };
231
237
 
@@ -235,12 +241,14 @@ IDBDatabase.prototype.close = function () {
235
241
  * @this {IDBDatabaseFull}
236
242
  * @returns {import('./IDBTransaction.js').IDBTransactionFull}
237
243
  */
238
- IDBDatabase.prototype.transaction = function (storeNames /* , mode */) {
244
+ IDBDatabase.prototype.transaction = function (storeNames /* , mode, options */) {
239
245
  if (arguments.length === 0) {
240
246
  throw new TypeError('You must supply a valid `storeNames` to `IDBDatabase.transaction`');
241
247
  }
242
248
  // eslint-disable-next-line prefer-rest-params -- API
243
249
  let mode = arguments[1];
250
+ // eslint-disable-next-line prefer-rest-params -- API
251
+ const options = arguments[2];
244
252
  storeNames = util.isIterable(storeNames)
245
253
  // Creating new array also ensures sequence is passed by value: https://heycam.github.io/webidl/#idl-sequence
246
254
  ? [...new Set( // to be unique
@@ -283,10 +291,18 @@ IDBDatabase.prototype.transaction = function (storeNames /* , mode */) {
283
291
  throw new TypeError('Invalid transaction mode: ' + mode);
284
292
  }
285
293
 
294
+ // Validated and reflected via `IDBTransaction.durability` for spec conformance only;
295
+ // the SQLite/WebSQL backend has no equivalent flush/fsync knob to wire this to.
296
+ let durability = options && options.durability;
297
+ durability ||= 'default';
298
+ if (durability !== 'default' && durability !== 'strict' && durability !== 'relaxed') {
299
+ throw new TypeError('Invalid transaction durability: ' + durability);
300
+ }
301
+
286
302
  // Do not set transaction state to "inactive" yet (will be set after
287
303
  // timeout on creating transaction instance):
288
304
  // https://github.com/w3c/IndexedDB/issues/87
289
- const trans = IDBTransaction.__createInstance(this, objectStoreNames, mode);
305
+ const trans = IDBTransaction.__createInstance(this, objectStoreNames, mode, durability);
290
306
  this.__transactions.push(trans);
291
307
  return trans;
292
308
  };
@@ -336,6 +352,7 @@ IDBDatabase.prototype.__forceClose = function (msg) {
336
352
  /* eslint-disable unicorn/no-top-level-side-effects -- Would be good */
337
353
  util.defineOuterInterface(IDBDatabase.prototype, listeners);
338
354
  util.defineReadonlyOuterInterface(IDBDatabase.prototype, readonlyProperties);
355
+ util.setOperationNames(IDBDatabase.prototype);
339
356
 
340
357
  Object.defineProperty(IDBDatabase.prototype, 'constructor', {
341
358
  enumerable: false,