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.
- 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 +3058 -1167
- package/dist/indexeddbshim-UnicodeIdentifiers-node.cjs.map +1 -1
- package/dist/indexeddbshim-UnicodeIdentifiers.js +2627 -986
- 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 +3058 -1167
- package/dist/indexeddbshim-node.cjs.map +1 -1
- package/dist/indexeddbshim-noninvasive.js +2627 -986
- 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 +2627 -986
- 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 +27 -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 +104 -57
- 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 +335 -67
- 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,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
|
|
184
|
-
// `cordova-plugin-sqlite-2`, etc.) never invoke the
|
|
185
|
-
// callback that installs the real `__transFinishedCb`,
|
|
186
|
-
// it here would defer forever. Detect that via arity
|
|
187
|
-
//
|
|
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
|
|
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
|
|
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
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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(
|
|
316
|
+
util.runContinuationSafely(advanceAfterDispatch);
|
|
261
317
|
}
|
|
262
318
|
|
|
263
319
|
/**
|
|
264
|
-
* @param {[
|
|
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(
|
|
287
|
-
|
|
288
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
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 {
|
|
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 (
|
|
575
|
+
if (err) {
|
|
368
576
|
return true;
|
|
369
577
|
}
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
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
|
-
|
|
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
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
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:
|
|
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: (
|
|
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 {
|
|
626
|
-
* @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]
|
|
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 {
|
|
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,
|