indexeddbshim 17.2.2 → 17.3.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 (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 +3058 -1167
  38. package/dist/indexeddbshim-UnicodeIdentifiers-node.cjs.map +1 -1
  39. package/dist/indexeddbshim-UnicodeIdentifiers.js +2627 -986
  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 +3058 -1167
  44. package/dist/indexeddbshim-node.cjs.map +1 -1
  45. package/dist/indexeddbshim-noninvasive.js +2627 -986
  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 +2627 -986
  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 +104 -57
  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 +5 -5
  91. package/src/util.js +29 -1
  92. package/typings/shimIndexedDB.d.ts +1 -1
@@ -9,7 +9,7 @@ import CFG from './CFG.js';
9
9
 
10
10
  let uniqueID = 0;
11
11
  const listeners = ['onabort', 'oncomplete', 'onerror'];
12
- const readonlyProperties = ['objectStoreNames', 'mode', 'db', 'error'];
12
+ const readonlyProperties = ['objectStoreNames', 'mode', 'durability', 'db', 'error'];
13
13
 
14
14
  /**
15
15
  * @typedef {number} Integer
@@ -26,6 +26,7 @@ const readonlyProperties = ['objectStoreNames', 'mode', 'db', 'error'];
26
26
  /**
27
27
  * @typedef {EventTarget & {
28
28
  * mode: "readonly"|"readwrite"|"versionchange",
29
+ * durability: "default"|"strict"|"relaxed",
29
30
  * db: import('./IDBDatabase.js').IDBDatabaseFull,
30
31
  * on__abort: () => void,
31
32
  * on__complete: () => void,
@@ -35,14 +36,17 @@ const readonlyProperties = ['objectStoreNames', 'mode', 'db', 'error'];
35
36
  * on__preabort: () => void,
36
37
  * __abortTransaction: (err: Error|DOMException|null) => void,
37
38
  * __executeRequests: () => void,
38
- * __tx: SQLTransaction,
39
+ * __tx: import('websql-configurable/lib/websql/WebSQLTransaction.js').default,
39
40
  * __id: Integer,
40
41
  * __active: boolean,
42
+ * __handlerActive: boolean,
41
43
  * __running: boolean,
42
44
  * __errored: boolean,
45
+ * __committed: boolean,
43
46
  * __requests: RequestInfo[],
44
47
  * __db: import('./IDBDatabase.js').IDBDatabaseFull,
45
48
  * __mode: string,
49
+ * __durability: string,
46
50
  * __error: null|DOMException|Error,
47
51
  * __objectStoreNames: import('./DOMStringList.js').DOMStringListFull,
48
52
  * __storeHandles: {
@@ -54,6 +58,7 @@ const readonlyProperties = ['objectStoreNames', 'mode', 'db', 'error'];
54
58
  * __transactionEndCallback: () => void,
55
59
  * __transactionFinished: boolean,
56
60
  * __completed: boolean,
61
+ * __transFinishedCbFired: boolean,
57
62
  * __internal: boolean,
58
63
  * __abortFinished: boolean,
59
64
  * __createRequest: (
@@ -68,6 +73,7 @@ const readonlyProperties = ['objectStoreNames', 'mode', 'db', 'error'];
68
73
  * args?: ObjectArray
69
74
  * ) => void,
70
75
  * __assertActive: () => void,
76
+ * commit: () => void,
71
77
  * __addNonRequestToTransactionQueue: (
72
78
  * callback: SQLCallback,
73
79
  * args?: ObjectArray
@@ -98,9 +104,10 @@ const IDBTransactionAlias = IDBTransaction;
98
104
  * @param {import('./IDBDatabase.js').IDBDatabaseFull} db
99
105
  * @param {import('./DOMStringList.js').DOMStringListFull} storeNames
100
106
  * @param {string} mode
107
+ * @param {string} [durability]
101
108
  * @returns {IDBTransactionFull}
102
109
  */
103
- IDBTransaction.__createInstance = function (db, storeNames, mode) {
110
+ IDBTransaction.__createInstance = function (db, storeNames, mode, durability = 'default') {
104
111
  /**
105
112
  * @class
106
113
  * @this {IDBTransactionFull}
@@ -113,11 +120,22 @@ IDBTransaction.__createInstance = function (db, storeNames, mode) {
113
120
  // eslint-disable-next-line unicorn/no-top-level-assignment-in-function -- Debugging only
114
121
  me.__id = ++uniqueID; // for debugging simultaneous transactions
115
122
  me.__active = true;
123
+ // Tracks the spec's "active" flag for the purpose of validating new
124
+ // requests/`commit()`: true only during the initial synchronous
125
+ // script that created the transaction and during each dispatched
126
+ // request's synchronous `success`/`error` handler. Deliberately
127
+ // kept separate from `__active` above, which additionally (and
128
+ // permanently, once false) signals that request-queue processing
129
+ // has stopped -- `executeNextRequest` relies on that to know
130
+ // whether it's still safe to finish the transaction normally.
131
+ me.__handlerActive = true;
116
132
  me.__running = false;
117
133
  me.__errored = false;
134
+ me.__committed = false;
118
135
  me.__requests = [];
119
136
  me.__objectStoreNames = storeNames;
120
137
  me.__mode = mode;
138
+ me.__durability = durability;
121
139
  me.__db = db;
122
140
  me.__error = null;
123
141
  // @ts-expect-error Part of `ShimEventTarget`
@@ -179,17 +197,34 @@ IDBTransaction.prototype.__transFinishedCb = function (err, cb) {
179
197
  */
180
198
  IDBTransaction.prototype.__callTransFinishedCb = function (err, cb) {
181
199
  const me = this;
200
+ if (me.__completed || me.__transFinishedCbFired) {
201
+ // Either this transaction's `nonstandardTransCb` installation (see
202
+ // `__executeRequests`) already found `__transactionEndCallback` set
203
+ // and fired `__transFinishedCb` itself in the meantime -- a race with
204
+ // this call's own (possibly `setTimeout`-deferred) retry below -- or
205
+ // this same call already fired it on an earlier retry. Either way,
206
+ // don't fire a second commit/rollback for the same transaction.
207
+ // `__transFinishedCbFired` is checked in addition to `__completed`
208
+ // because `__completed` isn't set until the resulting SQL commit/
209
+ // rollback round trip actually finishes, which is too late to stop
210
+ // the other side from *also* firing while that's still in flight.
211
+ return;
212
+ }
182
213
  if (me.__transFinishedCb === IDBTransaction.prototype.__transFinishedCb) {
183
- // Standard (3-argument) `transaction()` implementations (browser WebSQL,
184
- // `cordova-plugin-sqlite-2`, etc.) never invoke the non-standard 4th
185
- // callback that installs the real `__transFinishedCb`, so waiting for
186
- // it here would defer forever. Detect that via arity and, if it's not
187
- // supported, just call the default (the driver auto-commits on its own).
214
+ // Standard (3-argument) `transaction()`/`readTransaction()` implementations
215
+ // (browser WebSQL, `cordova-plugin-sqlite-2`, etc.) never invoke the
216
+ // non-standard 4th callback that installs the real `__transFinishedCb`,
217
+ // so waiting for it here would defer forever. Detect that via arity
218
+ // (checking whichever of the two methods this transaction's own mode
219
+ // actually uses -- see `__executeRequests`) and, if it's not supported,
220
+ // just call the default (the driver auto-commits on its own).
188
221
  const dbConn = me.db && me.db.__db;
222
+ const dbMethodName = me.mode === 'readonly' ? 'readTransaction' : 'transaction';
189
223
  const supportsNonstandardTransCb = Boolean(
190
- dbConn && typeof dbConn.transaction === 'function' && dbConn.transaction.length >= 4
224
+ dbConn && typeof dbConn[dbMethodName] === 'function' && dbConn[dbMethodName].length >= 4
191
225
  );
192
226
  if (!supportsNonstandardTransCb) {
227
+ me.__transFinishedCbFired = true;
193
228
  me.__transFinishedCb(err, cb);
194
229
  return;
195
230
  }
@@ -198,6 +233,7 @@ IDBTransaction.prototype.__callTransFinishedCb = function (err, cb) {
198
233
  }, 0);
199
234
  return;
200
235
  }
236
+ me.__transFinishedCbFired = true;
201
237
  me.__transFinishedCb(err, cb);
202
238
  };
203
239
  /**
@@ -211,6 +247,15 @@ IDBTransaction.prototype.__executeRequests = function () {
211
247
  return;
212
248
  }
213
249
 
250
+ // The synchronous script that created this transaction (and
251
+ // synchronously queued whatever requests it wanted to) has now
252
+ // definitely returned control to the event loop -- this callback was
253
+ // itself deferred via `setTimeout(..., 0)` for exactly that reason.
254
+ // So the transaction's initial "active" window closes here, until the
255
+ // first dispatched request's handler (see `success`/`error` below)
256
+ // reopens it.
257
+ me.__handlerActive = false;
258
+
214
259
  me.__running = true;
215
260
 
216
261
  me.db.__db[me.mode === 'readonly' ? 'readTransaction' : 'transaction']( // `readTransaction` is optimized, at least in `node-websql`
@@ -248,20 +293,34 @@ IDBTransaction.prototype.__executeRequests = function () {
248
293
  q.req.__error = null;
249
294
 
250
295
  me.__active = true;
296
+ me.__handlerActive = true;
251
297
  const e = createEvent('success');
252
298
  q.req.dispatchEvent(e);
253
- // Do not set __active flag to false yet: https://github.com/w3c/IndexedDB/issues/87
299
+ // Do not set __active or __handlerActive flags to false yet:
300
+ // https://github.com/w3c/IndexedDB/issues/87 -- a follow-up
301
+ // request queued from an `await`-based continuation of this
302
+ // one (a microtask, not a further synchronous call within
303
+ // this handler) must still pass `__assertActive`'s check of
304
+ // both flags. `checkQueueEntry` (see `executeNextRequest`)
305
+ // is what actually resets `__handlerActive`, once it's
306
+ // confirmed (across its own bounded microtask wait) that no
307
+ // such continuation queued anything.
254
308
  if (e.__legacyOutputDidListenersThrowError) {
255
309
  logError('Error', 'An error occurred in a success handler attached to request chain', e.__legacyOutputDidListenersThrowError); // We do nothing else with this error as per spec
256
- // me.__active = false;
257
- me.__abortTransaction(createDOMException('AbortError', 'A request was aborted (in user handler after success).'));
258
- return;
310
+ if (!me.__committed) { // An explicit `commit()` locks in the commit, so errors thrown afterward must not abort it
311
+ // me.__active = false;
312
+ me.__abortTransaction(createDOMException('AbortError', 'A request was aborted (in user handler after success).'));
313
+ return;
314
+ }
259
315
  }
260
- util.runContinuationSafely(executeNextRequest);
316
+ util.runContinuationSafely(advanceAfterDispatch);
261
317
  }
262
318
 
263
319
  /**
264
- * @param {[tx: SQLTransaction|DOMException|Error|SQLError, err?: SQLError]} args
320
+ * @param {[
321
+ * tx: import('websql-configurable/lib/websql/WebSQLTransaction.js').default|DOMException|Error,
322
+ * err?: Error & {code?: number}
323
+ * ]} args
265
324
  * @returns {void}
266
325
  */
267
326
  function error (...args /* tx, err */) {
@@ -283,11 +342,18 @@ IDBTransaction.prototype.__executeRequests = function () {
283
342
  q.req.__done = true;
284
343
  q.req.__error = err;
285
344
  q.req.__result = undefined; // Must be undefined if an error per `result` getter
286
- q.req.addLateEventListener('error', function (e) {
287
- if (e.cancelable && e.defaultPrevented && !e.__legacyOutputDidListenersThrowError) {
288
- executeNextRequest();
345
+ q.req.addLateEventListener(
346
+ 'error',
347
+ /**
348
+ * @param {Event & {__legacyOutputDidListenersThrowError: boolean}} e
349
+ * @returns {void}
350
+ */
351
+ function (e) {
352
+ if (e.cancelable && e.defaultPrevented && !e.__legacyOutputDidListenersThrowError) {
353
+ advanceAfterDispatch();
354
+ }
289
355
  }
290
- });
356
+ );
291
357
  q.req.addDefaultEventListener('error', function () {
292
358
  if (!q.req) { // TS guard
293
359
  return;
@@ -296,46 +362,187 @@ IDBTransaction.prototype.__executeRequests = function () {
296
362
  });
297
363
 
298
364
  me.__active = true;
365
+ me.__handlerActive = true;
299
366
  const e = createEvent('error', err, {bubbles: true, cancelable: true});
300
367
  q.req.dispatchEvent(e);
301
- // Do not set __active flag to false yet: https://github.com/w3c/IndexedDB/issues/87
368
+ // Do not set __active or __handlerActive flags to false yet --
369
+ // see the matching comment in `success`, above.
302
370
  if (e.__legacyOutputDidListenersThrowError) {
303
371
  logError('Error', 'An error occurred in an error handler attached to request chain', e.__legacyOutputDidListenersThrowError); // We do nothing else with this error as per spec
304
372
  e.preventDefault(); // Prevent 'error' default as steps indicate we should abort with `AbortError` even without cancellation
373
+ if (me.__committed) { // An explicit `commit()` locks in the commit, so errors thrown afterward must not abort it
374
+ util.runContinuationSafely(advanceAfterDispatch);
375
+ return;
376
+ }
305
377
  me.__abortTransaction(createDOMException('AbortError', 'A request was aborted (in user handler after error).'));
306
378
  }
307
379
  }
308
380
 
381
+ /**
382
+ * Sets up `q` for the current queue index and, for a genuine
383
+ * request (not one of our internal non-request queue entries),
384
+ * deactivates the transaction for the duration of its
385
+ * (possibly async) work. Returns `false` if there's nothing to
386
+ * do (an already-aborted request).
387
+ * @returns {boolean}
388
+ */
389
+ function prepareNextRequest () {
390
+ q = me.__requests[i];
391
+ if (!q.req) {
392
+ // Non-standard, non-`IDBRequest` queue entries (e.g.
393
+ // the internal `onupgradeneeded` dispatch op in
394
+ // `IDBFactory.js`) dispatch straight to user code
395
+ // without going through `success`/`error` below --
396
+ // unlike those, they never restore `__active`/
397
+ // `__handlerActive` to `true` before doing so, so
398
+ // they rely on the flags being left as they are
399
+ // (not reset here).
400
+ return true;
401
+ }
402
+ if (q.req.__done) { // Avoid continuing with aborted requests
403
+ return false;
404
+ }
405
+ // We're now handing off to (possibly async) work for
406
+ // this request, so the transaction is no longer active
407
+ // until its own `success`/`error` dispatch (below)
408
+ // sets these flags again -- a check that happens to
409
+ // run during this gap (e.g. `commit()` called from an
410
+ // unrelated `setTimeout`) must see the transaction as
411
+ // inactive, per spec.
412
+ me.__active = false;
413
+ me.__handlerActive = false;
414
+ return true;
415
+ }
416
+
309
417
  /**
310
418
  * @returns {void}
311
419
  */
312
- function executeNextRequest () {
420
+ function launchQueuedOp () {
421
+ try {
422
+ if (!q.req) {
423
+ q.op(tx, q.args, () => util.runContinuationSafely(executeNextRequest), error);
424
+ return;
425
+ }
426
+ q.op(tx, q.args, success, error, executeNextRequest);
427
+ } catch (e) {
428
+ error(/** @type {Error} */ (e));
429
+ }
430
+ }
431
+
432
+ /**
433
+ * @returns {void}
434
+ */
435
+ function runQueuedRequest () {
436
+ if (!prepareNextRequest()) {
437
+ return;
438
+ }
439
+ launchQueuedOp();
440
+ }
441
+
442
+ /**
443
+ * A request's `success`/`error` event fires (and any `await`-based
444
+ * continuation watching it resolves) before this check runs, since
445
+ * that dispatch happens synchronously above, one call frame up.
446
+ * If such a continuation queues a follow-up request, it does so
447
+ * from a microtask -- so if the JS-level queue is merely found
448
+ * empty here, that doesn't yet mean no more work is coming, only
449
+ * that none has been queued *yet*. Re-check across a small, bounded
450
+ * number of further microtask turns (letting a typical `await`
451
+ * chain like `await store.put(...); await store.get(...)` catch
452
+ * up) before finally concluding the transaction is genuinely done.
453
+ * Applies to `readonly` transactions too, not just `readwrite`/
454
+ * `versionchange`: a `readonly` transaction's `complete` event
455
+ * can otherwise fire synchronously, immediately after its last
456
+ * request's `success` handler returns -- before an `await`-based
457
+ * consumer of that same handler (e.g. one that resolves a
458
+ * promise from within `onsuccess` and only attaches `oncomplete`
459
+ * afterward) ever gets a turn to run, so it can miss `complete`
460
+ * entirely. `readonly` requests don't hold a real SQL
461
+ * transaction open, though, so there's no file-lock/connection
462
+ * collision risk in waiting the same bounded amount here.
463
+ * @param {number} attemptsLeft
464
+ * @returns {void}
465
+ */
466
+ function checkQueueEntry (attemptsLeft) {
313
467
  if (me.__errored || me.__requestsFinished) {
314
- // We've already called "onerror", "onabort", or thrown within the transaction, so don't do it again.
315
468
  return;
316
469
  }
317
- i++;
318
- if (i >= me.__requests.length) {
470
+ if (i < me.__requests.length) {
471
+ runQueuedRequest();
472
+ return;
473
+ }
474
+ if (attemptsLeft <= 0) {
319
475
  // All requests in the transaction are done
320
476
  me.__requests = [];
321
477
  if (me.__active) {
322
478
  requestsFinished();
323
479
  }
324
- } else {
325
- try {
326
- q = me.__requests[i];
327
- if (!q.req) {
328
- q.op(tx, q.args, () => util.runContinuationSafely(executeNextRequest), error);
329
- return;
330
- }
331
- if (q.req.__done) { // Avoid continuing with aborted requests
332
- return;
333
- }
334
- q.op(tx, q.args, success, error, executeNextRequest);
335
- } catch (e) {
336
- error(/** @type {Error} */ (e));
337
- }
480
+ return;
481
+ }
482
+ queueMicrotask(() => {
483
+ checkQueueEntry(attemptsLeft - 1);
484
+ });
485
+ }
486
+
487
+ /**
488
+ * @returns {void}
489
+ */
490
+ function executeNextRequest () {
491
+ if (me.__errored || me.__requestsFinished) {
492
+ // We've already called "onerror", "onabort", or thrown within the transaction, so don't do it again.
493
+ return;
494
+ }
495
+ i++;
496
+ if (i >= me.__requests.length) {
497
+ checkQueueEntry(10);
498
+ return;
499
+ }
500
+ runQueuedRequest();
501
+ }
502
+
503
+ /**
504
+ * Same as `executeNextRequest`, but used specifically as the
505
+ * continuation from a request's own `success`/`error` dispatch
506
+ * (see call sites above): gives same-tick microtasks scheduled
507
+ * from within that handler (e.g. a plain
508
+ * `Promise.resolve().then(...)`) a chance to run -- and still
509
+ * observe the transaction as active -- before we deactivate it
510
+ * again for the next queued request, per
511
+ * https://github.com/w3c/IndexedDB/issues/87.
512
+ *
513
+ * This is safe for `readonly` transactions too only because
514
+ * `__executeRequests` now passes a `nonstandardTransCb` to
515
+ * `readTransaction()` (matching `.transaction()`) and
516
+ * `requestsFinished` defers `readonly` completion through
517
+ * `__transactionEndCallback`/`__callTransFinishedCb` the same
518
+ * way `readwrite` already did: the WebSQL driver's
519
+ * "optimized" `readTransaction` path used to finalize the
520
+ * underlying transaction as soon as no further `executeSql`
521
+ * call was already in flight or queued, which -- before that
522
+ * fix -- meant even a microtask-long gap here would let the
523
+ * driver consider the transaction done, silently dropping
524
+ * every subsequent queued request (never firing
525
+ * `success`/`error` for it at all).
526
+ * @returns {void}
527
+ */
528
+ function advanceAfterDispatch () {
529
+ if (me.__errored || me.__requestsFinished) {
530
+ return;
531
+ }
532
+ i++;
533
+ if (i >= me.__requests.length) {
534
+ checkQueueEntry(10);
535
+ return;
338
536
  }
537
+ queueMicrotask(() => {
538
+ if (me.__errored || me.__requestsFinished) {
539
+ return;
540
+ }
541
+ if (!prepareNextRequest()) {
542
+ return;
543
+ }
544
+ launchQueuedOp();
545
+ });
339
546
  }
340
547
 
341
548
  executeNextRequest();
@@ -345,7 +552,7 @@ IDBTransaction.prototype.__executeRequests = function () {
345
552
  if (webSQLErr === true) { // Not a genuine SQL error
346
553
  return;
347
554
  }
348
- const err = webSQLErrback(/** @type {SQLError} */ (webSQLErr));
555
+ const err = webSQLErrback(/** @type {Error & {code?: number}} */ (webSQLErr));
349
556
  me.__abortTransaction(err);
350
557
  },
351
558
  function () {
@@ -359,22 +566,46 @@ IDBTransaction.prototype.__executeRequests = function () {
359
566
  me.__transactionFinished = true;
360
567
  return;
361
568
  }
362
- if (me.__transactionEndCallback && !me.__completed) {
569
+ if (me.__transactionEndCallback && !me.__completed && !me.__transFinishedCbFired) {
570
+ me.__transFinishedCbFired = true;
363
571
  me.__transFinishedCb(me.__errored, me.__transactionEndCallback);
364
572
  }
365
573
  },
366
574
  function (currentTask, err, done, rollback, commit) {
367
- if (currentTask.readOnly || err) {
575
+ if (err) {
368
576
  return true;
369
577
  }
370
- me.__transFinishedCb = function (err, cb) {
371
- if (err) {
372
- rollback(err, cb);
373
- } else {
374
- commit(cb);
578
+ // `readonly` transactions never hold a real SQL transaction open
579
+ // (see `WebSQLTransaction`'s constructor skipping `BEGIN;` for
580
+ // them), so there's no commit/rollback round trip to defer --
581
+ // `done` itself is the "genuinely finished" signal for them,
582
+ // called synchronously once `requestsFinished` (via
583
+ // `__callTransFinishedCb`) confirms no further request is
584
+ // coming.
585
+ me.__transFinishedCb = currentTask.readOnly
586
+ ? function (err, cb) {
587
+ done(err);
588
+ if (cb) {
589
+ cb();
590
+ }
375
591
  }
376
- };
377
- if (me.__transactionEndCallback && !me.__completed) {
592
+ : function (err, cb) {
593
+ if (err) {
594
+ rollback(err, cb);
595
+ } else {
596
+ commit(cb);
597
+ }
598
+ };
599
+ // Guarded by `__transFinishedCbFired`, not just `__completed`: this
600
+ // installation can race `IDBTransaction.prototype.__callTransFinishedCb`'s
601
+ // own `setTimeout` retry (see there) -- both watch for
602
+ // `__transFinishedCb` to become installed and `__transactionEndCallback`
603
+ // to become set, and either could observe both conditions first.
604
+ // `__completed` alone isn't set until the resulting SQL commit/rollback
605
+ // round trip actually finishes, which is too late to prevent the other
606
+ // side from *also* firing in the meantime.
607
+ if (me.__transactionEndCallback && !me.__completed && !me.__transFinishedCbFired) {
608
+ me.__transFinishedCbFired = true;
378
609
  me.__transFinishedCb(me.__errored, me.__transactionEndCallback);
379
610
  }
380
611
  return false;
@@ -386,6 +617,7 @@ IDBTransaction.prototype.__executeRequests = function () {
386
617
  */
387
618
  function requestsFinished () {
388
619
  me.__active = false;
620
+ me.__handlerActive = false;
389
621
  me.__requestsFinished = true;
390
622
 
391
623
  /**
@@ -412,16 +644,27 @@ IDBTransaction.prototype.__executeRequests = function () {
412
644
  me.__storeHandles = {};
413
645
  }
414
646
  }
415
- if (me.mode === 'readwrite') {
647
+ if (me.mode === 'readwrite' || me.mode === 'readonly') {
416
648
  if (me.__transactionFinished) {
417
649
  complete();
418
650
  return;
419
651
  }
420
652
  me.__transactionEndCallback = complete;
421
- return;
422
- }
423
- if (me.mode === 'readonly') {
424
- complete();
653
+ // The underlying SQL driver's own "queue empty" check
654
+ // (`nonstandardTransCb`, above) typically already ran and
655
+ // installed the real `__transFinishedCb` *before* this point --
656
+ // it fires synchronously once the last SQL batch's results are
657
+ // processed, whereas `requestsFinished` can now be reached only
658
+ // after `checkQueueEntry`'s bounded microtask wait, i.e. later.
659
+ // When that happens, its check for `me.__transactionEndCallback`
660
+ // (not yet set at that earlier time) finds nothing to do and
661
+ // just returns, so nothing else will ever re-trigger the actual
662
+ // commit unless this explicitly does so now. For `readonly`,
663
+ // `__transFinishedCb` (installed by the `nonstandardTransCb`
664
+ // passed to `readTransaction()`, above) just calls the WebSQL
665
+ // driver's own `done` -- there's no real commit/rollback SQL
666
+ // step for a `readonly` transaction.
667
+ me.__callTransFinishedCb(me.__errored, complete);
425
668
  return;
426
669
  }
427
670
  const ev = /** @type {Event & {complete: () => void}} */ (
@@ -449,10 +692,13 @@ IDBTransaction.prototype.__createRequest = function (source) {
449
692
 
450
693
  /**
451
694
  * @typedef {(
452
- * tx: SQLTransaction,
695
+ * tx: import('websql-configurable/lib/websql/WebSQLTransaction.js').default,
453
696
  * args: ObjectArray,
454
697
  * success: (result?: any, req?: import('./IDBRequest.js').IDBRequestFull) => void,
455
- * error: (tx: SQLTransaction|Error|DOMException|SQLError, err?: SQLError) => void,
698
+ * error: (
699
+ * tx: import('websql-configurable/lib/websql/WebSQLTransaction.js').default|Error|DOMException,
700
+ * err?: Error & {code?: number}
701
+ * ) => void,
456
702
  * executeNextRequest?: () => void
457
703
  * ) => void} SQLCallback
458
704
  */
@@ -504,10 +750,11 @@ IDBTransaction.prototype.__pushToQueue = function (request, callback, args) {
504
750
 
505
751
  /**
506
752
  * @throws {DOMException}
753
+ * @this {IDBTransactionFull}
507
754
  * @returns {void}
508
755
  */
509
756
  IDBTransaction.prototype.__assertActive = function () {
510
- if (!this.__active) {
757
+ if (!this.__active || !this.__handlerActive || this.__committed) {
511
758
  throw createDOMException('TransactionInactiveError', 'A request was placed against a transaction which is currently not active, or which is finished');
512
759
  }
513
760
  };
@@ -588,7 +835,7 @@ IDBTransaction.prototype.__abortTransaction = function (err) {
588
835
  ).forEach(function (store) {
589
836
  // Store was already created so we restore to name before the rename
590
837
  if ('__pendingName' in store &&
591
- me.db.__oldObjectStoreNames.indexOf(store.__pendingName) > -1 // eslint-disable-line unicorn/prefer-includes -- Not supported
838
+ me.db.__oldObjectStoreNames.indexOf(/** @type {string} */ (store.__pendingName)) > -1 // eslint-disable-line unicorn/prefer-includes -- Not supported
592
839
  ) {
593
840
  store.__name = store.__originalName;
594
841
  }
@@ -599,7 +846,7 @@ IDBTransaction.prototype.__abortTransaction = function (err) {
599
846
  ).forEach(function (index) {
600
847
  // Index was already created so we restore to name before the rename
601
848
  if ('__pendingName' in index &&
602
- store.__oldIndexNames.indexOf(index.__pendingName) > -1 // eslint-disable-line unicorn/prefer-includes -- Not supported
849
+ store.__oldIndexNames.indexOf(/** @type {string} */ (index.__pendingName)) > -1 // eslint-disable-line unicorn/prefer-includes -- Not supported
603
850
  ) {
604
851
  index.__name = index.__originalName;
605
852
  }
@@ -622,8 +869,8 @@ IDBTransaction.prototype.__abortTransaction = function (err) {
622
869
  }
623
870
 
624
871
  /**
625
- * @param {SQLTransaction|null} [tx]
626
- * @param {SQLResultSet|SQLError|{code: 0}} [errOrResult]
872
+ * @param {import('websql-configurable/lib/websql/WebSQLTransaction.js').default|null} [tx]
873
+ * @param {import('websql-configurable/lib/websql/WebSQLResultSet.js').default|(Error & {code?: number})|{code: 0}} [errOrResult]
627
874
  * @returns {void}
628
875
  */
629
876
  function abort (tx, errOrResult) {
@@ -652,7 +899,7 @@ IDBTransaction.prototype.__abortTransaction = function (err) {
652
899
  q.req.__error = createDOMException('AbortError', 'A request was aborted (an unfinished request).');
653
900
  const reqEvt = createEvent('error', q.req.__error, {bubbles: true, cancelable: true});
654
901
  return new SyncPromise(
655
- /** @type {() => void} */
902
+ /** @type {(resolve: (value?: any) => void) => void} */
656
903
  (resolve) => {
657
904
  setTimeout(() => {
658
905
  if (!q.req) { // TS guard
@@ -696,7 +943,7 @@ IDBTransaction.prototype.__abortTransaction = function (err) {
696
943
  'ROLLBACK',
697
944
  [],
698
945
  abort,
699
- /** @type {SQLStatementErrorCallback} */ (abort)
946
+ /** @type {import('websql-configurable/lib/websql/WebSQLTransaction.js').SqlErrorCallback} */ (abort)
700
947
  ); // Not working in some circumstances, even in Node
701
948
  } catch (err) {
702
949
  // Browser errs when transaction has ended and since it most likely already erred here,
@@ -720,14 +967,34 @@ IDBTransaction.prototype.abort = function () {
720
967
  }
721
968
  if (CFG.DEBUG) { console.log('The transaction was aborted', me); }
722
969
  IDBTransaction.__assertNotFinished(me);
970
+ if (me.__committed) {
971
+ throw createDOMException('InvalidStateError', 'The transaction has already been committed');
972
+ }
723
973
  me.__abortTransaction(null);
724
974
  };
725
975
 
976
+ /**
977
+ * @see https://www.w3.org/TR/IndexedDB/#dom-idbtransaction-commit
978
+ * @this {IDBTransactionFull}
979
+ * @returns {void}
980
+ */
981
+ IDBTransaction.prototype.commit = function () {
982
+ const me = this;
983
+ if (!(me instanceof IDBTransaction)) {
984
+ throw new TypeError('Illegal invocation');
985
+ }
986
+ if (!me.__active || !me.__handlerActive || me.__committed) {
987
+ throw createDOMException('InvalidStateError', 'Failed to execute \'commit\' on \'IDBTransaction\': The transaction is not active.');
988
+ }
989
+ if (CFG.DEBUG) { console.log('The transaction was explicitly committed', me); }
990
+ me.__committed = true;
991
+ };
992
+
726
993
  IDBTransaction.prototype[Symbol.toStringTag] = 'IDBTransactionPrototype';
727
994
 
728
995
  /**
729
996
  *
730
- * @param {IDBTransactionFull|undefined} tx
997
+ * @param {IDBTransactionFull|null|undefined} tx
731
998
  * @returns {void}
732
999
  */
733
1000
  IDBTransaction.__assertVersionChange = function (tx) {
@@ -737,7 +1004,7 @@ IDBTransaction.__assertVersionChange = function (tx) {
737
1004
  };
738
1005
  /**
739
1006
  *
740
- * @param {IDBTransactionFull} tx
1007
+ * @param {IDBTransactionFull|null} tx
741
1008
  * @throws {DOMException}
742
1009
  * @returns {void}
743
1010
  */
@@ -749,7 +1016,7 @@ IDBTransaction.__assertNotVersionChange = function (tx) {
749
1016
 
750
1017
  /**
751
1018
  *
752
- * @param {IDBTransactionFull|undefined} tx
1019
+ * @param {IDBTransactionFull|null|undefined} tx
753
1020
  * @throws {DOMException}
754
1021
  * @returns {void}
755
1022
  */
@@ -778,12 +1045,12 @@ IDBTransaction.__assertNotFinishedObjectStoreMethod = function (tx) {
778
1045
 
779
1046
  /**
780
1047
  *
781
- * @param {IDBTransactionFull|undefined} tx
1048
+ * @param {IDBTransactionFull|null|undefined} tx
782
1049
  * @throws {DOMException}
783
1050
  * @returns {void}
784
1051
  */
785
1052
  IDBTransaction.__assertActive = function (tx) {
786
- if (!tx || !tx.__active) {
1053
+ if (!tx || !tx.__active || tx.__committed) {
787
1054
  throw createDOMException('TransactionInactiveError', 'A request was placed against a transaction which is currently not active, or which is finished');
788
1055
  }
789
1056
  };
@@ -800,6 +1067,7 @@ IDBTransaction.prototype.__getParent = function () {
800
1067
  /* eslint-disable unicorn/no-top-level-side-effects -- Would be good */
801
1068
  util.defineOuterInterface(IDBTransaction.prototype, listeners);
802
1069
  util.defineReadonlyOuterInterface(IDBTransaction.prototype, readonlyProperties);
1070
+ util.setOperationNames(IDBTransaction.prototype);
803
1071
 
804
1072
  Object.defineProperty(IDBTransaction.prototype, 'constructor', {
805
1073
  enumerable: false,