ZeroOmega/omega-web/lib/fake-indexeddb/FDBTransaction.js

213 lines
6.3 KiB
JavaScript

import FDBObjectStore from "./FDBObjectStore.js";
import FDBRequest from "./FDBRequest.js";
import { AbortError, InvalidStateError, NotFoundError, TransactionInactiveError } from "./lib/errors.js";
import FakeDOMStringList from "./lib/FakeDOMStringList.js";
import FakeEvent from "./lib/FakeEvent.js";
import FakeEventTarget from "./lib/FakeEventTarget.js";
import { queueTask } from "./lib/scheduling.js";
// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#transaction
class FDBTransaction extends FakeEventTarget {
_state = "active";
_started = false;
_rollbackLog = [];
_objectStoresCache = new Map();
error = null;
onabort = null;
oncomplete = null;
onerror = null;
_requests = [];
constructor(storeNames, mode, db) {
super();
this._scope = new Set(storeNames);
this.mode = mode;
this.db = db;
this.objectStoreNames = new FakeDOMStringList(...Array.from(this._scope).sort());
}
// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#dfn-steps-for-aborting-a-transaction
_abort(errName) {
for (const f of this._rollbackLog.reverse()) {
f();
}
if (errName !== null) {
const e = new DOMException(undefined, errName);
this.error = e;
}
// Should this directly remove from _requests?
for (const {
request
} of this._requests) {
if (request.readyState !== "done") {
request.readyState = "done"; // This will cancel execution of this request's operation
if (request.source) {
request.result = undefined;
request.error = new AbortError();
const event = new FakeEvent("error", {
bubbles: true,
cancelable: true
});
event.eventPath = [this.db, this];
request.dispatchEvent(event);
}
}
}
queueTask(() => {
const event = new FakeEvent("abort", {
bubbles: true,
cancelable: false
});
event.eventPath = [this.db];
this.dispatchEvent(event);
});
this._state = "finished";
}
abort() {
if (this._state === "committing" || this._state === "finished") {
throw new InvalidStateError();
}
this._state = "active";
this._abort(null);
}
// http://w3c.github.io/IndexedDB/#dom-idbtransaction-objectstore
objectStore(name) {
if (this._state !== "active") {
throw new InvalidStateError();
}
const objectStore = this._objectStoresCache.get(name);
if (objectStore !== undefined) {
return objectStore;
}
const rawObjectStore = this.db._rawDatabase.rawObjectStores.get(name);
if (!this._scope.has(name) || rawObjectStore === undefined) {
throw new NotFoundError();
}
const objectStore2 = new FDBObjectStore(this, rawObjectStore);
this._objectStoresCache.set(name, objectStore2);
return objectStore2;
}
// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#dfn-steps-for-asynchronously-executing-a-request
_execRequestAsync(obj) {
const source = obj.source;
const operation = obj.operation;
let request = Object.hasOwn(obj, "request") ? obj.request : null;
if (this._state !== "active") {
throw new TransactionInactiveError();
}
// Request should only be passed for cursors
if (!request) {
if (!source) {
// Special requests like indexes that just need to run some code
request = new FDBRequest();
} else {
request = new FDBRequest();
request.source = source;
request.transaction = source.transaction;
}
}
this._requests.push({
operation,
request
});
return request;
}
_start() {
this._started = true;
// Remove from request queue - cursor ones will be added back if necessary by cursor.continue and such
let operation;
let request;
while (this._requests.length > 0) {
const r = this._requests.shift();
// This should only be false if transaction was aborted
if (r && r.request.readyState !== "done") {
request = r.request;
operation = r.operation;
break;
}
}
if (request && operation) {
if (!request.source) {
// Special requests like indexes that just need to run some code, with error handling already built into
// operation
operation();
} else {
let defaultAction;
let event;
try {
const result = operation();
request.readyState = "done";
request.result = result;
request.error = undefined;
// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#dfn-fire-a-success-event
if (this._state === "inactive") {
this._state = "active";
}
event = new FakeEvent("success", {
bubbles: false,
cancelable: false
});
} catch (err) {
request.readyState = "done";
request.result = undefined;
request.error = err;
// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#dfn-fire-an-error-event
if (this._state === "inactive") {
this._state = "active";
}
event = new FakeEvent("error", {
bubbles: true,
cancelable: true
});
defaultAction = this._abort.bind(this, err.name);
}
try {
event.eventPath = [this.db, this];
request.dispatchEvent(event);
} catch (err) {
if (this._state !== "committing") {
this._abort("AbortError");
}
throw err;
}
// Default action of event
if (!event.canceled) {
if (defaultAction) {
defaultAction();
}
}
}
// Give it another chance for new handlers to be set before finishing
queueTask(this._start.bind(this));
return;
}
// Check if transaction complete event needs to be fired
if (this._state !== "finished") {
// Either aborted or committed already
this._state = "finished";
if (!this.error) {
const event = new FakeEvent("complete");
this.dispatchEvent(event);
}
}
}
commit() {
if (this._state !== "active") {
throw new InvalidStateError();
}
this._state = "committing";
}
toString() {
return "[object IDBRequest]";
}
}
export default FDBTransaction;