indexeddbshim 17.2.1 → 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.
- package/README.md +8 -5
- package/dist/CFG.d.ts +1 -1
- package/dist/CFG.d.ts.map +1 -1
- package/dist/DOMException.d.ts +21 -7
- package/dist/DOMException.d.ts.map +1 -1
- package/dist/DOMStringList.d.ts.map +1 -1
- package/dist/Event.d.ts +12 -6
- package/dist/Event.d.ts.map +1 -1
- package/dist/IDBCursor.d.ts +125 -7
- package/dist/IDBCursor.d.ts.map +1 -1
- package/dist/IDBDatabase.d.ts +17 -10
- package/dist/IDBDatabase.d.ts.map +1 -1
- package/dist/IDBFactory.d.ts +5 -0
- package/dist/IDBFactory.d.ts.map +1 -1
- package/dist/IDBIndex.d.ts +43 -13
- package/dist/IDBIndex.d.ts.map +1 -1
- package/dist/IDBKeyRange.d.ts +6 -2
- package/dist/IDBKeyRange.d.ts.map +1 -1
- package/dist/IDBObjectStore.d.ts +66 -17
- package/dist/IDBObjectStore.d.ts.map +1 -1
- package/dist/IDBRecord.d.ts +44 -0
- package/dist/IDBRecord.d.ts.map +1 -0
- package/dist/IDBRequest.d.ts +2 -2
- package/dist/IDBRequest.d.ts.map +1 -1
- package/dist/IDBTransaction.d.ts +43 -15
- package/dist/IDBTransaction.d.ts.map +1 -1
- package/dist/IDBVersionChangeEvent.d.ts +23 -12
- package/dist/IDBVersionChangeEvent.d.ts.map +1 -1
- package/dist/Key.d.ts +7 -6
- package/dist/Key.d.ts.map +1 -1
- package/dist/Sca.d.ts +15 -0
- package/dist/Sca.d.ts.map +1 -1
- package/dist/indexeddbshim-Key.js +59 -23
- package/dist/indexeddbshim-Key.js.map +1 -1
- package/dist/indexeddbshim-Key.min.js +2 -2
- package/dist/indexeddbshim-Key.min.js.map +1 -1
- package/dist/indexeddbshim-UnicodeIdentifiers-node.cjs +3123 -1169
- package/dist/indexeddbshim-UnicodeIdentifiers-node.cjs.map +1 -1
- package/dist/indexeddbshim-UnicodeIdentifiers.js +2683 -979
- package/dist/indexeddbshim-UnicodeIdentifiers.js.map +1 -1
- package/dist/indexeddbshim-UnicodeIdentifiers.min.js +4 -4
- package/dist/indexeddbshim-UnicodeIdentifiers.min.js.map +1 -1
- package/dist/indexeddbshim-node.cjs +3123 -1169
- package/dist/indexeddbshim-node.cjs.map +1 -1
- package/dist/indexeddbshim-noninvasive.js +2683 -979
- package/dist/indexeddbshim-noninvasive.js.map +1 -1
- package/dist/indexeddbshim-noninvasive.min.js +4 -4
- package/dist/indexeddbshim-noninvasive.min.js.map +1 -1
- package/dist/indexeddbshim.js +2683 -979
- package/dist/indexeddbshim.js.map +1 -1
- package/dist/indexeddbshim.min.js +4 -4
- package/dist/indexeddbshim.min.js.map +1 -1
- package/dist/node-UnicodeIdentifiers.d.ts +2 -2
- package/dist/node-UnicodeIdentifiers.d.ts.map +1 -1
- package/dist/node.d.ts +2 -2
- package/dist/node.d.ts.map +1 -1
- package/dist/nodeSQLiteDatabase.d.ts +13 -14
- package/dist/nodeSQLiteDatabase.d.ts.map +1 -1
- package/dist/nodeWebSQL.d.ts +17 -1
- package/dist/nodeWebSQL.d.ts.map +1 -1
- package/dist/setGlobalVars.d.ts +2 -2
- package/dist/setGlobalVars.d.ts.map +1 -1
- package/dist/util.d.ts +15 -0
- package/dist/util.d.ts.map +1 -1
- package/examples/IDBKeyUtils.html +0 -1
- package/package.json +28 -21
- package/src/CFG.js +2 -1
- package/src/DOMException.js +38 -12
- package/src/DOMStringList.js +21 -2
- package/src/Event.js +9 -4
- package/src/IDBCursor.js +346 -18
- package/src/IDBDatabase.js +33 -16
- package/src/IDBFactory.js +162 -62
- package/src/IDBIndex.js +78 -45
- package/src/IDBKeyRange.js +8 -2
- package/src/IDBObjectStore.js +89 -65
- package/src/IDBRecord.js +96 -0
- package/src/IDBRequest.js +7 -7
- package/src/IDBTransaction.js +342 -61
- package/src/IDBVersionChangeEvent.js +37 -30
- package/src/Key.js +36 -18
- package/src/Sca.js +22 -1
- package/src/browser-UnicodeIdentifiers.js +3 -1
- package/src/browser-noninvasive.js +3 -1
- package/src/browser.js +3 -1
- package/src/node-UnicodeIdentifiers.js +1 -1
- package/src/node.js +1 -1
- package/src/nodeSQLiteDatabase.js +67 -2
- package/src/nodeWebSQL.js +22 -2
- package/src/setGlobalVars.js +5 -5
- package/src/util.js +29 -1
- package/typings/shimIndexedDB.d.ts +1 -1
package/src/IDBTransaction.js
CHANGED
|
@@ -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:
|
|
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,12 +197,43 @@ 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) {
|
|
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).
|
|
221
|
+
const dbConn = me.db && me.db.__db;
|
|
222
|
+
const dbMethodName = me.mode === 'readonly' ? 'readTransaction' : 'transaction';
|
|
223
|
+
const supportsNonstandardTransCb = Boolean(
|
|
224
|
+
dbConn && typeof dbConn[dbMethodName] === 'function' && dbConn[dbMethodName].length >= 4
|
|
225
|
+
);
|
|
226
|
+
if (!supportsNonstandardTransCb) {
|
|
227
|
+
me.__transFinishedCbFired = true;
|
|
228
|
+
me.__transFinishedCb(err, cb);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
183
231
|
setTimeout(() => {
|
|
184
232
|
me.__callTransFinishedCb(err, cb);
|
|
185
233
|
}, 0);
|
|
186
234
|
return;
|
|
187
235
|
}
|
|
236
|
+
me.__transFinishedCbFired = true;
|
|
188
237
|
me.__transFinishedCb(err, cb);
|
|
189
238
|
};
|
|
190
239
|
/**
|
|
@@ -198,6 +247,15 @@ IDBTransaction.prototype.__executeRequests = function () {
|
|
|
198
247
|
return;
|
|
199
248
|
}
|
|
200
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
|
+
|
|
201
259
|
me.__running = true;
|
|
202
260
|
|
|
203
261
|
me.db.__db[me.mode === 'readonly' ? 'readTransaction' : 'transaction']( // `readTransaction` is optimized, at least in `node-websql`
|
|
@@ -235,20 +293,34 @@ IDBTransaction.prototype.__executeRequests = function () {
|
|
|
235
293
|
q.req.__error = null;
|
|
236
294
|
|
|
237
295
|
me.__active = true;
|
|
296
|
+
me.__handlerActive = true;
|
|
238
297
|
const e = createEvent('success');
|
|
239
298
|
q.req.dispatchEvent(e);
|
|
240
|
-
// Do not set __active
|
|
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.
|
|
241
308
|
if (e.__legacyOutputDidListenersThrowError) {
|
|
242
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
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
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
|
+
}
|
|
246
315
|
}
|
|
247
|
-
util.runContinuationSafely(
|
|
316
|
+
util.runContinuationSafely(advanceAfterDispatch);
|
|
248
317
|
}
|
|
249
318
|
|
|
250
319
|
/**
|
|
251
|
-
* @param {[
|
|
320
|
+
* @param {[
|
|
321
|
+
* tx: import('websql-configurable/lib/websql/WebSQLTransaction.js').default|DOMException|Error,
|
|
322
|
+
* err?: Error & {code?: number}
|
|
323
|
+
* ]} args
|
|
252
324
|
* @returns {void}
|
|
253
325
|
*/
|
|
254
326
|
function error (...args /* tx, err */) {
|
|
@@ -270,11 +342,18 @@ IDBTransaction.prototype.__executeRequests = function () {
|
|
|
270
342
|
q.req.__done = true;
|
|
271
343
|
q.req.__error = err;
|
|
272
344
|
q.req.__result = undefined; // Must be undefined if an error per `result` getter
|
|
273
|
-
q.req.addLateEventListener(
|
|
274
|
-
|
|
275
|
-
|
|
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
|
+
}
|
|
276
355
|
}
|
|
277
|
-
|
|
356
|
+
);
|
|
278
357
|
q.req.addDefaultEventListener('error', function () {
|
|
279
358
|
if (!q.req) { // TS guard
|
|
280
359
|
return;
|
|
@@ -283,46 +362,187 @@ IDBTransaction.prototype.__executeRequests = function () {
|
|
|
283
362
|
});
|
|
284
363
|
|
|
285
364
|
me.__active = true;
|
|
365
|
+
me.__handlerActive = true;
|
|
286
366
|
const e = createEvent('error', err, {bubbles: true, cancelable: true});
|
|
287
367
|
q.req.dispatchEvent(e);
|
|
288
|
-
// Do not set __active
|
|
368
|
+
// Do not set __active or __handlerActive flags to false yet --
|
|
369
|
+
// see the matching comment in `success`, above.
|
|
289
370
|
if (e.__legacyOutputDidListenersThrowError) {
|
|
290
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
|
|
291
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
|
+
}
|
|
292
377
|
me.__abortTransaction(createDOMException('AbortError', 'A request was aborted (in user handler after error).'));
|
|
293
378
|
}
|
|
294
379
|
}
|
|
295
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
|
+
|
|
296
417
|
/**
|
|
297
418
|
* @returns {void}
|
|
298
419
|
*/
|
|
299
|
-
function
|
|
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) {
|
|
300
467
|
if (me.__errored || me.__requestsFinished) {
|
|
301
|
-
// We've already called "onerror", "onabort", or thrown within the transaction, so don't do it again.
|
|
302
468
|
return;
|
|
303
469
|
}
|
|
304
|
-
i
|
|
305
|
-
|
|
470
|
+
if (i < me.__requests.length) {
|
|
471
|
+
runQueuedRequest();
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
if (attemptsLeft <= 0) {
|
|
306
475
|
// All requests in the transaction are done
|
|
307
476
|
me.__requests = [];
|
|
308
477
|
if (me.__active) {
|
|
309
478
|
requestsFinished();
|
|
310
479
|
}
|
|
311
|
-
|
|
312
|
-
try {
|
|
313
|
-
q = me.__requests[i];
|
|
314
|
-
if (!q.req) {
|
|
315
|
-
q.op(tx, q.args, () => util.runContinuationSafely(executeNextRequest), error);
|
|
316
|
-
return;
|
|
317
|
-
}
|
|
318
|
-
if (q.req.__done) { // Avoid continuing with aborted requests
|
|
319
|
-
return;
|
|
320
|
-
}
|
|
321
|
-
q.op(tx, q.args, success, error, executeNextRequest);
|
|
322
|
-
} catch (e) {
|
|
323
|
-
error(/** @type {Error} */ (e));
|
|
324
|
-
}
|
|
480
|
+
return;
|
|
325
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;
|
|
536
|
+
}
|
|
537
|
+
queueMicrotask(() => {
|
|
538
|
+
if (me.__errored || me.__requestsFinished) {
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
if (!prepareNextRequest()) {
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
launchQueuedOp();
|
|
545
|
+
});
|
|
326
546
|
}
|
|
327
547
|
|
|
328
548
|
executeNextRequest();
|
|
@@ -332,7 +552,7 @@ IDBTransaction.prototype.__executeRequests = function () {
|
|
|
332
552
|
if (webSQLErr === true) { // Not a genuine SQL error
|
|
333
553
|
return;
|
|
334
554
|
}
|
|
335
|
-
const err = webSQLErrback(/** @type {
|
|
555
|
+
const err = webSQLErrback(/** @type {Error & {code?: number}} */ (webSQLErr));
|
|
336
556
|
me.__abortTransaction(err);
|
|
337
557
|
},
|
|
338
558
|
function () {
|
|
@@ -346,22 +566,46 @@ IDBTransaction.prototype.__executeRequests = function () {
|
|
|
346
566
|
me.__transactionFinished = true;
|
|
347
567
|
return;
|
|
348
568
|
}
|
|
349
|
-
if (me.__transactionEndCallback && !me.__completed) {
|
|
569
|
+
if (me.__transactionEndCallback && !me.__completed && !me.__transFinishedCbFired) {
|
|
570
|
+
me.__transFinishedCbFired = true;
|
|
350
571
|
me.__transFinishedCb(me.__errored, me.__transactionEndCallback);
|
|
351
572
|
}
|
|
352
573
|
},
|
|
353
574
|
function (currentTask, err, done, rollback, commit) {
|
|
354
|
-
if (
|
|
575
|
+
if (err) {
|
|
355
576
|
return true;
|
|
356
577
|
}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
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
|
+
}
|
|
362
591
|
}
|
|
363
|
-
|
|
364
|
-
|
|
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;
|
|
365
609
|
me.__transFinishedCb(me.__errored, me.__transactionEndCallback);
|
|
366
610
|
}
|
|
367
611
|
return false;
|
|
@@ -373,6 +617,7 @@ IDBTransaction.prototype.__executeRequests = function () {
|
|
|
373
617
|
*/
|
|
374
618
|
function requestsFinished () {
|
|
375
619
|
me.__active = false;
|
|
620
|
+
me.__handlerActive = false;
|
|
376
621
|
me.__requestsFinished = true;
|
|
377
622
|
|
|
378
623
|
/**
|
|
@@ -399,16 +644,27 @@ IDBTransaction.prototype.__executeRequests = function () {
|
|
|
399
644
|
me.__storeHandles = {};
|
|
400
645
|
}
|
|
401
646
|
}
|
|
402
|
-
if (me.mode === 'readwrite') {
|
|
647
|
+
if (me.mode === 'readwrite' || me.mode === 'readonly') {
|
|
403
648
|
if (me.__transactionFinished) {
|
|
404
649
|
complete();
|
|
405
650
|
return;
|
|
406
651
|
}
|
|
407
652
|
me.__transactionEndCallback = complete;
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
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);
|
|
412
668
|
return;
|
|
413
669
|
}
|
|
414
670
|
const ev = /** @type {Event & {complete: () => void}} */ (
|
|
@@ -436,10 +692,13 @@ IDBTransaction.prototype.__createRequest = function (source) {
|
|
|
436
692
|
|
|
437
693
|
/**
|
|
438
694
|
* @typedef {(
|
|
439
|
-
* tx:
|
|
695
|
+
* tx: import('websql-configurable/lib/websql/WebSQLTransaction.js').default,
|
|
440
696
|
* args: ObjectArray,
|
|
441
697
|
* success: (result?: any, req?: import('./IDBRequest.js').IDBRequestFull) => void,
|
|
442
|
-
* error: (
|
|
698
|
+
* error: (
|
|
699
|
+
* tx: import('websql-configurable/lib/websql/WebSQLTransaction.js').default|Error|DOMException,
|
|
700
|
+
* err?: Error & {code?: number}
|
|
701
|
+
* ) => void,
|
|
443
702
|
* executeNextRequest?: () => void
|
|
444
703
|
* ) => void} SQLCallback
|
|
445
704
|
*/
|
|
@@ -491,10 +750,11 @@ IDBTransaction.prototype.__pushToQueue = function (request, callback, args) {
|
|
|
491
750
|
|
|
492
751
|
/**
|
|
493
752
|
* @throws {DOMException}
|
|
753
|
+
* @this {IDBTransactionFull}
|
|
494
754
|
* @returns {void}
|
|
495
755
|
*/
|
|
496
756
|
IDBTransaction.prototype.__assertActive = function () {
|
|
497
|
-
if (!this.__active) {
|
|
757
|
+
if (!this.__active || !this.__handlerActive || this.__committed) {
|
|
498
758
|
throw createDOMException('TransactionInactiveError', 'A request was placed against a transaction which is currently not active, or which is finished');
|
|
499
759
|
}
|
|
500
760
|
};
|
|
@@ -575,7 +835,7 @@ IDBTransaction.prototype.__abortTransaction = function (err) {
|
|
|
575
835
|
).forEach(function (store) {
|
|
576
836
|
// Store was already created so we restore to name before the rename
|
|
577
837
|
if ('__pendingName' in store &&
|
|
578
|
-
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
|
|
579
839
|
) {
|
|
580
840
|
store.__name = store.__originalName;
|
|
581
841
|
}
|
|
@@ -586,7 +846,7 @@ IDBTransaction.prototype.__abortTransaction = function (err) {
|
|
|
586
846
|
).forEach(function (index) {
|
|
587
847
|
// Index was already created so we restore to name before the rename
|
|
588
848
|
if ('__pendingName' in index &&
|
|
589
|
-
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
|
|
590
850
|
) {
|
|
591
851
|
index.__name = index.__originalName;
|
|
592
852
|
}
|
|
@@ -609,8 +869,8 @@ IDBTransaction.prototype.__abortTransaction = function (err) {
|
|
|
609
869
|
}
|
|
610
870
|
|
|
611
871
|
/**
|
|
612
|
-
* @param {
|
|
613
|
-
* @param {
|
|
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]
|
|
614
874
|
* @returns {void}
|
|
615
875
|
*/
|
|
616
876
|
function abort (tx, errOrResult) {
|
|
@@ -639,7 +899,7 @@ IDBTransaction.prototype.__abortTransaction = function (err) {
|
|
|
639
899
|
q.req.__error = createDOMException('AbortError', 'A request was aborted (an unfinished request).');
|
|
640
900
|
const reqEvt = createEvent('error', q.req.__error, {bubbles: true, cancelable: true});
|
|
641
901
|
return new SyncPromise(
|
|
642
|
-
/** @type {() => void} */
|
|
902
|
+
/** @type {(resolve: (value?: any) => void) => void} */
|
|
643
903
|
(resolve) => {
|
|
644
904
|
setTimeout(() => {
|
|
645
905
|
if (!q.req) { // TS guard
|
|
@@ -683,7 +943,7 @@ IDBTransaction.prototype.__abortTransaction = function (err) {
|
|
|
683
943
|
'ROLLBACK',
|
|
684
944
|
[],
|
|
685
945
|
abort,
|
|
686
|
-
/** @type {
|
|
946
|
+
/** @type {import('websql-configurable/lib/websql/WebSQLTransaction.js').SqlErrorCallback} */ (abort)
|
|
687
947
|
); // Not working in some circumstances, even in Node
|
|
688
948
|
} catch (err) {
|
|
689
949
|
// Browser errs when transaction has ended and since it most likely already erred here,
|
|
@@ -707,14 +967,34 @@ IDBTransaction.prototype.abort = function () {
|
|
|
707
967
|
}
|
|
708
968
|
if (CFG.DEBUG) { console.log('The transaction was aborted', me); }
|
|
709
969
|
IDBTransaction.__assertNotFinished(me);
|
|
970
|
+
if (me.__committed) {
|
|
971
|
+
throw createDOMException('InvalidStateError', 'The transaction has already been committed');
|
|
972
|
+
}
|
|
710
973
|
me.__abortTransaction(null);
|
|
711
974
|
};
|
|
712
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
|
+
|
|
713
993
|
IDBTransaction.prototype[Symbol.toStringTag] = 'IDBTransactionPrototype';
|
|
714
994
|
|
|
715
995
|
/**
|
|
716
996
|
*
|
|
717
|
-
* @param {IDBTransactionFull|undefined} tx
|
|
997
|
+
* @param {IDBTransactionFull|null|undefined} tx
|
|
718
998
|
* @returns {void}
|
|
719
999
|
*/
|
|
720
1000
|
IDBTransaction.__assertVersionChange = function (tx) {
|
|
@@ -724,7 +1004,7 @@ IDBTransaction.__assertVersionChange = function (tx) {
|
|
|
724
1004
|
};
|
|
725
1005
|
/**
|
|
726
1006
|
*
|
|
727
|
-
* @param {IDBTransactionFull} tx
|
|
1007
|
+
* @param {IDBTransactionFull|null} tx
|
|
728
1008
|
* @throws {DOMException}
|
|
729
1009
|
* @returns {void}
|
|
730
1010
|
*/
|
|
@@ -736,7 +1016,7 @@ IDBTransaction.__assertNotVersionChange = function (tx) {
|
|
|
736
1016
|
|
|
737
1017
|
/**
|
|
738
1018
|
*
|
|
739
|
-
* @param {IDBTransactionFull|undefined} tx
|
|
1019
|
+
* @param {IDBTransactionFull|null|undefined} tx
|
|
740
1020
|
* @throws {DOMException}
|
|
741
1021
|
* @returns {void}
|
|
742
1022
|
*/
|
|
@@ -765,12 +1045,12 @@ IDBTransaction.__assertNotFinishedObjectStoreMethod = function (tx) {
|
|
|
765
1045
|
|
|
766
1046
|
/**
|
|
767
1047
|
*
|
|
768
|
-
* @param {IDBTransactionFull|undefined} tx
|
|
1048
|
+
* @param {IDBTransactionFull|null|undefined} tx
|
|
769
1049
|
* @throws {DOMException}
|
|
770
1050
|
* @returns {void}
|
|
771
1051
|
*/
|
|
772
1052
|
IDBTransaction.__assertActive = function (tx) {
|
|
773
|
-
if (!tx || !tx.__active) {
|
|
1053
|
+
if (!tx || !tx.__active || tx.__committed) {
|
|
774
1054
|
throw createDOMException('TransactionInactiveError', 'A request was placed against a transaction which is currently not active, or which is finished');
|
|
775
1055
|
}
|
|
776
1056
|
};
|
|
@@ -787,6 +1067,7 @@ IDBTransaction.prototype.__getParent = function () {
|
|
|
787
1067
|
/* eslint-disable unicorn/no-top-level-side-effects -- Would be good */
|
|
788
1068
|
util.defineOuterInterface(IDBTransaction.prototype, listeners);
|
|
789
1069
|
util.defineReadonlyOuterInterface(IDBTransaction.prototype, readonlyProperties);
|
|
1070
|
+
util.setOperationNames(IDBTransaction.prototype);
|
|
790
1071
|
|
|
791
1072
|
Object.defineProperty(IDBTransaction.prototype, 'constructor', {
|
|
792
1073
|
enumerable: false,
|