2021-02-08 15:23:44 +01:00
|
|
|
/*
|
|
|
|
Copyright 2017 Jeremy Scheff
|
|
|
|
Copyright 2019-2021 Taler Systems S.A.
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
|
|
|
or implied. See the License for the specific language governing
|
|
|
|
permissions and limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
import {
|
|
|
|
Backend,
|
|
|
|
DatabaseConnection,
|
|
|
|
DatabaseTransaction,
|
|
|
|
RecordGetRequest,
|
|
|
|
RecordStoreRequest,
|
|
|
|
ResultLevel,
|
|
|
|
Schema,
|
|
|
|
StoreLevel,
|
|
|
|
} from "./backend-interface";
|
2021-02-08 19:59:19 +01:00
|
|
|
import {
|
|
|
|
DOMException,
|
|
|
|
DOMStringList,
|
|
|
|
EventListener,
|
|
|
|
IDBCursor,
|
|
|
|
IDBCursorDirection,
|
|
|
|
IDBDatabase,
|
|
|
|
IDBIndex,
|
|
|
|
IDBKeyPath,
|
|
|
|
IDBKeyRange,
|
|
|
|
IDBObjectStore,
|
|
|
|
IDBOpenDBRequest,
|
|
|
|
IDBRequest,
|
|
|
|
IDBTransaction,
|
|
|
|
IDBTransactionMode,
|
|
|
|
IDBValidKey,
|
|
|
|
} from "./idbtypes";
|
2021-02-16 11:34:50 +01:00
|
|
|
import { compareKeys } from "./util/cmp";
|
|
|
|
import { enforceRange } from "./util/enforceRange";
|
2021-02-08 15:23:44 +01:00
|
|
|
import {
|
|
|
|
AbortError,
|
|
|
|
ConstraintError,
|
|
|
|
DataError,
|
|
|
|
InvalidAccessError,
|
|
|
|
InvalidStateError,
|
|
|
|
NotFoundError,
|
|
|
|
ReadOnlyError,
|
|
|
|
TransactionInactiveError,
|
|
|
|
VersionError,
|
|
|
|
} from "./util/errors";
|
|
|
|
import { fakeDOMStringList } from "./util/fakeDOMStringList";
|
|
|
|
import FakeEvent from "./util/FakeEvent";
|
|
|
|
import FakeEventTarget from "./util/FakeEventTarget";
|
2021-02-08 19:59:19 +01:00
|
|
|
import { normalizeKeyPath } from "./util/normalizeKeyPath";
|
2021-02-16 11:34:50 +01:00
|
|
|
import { openPromise } from "./util/openPromise";
|
2021-02-08 15:23:44 +01:00
|
|
|
import queueTask from "./util/queueTask";
|
2021-02-16 10:31:55 +01:00
|
|
|
import {
|
|
|
|
structuredClone,
|
|
|
|
structuredEncapsulate,
|
|
|
|
structuredRevive,
|
|
|
|
} from "./util/structuredClone";
|
2021-02-16 11:34:50 +01:00
|
|
|
import { validateKeyPath } from "./util/validateKeyPath";
|
|
|
|
import { valueToKey } from "./util/valueToKey";
|
2021-02-08 15:23:44 +01:00
|
|
|
|
|
|
|
/** @public */
|
|
|
|
export type CursorSource = BridgeIDBIndex | BridgeIDBObjectStore;
|
|
|
|
|
|
|
|
/** @public */
|
|
|
|
export interface FakeDOMStringList extends Array<string> {
|
|
|
|
contains: (value: string) => boolean;
|
|
|
|
item: (i: number) => string | undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @public */
|
|
|
|
export interface RequestObj {
|
|
|
|
operation: () => Promise<any>;
|
|
|
|
request?: BridgeIDBRequest | undefined;
|
|
|
|
source?: any;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @public */
|
|
|
|
export interface BridgeIDBDatabaseInfo {
|
|
|
|
name: string;
|
|
|
|
version: number;
|
|
|
|
}
|
|
|
|
|
|
|
|
function simplifyRange(
|
|
|
|
r: IDBValidKey | IDBKeyRange | undefined | null,
|
|
|
|
): IDBKeyRange | null {
|
|
|
|
if (r && typeof r === "object" && "lower" in r) {
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
if (r === undefined || r === null) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
return BridgeIDBKeyRange.bound(r, r, false, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#cursor
|
|
|
|
*
|
|
|
|
* @public
|
|
|
|
*/
|
2021-02-08 19:59:19 +01:00
|
|
|
export class BridgeIDBCursor implements IDBCursor {
|
2021-02-08 15:23:44 +01:00
|
|
|
_request: BridgeIDBRequest | undefined;
|
|
|
|
|
|
|
|
private _gotValue: boolean = false;
|
|
|
|
private _range: IDBValidKey | IDBKeyRange | undefined | null;
|
|
|
|
private _indexPosition = undefined; // Key of previously returned record
|
|
|
|
private _objectStorePosition = undefined;
|
|
|
|
private _keyOnly: boolean;
|
|
|
|
|
|
|
|
private _source: CursorSource;
|
|
|
|
private _direction: IDBCursorDirection;
|
|
|
|
private _key: IDBValidKey | undefined = undefined;
|
|
|
|
private _primaryKey: IDBValidKey | undefined = undefined;
|
|
|
|
private _indexName: string | undefined;
|
|
|
|
private _objectStoreName: string;
|
|
|
|
|
|
|
|
protected _value: any = undefined;
|
|
|
|
|
|
|
|
constructor(
|
|
|
|
source: CursorSource,
|
|
|
|
objectStoreName: string,
|
|
|
|
indexName: string | undefined,
|
|
|
|
range: IDBValidKey | IDBKeyRange | null | undefined,
|
|
|
|
direction: IDBCursorDirection,
|
|
|
|
request: BridgeIDBRequest,
|
|
|
|
keyOnly: boolean,
|
|
|
|
) {
|
|
|
|
this._indexName = indexName;
|
|
|
|
this._objectStoreName = objectStoreName;
|
|
|
|
this._range = range;
|
|
|
|
this._source = source;
|
|
|
|
this._direction = direction;
|
|
|
|
this._request = request;
|
|
|
|
this._keyOnly = keyOnly;
|
|
|
|
}
|
|
|
|
|
|
|
|
get _effectiveObjectStore(): BridgeIDBObjectStore {
|
|
|
|
if (this.source instanceof BridgeIDBObjectStore) {
|
|
|
|
return this.source;
|
|
|
|
}
|
2021-02-08 19:59:19 +01:00
|
|
|
return this.source._objectStore;
|
2021-02-08 15:23:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
get _backend(): Backend {
|
|
|
|
return this._source._backend;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read only properties
|
|
|
|
get source() {
|
|
|
|
return this._source;
|
|
|
|
}
|
|
|
|
set source(val) {
|
|
|
|
/* For babel */
|
|
|
|
}
|
|
|
|
|
|
|
|
get direction() {
|
|
|
|
return this._direction;
|
|
|
|
}
|
|
|
|
set direction(val) {
|
|
|
|
/* For babel */
|
|
|
|
}
|
|
|
|
|
2021-02-08 19:59:19 +01:00
|
|
|
get key(): IDBValidKey {
|
|
|
|
const k = this._key;
|
|
|
|
if (k === null || k === undefined) {
|
|
|
|
throw Error("no key");
|
|
|
|
}
|
|
|
|
return k;
|
2021-02-08 15:23:44 +01:00
|
|
|
}
|
|
|
|
set key(val) {
|
|
|
|
/* For babel */
|
|
|
|
}
|
|
|
|
|
2021-02-08 19:59:19 +01:00
|
|
|
get primaryKey(): IDBValidKey {
|
|
|
|
const k = this._primaryKey;
|
|
|
|
if (k === 0 || k === undefined) {
|
|
|
|
throw Error("no key");
|
|
|
|
}
|
|
|
|
return k;
|
2021-02-08 15:23:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
set primaryKey(val) {
|
|
|
|
/* For babel */
|
|
|
|
}
|
|
|
|
|
|
|
|
protected get _isValueCursor(): boolean {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* https://w3c.github.io/IndexedDB/#iterate-a-cursor
|
|
|
|
*/
|
|
|
|
async _iterate(key?: IDBValidKey, primaryKey?: IDBValidKey): Promise<any> {
|
|
|
|
BridgeIDBFactory.enableTracing &&
|
|
|
|
console.log(
|
|
|
|
`iterating cursor os=${this._objectStoreName},idx=${this._indexName}`,
|
|
|
|
);
|
|
|
|
BridgeIDBFactory.enableTracing &&
|
|
|
|
console.log("cursor type ", this.toString());
|
|
|
|
const recordGetRequest: RecordGetRequest = {
|
|
|
|
direction: this.direction,
|
|
|
|
indexName: this._indexName,
|
|
|
|
lastIndexPosition: this._indexPosition,
|
|
|
|
lastObjectStorePosition: this._objectStorePosition,
|
|
|
|
limit: 1,
|
|
|
|
range: simplifyRange(this._range),
|
|
|
|
objectStoreName: this._objectStoreName,
|
|
|
|
advanceIndexKey: key,
|
|
|
|
advancePrimaryKey: primaryKey,
|
|
|
|
resultLevel: this._keyOnly ? ResultLevel.OnlyKeys : ResultLevel.Full,
|
|
|
|
};
|
|
|
|
|
|
|
|
const { btx } = this.source._confirmActiveTransaction();
|
|
|
|
|
|
|
|
let response = await this._backend.getRecords(btx, recordGetRequest);
|
|
|
|
|
|
|
|
if (response.count === 0) {
|
|
|
|
if (BridgeIDBFactory.enableTracing) {
|
|
|
|
console.log("cursor is returning empty result");
|
|
|
|
}
|
|
|
|
this._gotValue = false;
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (response.count !== 1) {
|
|
|
|
throw Error("invariant failed");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (BridgeIDBFactory.enableTracing) {
|
|
|
|
console.log("request is:", JSON.stringify(recordGetRequest));
|
|
|
|
console.log("get response is:", JSON.stringify(response));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this._indexName !== undefined) {
|
|
|
|
this._key = response.indexKeys![0];
|
|
|
|
} else {
|
|
|
|
this._key = response.primaryKeys![0];
|
|
|
|
}
|
|
|
|
|
|
|
|
this._primaryKey = response.primaryKeys![0];
|
|
|
|
|
|
|
|
if (!this._keyOnly) {
|
2021-02-08 19:59:19 +01:00
|
|
|
this._value = structuredRevive(response.values![0]);
|
2021-02-08 15:23:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
this._gotValue = true;
|
|
|
|
this._objectStorePosition = structuredClone(response.primaryKeys![0]);
|
|
|
|
if (response.indexKeys !== undefined && response.indexKeys.length > 0) {
|
|
|
|
this._indexPosition = structuredClone(response.indexKeys[0]);
|
|
|
|
}
|
|
|
|
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#widl-IDBCursor-update-IDBRequest-any-value
|
|
|
|
public update(value: any) {
|
|
|
|
if (value === undefined) {
|
|
|
|
throw new TypeError();
|
|
|
|
}
|
|
|
|
|
2021-02-08 19:59:19 +01:00
|
|
|
const transaction = this._effectiveObjectStore._transaction;
|
2021-02-08 15:23:44 +01:00
|
|
|
|
2021-02-16 10:31:55 +01:00
|
|
|
if (!transaction._active) {
|
2021-02-08 15:23:44 +01:00
|
|
|
throw new TransactionInactiveError();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (transaction.mode === "readonly") {
|
|
|
|
throw new ReadOnlyError();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this._effectiveObjectStore._deleted) {
|
|
|
|
throw new InvalidStateError();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (
|
|
|
|
!(this.source instanceof BridgeIDBObjectStore) &&
|
|
|
|
this.source._deleted
|
|
|
|
) {
|
|
|
|
throw new InvalidStateError();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!this._gotValue || !this._isValueCursor) {
|
|
|
|
throw new InvalidStateError();
|
|
|
|
}
|
|
|
|
|
|
|
|
const storeReq: RecordStoreRequest = {
|
|
|
|
key: this._primaryKey,
|
2021-02-08 19:59:19 +01:00
|
|
|
value: structuredEncapsulate(value),
|
2021-02-08 15:23:44 +01:00
|
|
|
objectStoreName: this._objectStoreName,
|
|
|
|
storeLevel: StoreLevel.UpdateExisting,
|
|
|
|
};
|
|
|
|
|
|
|
|
const operation = async () => {
|
|
|
|
if (BridgeIDBFactory.enableTracing) {
|
|
|
|
console.log("updating at cursor");
|
|
|
|
}
|
|
|
|
const { btx } = this.source._confirmActiveTransaction();
|
|
|
|
await this._backend.storeRecord(btx, storeReq);
|
|
|
|
};
|
|
|
|
return transaction._execRequestAsync({
|
|
|
|
operation,
|
|
|
|
source: this,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#widl-IDBCursor-advance-void-unsigned-long-count
|
|
|
|
*/
|
|
|
|
public advance(count: number) {
|
|
|
|
throw Error("not implemented");
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#widl-IDBCursor-continue-void-any-key
|
|
|
|
*/
|
|
|
|
public continue(key?: IDBValidKey) {
|
2021-02-08 19:59:19 +01:00
|
|
|
const transaction = this._effectiveObjectStore._transaction;
|
2021-02-08 15:23:44 +01:00
|
|
|
|
2021-02-16 10:31:55 +01:00
|
|
|
if (!transaction._active) {
|
2021-02-08 15:23:44 +01:00
|
|
|
throw new TransactionInactiveError();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this._effectiveObjectStore._deleted) {
|
|
|
|
throw new InvalidStateError();
|
|
|
|
}
|
|
|
|
if (
|
|
|
|
!(this.source instanceof BridgeIDBObjectStore) &&
|
|
|
|
this.source._deleted
|
|
|
|
) {
|
|
|
|
throw new InvalidStateError();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!this._gotValue) {
|
|
|
|
throw new InvalidStateError();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (key !== undefined) {
|
|
|
|
key = valueToKey(key);
|
|
|
|
let lastKey =
|
|
|
|
this._indexName === undefined
|
|
|
|
? this._objectStorePosition
|
|
|
|
: this._indexPosition;
|
|
|
|
|
|
|
|
const cmpResult = compareKeys(key, lastKey);
|
|
|
|
|
|
|
|
if (
|
|
|
|
(cmpResult <= 0 &&
|
|
|
|
(this.direction === "next" || this.direction === "nextunique")) ||
|
|
|
|
(cmpResult >= 0 &&
|
|
|
|
(this.direction === "prev" || this.direction === "prevunique"))
|
|
|
|
) {
|
|
|
|
throw new DataError();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this._request) {
|
|
|
|
this._request.readyState = "pending";
|
|
|
|
}
|
|
|
|
|
|
|
|
const operation = async () => {
|
|
|
|
return this._iterate(key);
|
|
|
|
};
|
|
|
|
|
|
|
|
transaction._execRequestAsync({
|
|
|
|
operation,
|
|
|
|
request: this._request,
|
|
|
|
source: this.source,
|
|
|
|
});
|
|
|
|
|
|
|
|
this._gotValue = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// https://w3c.github.io/IndexedDB/#dom-idbcursor-continueprimarykey
|
|
|
|
public continuePrimaryKey(key: IDBValidKey, primaryKey: IDBValidKey) {
|
|
|
|
throw Error("not implemented");
|
|
|
|
}
|
|
|
|
|
|
|
|
public delete() {
|
2021-02-08 19:59:19 +01:00
|
|
|
const transaction = this._effectiveObjectStore._transaction;
|
2021-02-08 15:23:44 +01:00
|
|
|
|
2021-02-16 10:31:55 +01:00
|
|
|
if (!transaction._active) {
|
2021-02-08 15:23:44 +01:00
|
|
|
throw new TransactionInactiveError();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (transaction.mode === "readonly") {
|
|
|
|
throw new ReadOnlyError();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this._effectiveObjectStore._deleted) {
|
|
|
|
throw new InvalidStateError();
|
|
|
|
}
|
|
|
|
if (
|
|
|
|
!(this.source instanceof BridgeIDBObjectStore) &&
|
|
|
|
this.source._deleted
|
|
|
|
) {
|
|
|
|
throw new InvalidStateError();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!this._gotValue || !this._isValueCursor) {
|
|
|
|
throw new InvalidStateError();
|
|
|
|
}
|
|
|
|
|
|
|
|
const operation = async () => {
|
|
|
|
const { btx } = this.source._confirmActiveTransaction();
|
|
|
|
this._backend.deleteRecord(
|
|
|
|
btx,
|
|
|
|
this._objectStoreName,
|
|
|
|
BridgeIDBKeyRange._valueToKeyRange(this._primaryKey),
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
return transaction._execRequestAsync({
|
|
|
|
operation,
|
|
|
|
source: this,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
public toString() {
|
|
|
|
return "[object IDBCursor]";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export class BridgeIDBCursorWithValue extends BridgeIDBCursor {
|
|
|
|
get value(): any {
|
|
|
|
return this._value;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected get _isValueCursor(): boolean {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
constructor(
|
|
|
|
source: CursorSource,
|
|
|
|
objectStoreName: string,
|
|
|
|
indexName: string | undefined,
|
|
|
|
range: IDBValidKey | IDBKeyRange | undefined | null,
|
|
|
|
direction: IDBCursorDirection,
|
|
|
|
request?: any,
|
|
|
|
) {
|
|
|
|
super(source, objectStoreName, indexName, range, direction, request, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
public toString() {
|
|
|
|
return "[object IDBCursorWithValue]";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Ensure that an active version change transaction is currently running.
|
|
|
|
*/
|
|
|
|
const confirmActiveVersionchangeTransaction = (database: BridgeIDBDatabase) => {
|
2021-02-16 10:31:55 +01:00
|
|
|
if (!database._upgradeTransaction) {
|
2021-02-08 15:23:44 +01:00
|
|
|
throw new InvalidStateError();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Find the latest versionchange transaction
|
|
|
|
const transactions = database._transactions.filter(
|
|
|
|
(tx: BridgeIDBTransaction) => {
|
|
|
|
return tx.mode === "versionchange";
|
|
|
|
},
|
|
|
|
);
|
|
|
|
const transaction = transactions[transactions.length - 1];
|
|
|
|
|
2021-02-16 10:31:55 +01:00
|
|
|
if (!transaction || transaction._finished) {
|
2021-02-08 15:23:44 +01:00
|
|
|
throw new InvalidStateError();
|
|
|
|
}
|
|
|
|
|
2021-02-16 10:31:55 +01:00
|
|
|
if (!transaction._active) {
|
2021-02-08 15:23:44 +01:00
|
|
|
throw new TransactionInactiveError();
|
|
|
|
}
|
|
|
|
|
|
|
|
return transaction;
|
|
|
|
};
|
|
|
|
|
|
|
|
// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#database-interface
|
|
|
|
/** @public */
|
2021-02-16 10:31:55 +01:00
|
|
|
export class BridgeIDBDatabase extends FakeEventTarget implements IDBDatabase {
|
2021-02-08 15:23:44 +01:00
|
|
|
_closePending = false;
|
|
|
|
_closed = false;
|
|
|
|
_transactions: Array<BridgeIDBTransaction> = [];
|
|
|
|
|
2021-02-16 10:31:55 +01:00
|
|
|
_upgradeTransaction: BridgeIDBTransaction | null = null;
|
|
|
|
|
2021-02-08 15:23:44 +01:00
|
|
|
_backendConnection: DatabaseConnection;
|
|
|
|
_backend: Backend;
|
|
|
|
|
|
|
|
_schema: Schema;
|
|
|
|
|
|
|
|
get name(): string {
|
|
|
|
return this._schema.databaseName;
|
|
|
|
}
|
|
|
|
|
|
|
|
get version(): number {
|
|
|
|
return this._schema.databaseVersion;
|
|
|
|
}
|
|
|
|
|
2021-02-16 10:31:55 +01:00
|
|
|
get objectStoreNames(): DOMStringList {
|
|
|
|
return fakeDOMStringList(
|
|
|
|
Object.keys(this._schema.objectStores),
|
|
|
|
).sort() as DOMStringList;
|
2021-02-08 15:23:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#database-closing-steps
|
|
|
|
*/
|
|
|
|
_closeConnection() {
|
|
|
|
this._closePending = true;
|
|
|
|
|
2021-02-16 10:31:55 +01:00
|
|
|
// Spec is unclear what "complete" means, we assume it's
|
|
|
|
// the same as "finished".
|
2021-02-08 15:23:44 +01:00
|
|
|
const transactionsComplete = this._transactions.every(
|
|
|
|
(transaction: BridgeIDBTransaction) => {
|
2021-02-16 10:31:55 +01:00
|
|
|
return transaction._finished;
|
2021-02-08 15:23:44 +01:00
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
if (transactionsComplete) {
|
|
|
|
this._closed = true;
|
|
|
|
this._backend.close(this._backendConnection);
|
|
|
|
} else {
|
|
|
|
queueTask(() => {
|
|
|
|
this._closeConnection();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-16 10:31:55 +01:00
|
|
|
/**
|
|
|
|
* Refresh the schema by querying it from the backend.
|
|
|
|
*/
|
|
|
|
_refreshSchema() {
|
|
|
|
this._schema = this._backend.getSchema(this._backendConnection);
|
|
|
|
}
|
|
|
|
|
2021-02-08 15:23:44 +01:00
|
|
|
constructor(backend: Backend, backendConnection: DatabaseConnection) {
|
|
|
|
super();
|
|
|
|
|
|
|
|
this._schema = backend.getSchema(backendConnection);
|
|
|
|
|
|
|
|
this._backend = backend;
|
|
|
|
this._backendConnection = backendConnection;
|
|
|
|
}
|
|
|
|
|
|
|
|
// http://w3c.github.io/IndexedDB/#dom-idbdatabase-createobjectstore
|
|
|
|
public createObjectStore(
|
|
|
|
name: string,
|
2021-02-16 10:31:55 +01:00
|
|
|
options: {
|
|
|
|
autoIncrement?: boolean;
|
|
|
|
keyPath?: null | IDBKeyPath | IDBKeyPath[];
|
|
|
|
} | null = {},
|
2021-02-08 15:23:44 +01:00
|
|
|
): BridgeIDBObjectStore {
|
|
|
|
if (name === undefined) {
|
|
|
|
throw new TypeError();
|
|
|
|
}
|
|
|
|
const transaction = confirmActiveVersionchangeTransaction(this);
|
|
|
|
const backendTx = transaction._backendTransaction;
|
|
|
|
if (!backendTx) {
|
|
|
|
throw Error("invariant violated");
|
|
|
|
}
|
|
|
|
|
|
|
|
const keyPath =
|
|
|
|
options !== null && options.keyPath !== undefined
|
|
|
|
? options.keyPath
|
|
|
|
: null;
|
|
|
|
const autoIncrement =
|
|
|
|
options !== null && options.autoIncrement !== undefined
|
|
|
|
? options.autoIncrement
|
|
|
|
: false;
|
|
|
|
|
|
|
|
if (keyPath !== null) {
|
|
|
|
validateKeyPath(keyPath);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Object.keys(this._schema.objectStores).includes(name)) {
|
|
|
|
throw new ConstraintError();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (autoIncrement && (keyPath === "" || Array.isArray(keyPath))) {
|
|
|
|
throw new InvalidAccessError();
|
|
|
|
}
|
|
|
|
|
|
|
|
transaction._backend.createObjectStore(
|
|
|
|
backendTx,
|
|
|
|
name,
|
2021-02-16 10:31:55 +01:00
|
|
|
keyPath !== null ? normalizeKeyPath(keyPath) : null,
|
2021-02-08 15:23:44 +01:00
|
|
|
autoIncrement,
|
|
|
|
);
|
|
|
|
|
|
|
|
this._schema = this._backend.getSchema(this._backendConnection);
|
|
|
|
|
|
|
|
return transaction.objectStore(name);
|
|
|
|
}
|
|
|
|
|
|
|
|
public deleteObjectStore(name: string): void {
|
|
|
|
if (name === undefined) {
|
|
|
|
throw new TypeError();
|
|
|
|
}
|
|
|
|
const transaction = confirmActiveVersionchangeTransaction(this);
|
|
|
|
transaction._objectStoresCache.delete(name);
|
|
|
|
}
|
|
|
|
|
|
|
|
public _internalTransaction(
|
|
|
|
storeNames: string | string[],
|
|
|
|
mode?: IDBTransactionMode,
|
|
|
|
backendTransaction?: DatabaseTransaction,
|
2021-02-16 10:31:55 +01:00
|
|
|
openRequest?: BridgeIDBOpenDBRequest,
|
2021-02-08 15:23:44 +01:00
|
|
|
): BridgeIDBTransaction {
|
|
|
|
mode = mode !== undefined ? mode : "readonly";
|
|
|
|
if (
|
|
|
|
mode !== "readonly" &&
|
|
|
|
mode !== "readwrite" &&
|
|
|
|
mode !== "versionchange"
|
|
|
|
) {
|
|
|
|
throw new TypeError("Invalid mode: " + mode);
|
|
|
|
}
|
|
|
|
|
2021-02-16 10:31:55 +01:00
|
|
|
if (this._upgradeTransaction) {
|
2021-02-08 15:23:44 +01:00
|
|
|
throw new InvalidStateError();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this._closePending) {
|
|
|
|
throw new InvalidStateError();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!Array.isArray(storeNames)) {
|
|
|
|
storeNames = [storeNames];
|
|
|
|
}
|
|
|
|
if (storeNames.length === 0 && mode !== "versionchange") {
|
|
|
|
throw new InvalidAccessError();
|
|
|
|
}
|
|
|
|
for (const storeName of storeNames) {
|
2021-02-16 10:31:55 +01:00
|
|
|
if (!this.objectStoreNames.contains(storeName)) {
|
2021-02-08 15:23:44 +01:00
|
|
|
throw new NotFoundError(
|
|
|
|
"No objectStore named " + storeName + " in this database",
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const tx = new BridgeIDBTransaction(
|
|
|
|
storeNames,
|
|
|
|
mode,
|
|
|
|
this,
|
|
|
|
backendTransaction,
|
2021-02-16 10:31:55 +01:00
|
|
|
openRequest,
|
2021-02-08 15:23:44 +01:00
|
|
|
);
|
|
|
|
this._transactions.push(tx);
|
|
|
|
queueTask(() => tx._start());
|
2021-02-16 10:31:55 +01:00
|
|
|
// "When a transaction is created its active flag is initially set."
|
|
|
|
tx._active = true;
|
2021-02-08 15:23:44 +01:00
|
|
|
return tx;
|
|
|
|
}
|
|
|
|
|
|
|
|
public transaction(
|
|
|
|
storeNames: string | string[],
|
|
|
|
mode?: IDBTransactionMode,
|
|
|
|
): BridgeIDBTransaction {
|
|
|
|
if (mode === "versionchange") {
|
|
|
|
throw new TypeError("Invalid mode: " + mode);
|
|
|
|
}
|
|
|
|
return this._internalTransaction(storeNames, mode);
|
|
|
|
}
|
|
|
|
|
|
|
|
public close() {
|
|
|
|
this._closeConnection();
|
|
|
|
}
|
|
|
|
|
|
|
|
public toString() {
|
|
|
|
return "[object IDBDatabase]";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @public */
|
|
|
|
export type DatabaseList = Array<{ name: string; version: number }>;
|
|
|
|
|
|
|
|
/** @public */
|
|
|
|
export class BridgeIDBFactory {
|
|
|
|
public cmp = compareKeys;
|
|
|
|
private backend: Backend;
|
|
|
|
private connections: BridgeIDBDatabase[] = [];
|
|
|
|
static enableTracing: boolean = false;
|
|
|
|
|
|
|
|
public constructor(backend: Backend) {
|
|
|
|
this.backend = backend;
|
|
|
|
}
|
|
|
|
|
|
|
|
// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#widl-IDBFactory-deleteDatabase-IDBOpenDBRequest-DOMString-name
|
|
|
|
public deleteDatabase(name: string): BridgeIDBOpenDBRequest {
|
|
|
|
const request = new BridgeIDBOpenDBRequest();
|
2021-02-08 19:59:19 +01:00
|
|
|
request._source = null;
|
2021-02-08 15:23:44 +01:00
|
|
|
|
|
|
|
queueTask(async () => {
|
|
|
|
const databases = await this.backend.getDatabases();
|
|
|
|
const dbInfo = databases.find((x) => x.name == name);
|
|
|
|
if (!dbInfo) {
|
|
|
|
// Database already doesn't exist, success!
|
|
|
|
const event = new BridgeIDBVersionChangeEvent("success", {
|
|
|
|
newVersion: null,
|
|
|
|
oldVersion: 0,
|
|
|
|
});
|
|
|
|
request.dispatchEvent(event);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const oldVersion = dbInfo.version;
|
|
|
|
|
|
|
|
try {
|
|
|
|
const dbconn = await this.backend.connectDatabase(name);
|
|
|
|
const backendTransaction = await this.backend.enterVersionChange(
|
|
|
|
dbconn,
|
|
|
|
0,
|
|
|
|
);
|
|
|
|
await this.backend.deleteDatabase(backendTransaction, name);
|
|
|
|
await this.backend.commit(backendTransaction);
|
|
|
|
await this.backend.close(dbconn);
|
|
|
|
|
|
|
|
request.result = undefined;
|
|
|
|
request.readyState = "done";
|
|
|
|
|
|
|
|
const event2 = new BridgeIDBVersionChangeEvent("success", {
|
|
|
|
newVersion: null,
|
|
|
|
oldVersion,
|
|
|
|
});
|
|
|
|
request.dispatchEvent(event2);
|
|
|
|
} catch (err) {
|
|
|
|
request.error = new Error();
|
|
|
|
request.error.name = err.name;
|
|
|
|
request.readyState = "done";
|
|
|
|
|
|
|
|
const event = new FakeEvent("error", {
|
|
|
|
bubbles: true,
|
|
|
|
cancelable: true,
|
|
|
|
});
|
|
|
|
event.eventPath = [];
|
|
|
|
request.dispatchEvent(event);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return request;
|
|
|
|
}
|
|
|
|
|
|
|
|
// tslint:disable-next-line max-line-length
|
|
|
|
// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#widl-IDBFactory-open-IDBOpenDBRequest-DOMString-name-unsigned-long-long-version
|
2021-02-08 19:59:19 +01:00
|
|
|
public open(name: string, version?: number): BridgeIDBOpenDBRequest {
|
2021-02-08 15:23:44 +01:00
|
|
|
if (arguments.length > 1 && version !== undefined) {
|
|
|
|
// Based on spec, not sure why "MAX_SAFE_INTEGER" instead of "unsigned long long", but it's needed to pass
|
|
|
|
// tests
|
|
|
|
version = enforceRange(version, "MAX_SAFE_INTEGER");
|
|
|
|
}
|
|
|
|
if (version === 0) {
|
|
|
|
throw new TypeError();
|
|
|
|
}
|
|
|
|
|
|
|
|
const request = new BridgeIDBOpenDBRequest();
|
|
|
|
|
|
|
|
queueTask(async () => {
|
|
|
|
let dbconn: DatabaseConnection;
|
|
|
|
try {
|
|
|
|
dbconn = await this.backend.connectDatabase(name);
|
|
|
|
} catch (err) {
|
|
|
|
request._finishWithError(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const schema = this.backend.getSchema(dbconn);
|
|
|
|
const existingVersion = schema.databaseVersion;
|
|
|
|
|
|
|
|
if (version === undefined) {
|
|
|
|
version = existingVersion !== 0 ? existingVersion : 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
const requestedVersion = version;
|
|
|
|
|
|
|
|
BridgeIDBFactory.enableTracing &&
|
|
|
|
console.log(
|
|
|
|
`TRACE: existing version ${existingVersion}, requested version ${requestedVersion}`,
|
|
|
|
);
|
|
|
|
|
|
|
|
if (existingVersion > requestedVersion) {
|
|
|
|
request._finishWithError(new VersionError());
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const db = new BridgeIDBDatabase(this.backend, dbconn);
|
|
|
|
|
|
|
|
if (existingVersion == requestedVersion) {
|
|
|
|
request.result = db;
|
|
|
|
request.readyState = "done";
|
|
|
|
|
|
|
|
const event2 = new FakeEvent("success", {
|
|
|
|
bubbles: false,
|
|
|
|
cancelable: false,
|
|
|
|
});
|
|
|
|
event2.eventPath = [request];
|
|
|
|
request.dispatchEvent(event2);
|
2021-02-08 19:59:19 +01:00
|
|
|
} else if (existingVersion < requestedVersion) {
|
2021-02-08 15:23:44 +01:00
|
|
|
// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#dfn-steps-for-running-a-versionchange-transaction
|
|
|
|
|
|
|
|
for (const otherConn of this.connections) {
|
|
|
|
const event = new BridgeIDBVersionChangeEvent("versionchange", {
|
|
|
|
newVersion: version,
|
|
|
|
oldVersion: existingVersion,
|
|
|
|
});
|
|
|
|
otherConn.dispatchEvent(event);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this._anyOpen()) {
|
|
|
|
const event = new BridgeIDBVersionChangeEvent("blocked", {
|
|
|
|
newVersion: version,
|
|
|
|
oldVersion: existingVersion,
|
|
|
|
});
|
|
|
|
request.dispatchEvent(event);
|
|
|
|
}
|
|
|
|
|
|
|
|
const backendTransaction = await this.backend.enterVersionChange(
|
|
|
|
dbconn,
|
|
|
|
requestedVersion,
|
|
|
|
);
|
|
|
|
|
|
|
|
const transaction = db._internalTransaction(
|
|
|
|
[],
|
|
|
|
"versionchange",
|
|
|
|
backendTransaction,
|
2021-02-16 10:31:55 +01:00
|
|
|
request,
|
2021-02-08 15:23:44 +01:00
|
|
|
);
|
2021-02-16 10:31:55 +01:00
|
|
|
|
|
|
|
db._upgradeTransaction = transaction;
|
|
|
|
|
2021-02-08 15:23:44 +01:00
|
|
|
const event = new BridgeIDBVersionChangeEvent("upgradeneeded", {
|
|
|
|
newVersion: version,
|
|
|
|
oldVersion: existingVersion,
|
|
|
|
});
|
|
|
|
|
2021-02-16 10:31:55 +01:00
|
|
|
transaction._active = true;
|
|
|
|
|
2021-02-08 15:23:44 +01:00
|
|
|
request.readyState = "done";
|
2021-02-16 10:31:55 +01:00
|
|
|
request.result = db;
|
2021-02-08 15:23:44 +01:00
|
|
|
request.transaction = transaction;
|
|
|
|
request.dispatchEvent(event);
|
|
|
|
|
2021-02-08 19:59:19 +01:00
|
|
|
console.log("awaiting until initial transaction is done");
|
2021-02-08 15:23:44 +01:00
|
|
|
await transaction._waitDone();
|
2021-02-08 19:59:19 +01:00
|
|
|
console.log("initial transaction is done");
|
2021-02-08 15:23:44 +01:00
|
|
|
|
|
|
|
// We don't explicitly exit the versionchange transaction,
|
|
|
|
// since this is already done by the BridgeIDBTransaction.
|
2021-02-16 10:31:55 +01:00
|
|
|
db._upgradeTransaction = null;
|
2021-02-08 15:23:44 +01:00
|
|
|
|
2021-02-16 10:31:55 +01:00
|
|
|
// We re-use the same transaction (as per spec) here.
|
|
|
|
transaction._active = true;
|
|
|
|
if (transaction._aborted) {
|
|
|
|
request.result = undefined;
|
|
|
|
request.error = new AbortError();
|
|
|
|
request.readyState = "done";
|
|
|
|
const event2 = new FakeEvent("error", {
|
|
|
|
bubbles: false,
|
|
|
|
cancelable: false,
|
|
|
|
});
|
|
|
|
event2.eventPath = [request];
|
|
|
|
request.dispatchEvent(event2);
|
|
|
|
} else {
|
|
|
|
console.log(`dispatching success event, _active=${transaction._active}`);
|
|
|
|
const event2 = new FakeEvent("success", {
|
|
|
|
bubbles: false,
|
|
|
|
cancelable: false,
|
|
|
|
});
|
|
|
|
event2.eventPath = [request];
|
2021-02-08 15:23:44 +01:00
|
|
|
|
2021-02-16 10:31:55 +01:00
|
|
|
request.dispatchEvent(event2);
|
|
|
|
}
|
2021-02-08 15:23:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
this.connections.push(db);
|
|
|
|
return db;
|
|
|
|
});
|
|
|
|
|
|
|
|
return request;
|
|
|
|
}
|
|
|
|
|
|
|
|
// https://w3c.github.io/IndexedDB/#dom-idbfactory-databases
|
|
|
|
public databases(): Promise<DatabaseList> {
|
|
|
|
return this.backend.getDatabases();
|
|
|
|
}
|
|
|
|
|
|
|
|
public toString(): string {
|
|
|
|
return "[object IDBFactory]";
|
|
|
|
}
|
|
|
|
|
|
|
|
private _anyOpen(): boolean {
|
|
|
|
return this.connections.some((c) => !c._closed && !c._closePending);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const confirmActiveTransaction = (
|
|
|
|
index: BridgeIDBIndex,
|
|
|
|
): BridgeIDBTransaction => {
|
2021-02-08 19:59:19 +01:00
|
|
|
if (index._deleted || index._objectStore._deleted) {
|
2021-02-08 15:23:44 +01:00
|
|
|
throw new InvalidStateError();
|
|
|
|
}
|
|
|
|
|
2021-02-16 10:31:55 +01:00
|
|
|
if (!index._objectStore._transaction._active) {
|
2021-02-08 15:23:44 +01:00
|
|
|
throw new TransactionInactiveError();
|
|
|
|
}
|
|
|
|
|
2021-02-08 19:59:19 +01:00
|
|
|
return index._objectStore._transaction;
|
2021-02-08 15:23:44 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#idl-def-IDBIndex
|
|
|
|
/** @public */
|
2021-02-08 19:59:19 +01:00
|
|
|
export class BridgeIDBIndex implements IDBIndex {
|
|
|
|
_objectStore: BridgeIDBObjectStore;
|
|
|
|
|
|
|
|
get objectStore(): IDBObjectStore {
|
|
|
|
return this._objectStore;
|
|
|
|
}
|
2021-02-08 15:23:44 +01:00
|
|
|
|
|
|
|
get _schema(): Schema {
|
2021-02-08 19:59:19 +01:00
|
|
|
return this._objectStore._transaction._db._schema;
|
2021-02-08 15:23:44 +01:00
|
|
|
}
|
|
|
|
|
2021-02-08 19:59:19 +01:00
|
|
|
get keyPath(): IDBKeyPath | IDBKeyPath[] {
|
|
|
|
return this._schema.objectStores[this._objectStore.name].indexes[this._name]
|
2021-02-08 15:23:44 +01:00
|
|
|
.keyPath;
|
|
|
|
}
|
|
|
|
|
|
|
|
get multiEntry(): boolean {
|
2021-02-08 19:59:19 +01:00
|
|
|
return this._schema.objectStores[this._objectStore.name].indexes[this._name]
|
2021-02-08 15:23:44 +01:00
|
|
|
.multiEntry;
|
|
|
|
}
|
|
|
|
|
|
|
|
get unique(): boolean {
|
2021-02-08 19:59:19 +01:00
|
|
|
return this._schema.objectStores[this._objectStore.name].indexes[this._name]
|
2021-02-08 15:23:44 +01:00
|
|
|
.unique;
|
|
|
|
}
|
|
|
|
|
|
|
|
get _backend(): Backend {
|
2021-02-08 19:59:19 +01:00
|
|
|
return this._objectStore._backend;
|
2021-02-08 15:23:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
_confirmActiveTransaction(): { btx: DatabaseTransaction } {
|
2021-02-08 19:59:19 +01:00
|
|
|
return this._objectStore._confirmActiveTransaction();
|
2021-02-08 15:23:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
private _name: string;
|
|
|
|
|
|
|
|
public _deleted: boolean = false;
|
|
|
|
|
|
|
|
constructor(objectStore: BridgeIDBObjectStore, name: string) {
|
|
|
|
this._name = name;
|
2021-02-08 19:59:19 +01:00
|
|
|
this._objectStore = objectStore;
|
2021-02-08 15:23:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
get name() {
|
|
|
|
return this._name;
|
|
|
|
}
|
|
|
|
|
|
|
|
// https://w3c.github.io/IndexedDB/#dom-idbindex-name
|
|
|
|
set name(name: any) {
|
2021-02-08 19:59:19 +01:00
|
|
|
const transaction = this._objectStore._transaction;
|
2021-02-08 15:23:44 +01:00
|
|
|
|
2021-02-16 10:31:55 +01:00
|
|
|
if (!transaction._db._upgradeTransaction) {
|
2021-02-08 15:23:44 +01:00
|
|
|
throw new InvalidStateError();
|
|
|
|
}
|
|
|
|
|
2021-02-16 10:31:55 +01:00
|
|
|
if (!transaction._active) {
|
2021-02-08 15:23:44 +01:00
|
|
|
throw new TransactionInactiveError();
|
|
|
|
}
|
|
|
|
|
|
|
|
const { btx } = this._confirmActiveTransaction();
|
|
|
|
|
|
|
|
const oldName = this._name;
|
|
|
|
const newName = String(name);
|
|
|
|
|
|
|
|
if (newName === oldName) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-02-08 19:59:19 +01:00
|
|
|
this._backend.renameIndex(btx, this._objectStore.name, oldName, newName);
|
2021-02-08 15:23:44 +01:00
|
|
|
|
2021-02-08 19:59:19 +01:00
|
|
|
if (this._objectStore._indexNames.indexOf(name) >= 0) {
|
2021-02-08 15:23:44 +01:00
|
|
|
throw new ConstraintError();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// tslint:disable-next-line max-line-length
|
|
|
|
// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#widl-IDBIndex-openCursor-IDBRequest-any-range-IDBCursorDirection-direction
|
|
|
|
public openCursor(
|
|
|
|
range?: BridgeIDBKeyRange | IDBValidKey | null | undefined,
|
|
|
|
direction: IDBCursorDirection = "next",
|
|
|
|
) {
|
|
|
|
confirmActiveTransaction(this);
|
|
|
|
|
|
|
|
if (range === null) {
|
|
|
|
range = undefined;
|
|
|
|
}
|
|
|
|
if (range !== undefined && !(range instanceof BridgeIDBKeyRange)) {
|
|
|
|
range = BridgeIDBKeyRange.only(valueToKey(range));
|
|
|
|
}
|
|
|
|
|
|
|
|
const request = new BridgeIDBRequest();
|
2021-02-08 19:59:19 +01:00
|
|
|
request._source = this;
|
|
|
|
request.transaction = this._objectStore._transaction;
|
2021-02-08 15:23:44 +01:00
|
|
|
|
|
|
|
const cursor = new BridgeIDBCursorWithValue(
|
|
|
|
this,
|
2021-02-08 19:59:19 +01:00
|
|
|
this._objectStore.name,
|
2021-02-08 15:23:44 +01:00
|
|
|
this._name,
|
|
|
|
range,
|
|
|
|
direction,
|
|
|
|
request,
|
|
|
|
);
|
|
|
|
|
|
|
|
const operation = async () => {
|
|
|
|
return cursor._iterate();
|
|
|
|
};
|
|
|
|
|
2021-02-08 19:59:19 +01:00
|
|
|
return this._objectStore._transaction._execRequestAsync({
|
2021-02-08 15:23:44 +01:00
|
|
|
operation,
|
|
|
|
request,
|
|
|
|
source: this,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// tslint:disable-next-line max-line-length
|
|
|
|
// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#widl-IDBIndex-openKeyCursor-IDBRequest-any-range-IDBCursorDirection-direction
|
|
|
|
public openKeyCursor(
|
|
|
|
range?: BridgeIDBKeyRange | IDBValidKey | null | undefined,
|
|
|
|
direction: IDBCursorDirection = "next",
|
|
|
|
) {
|
|
|
|
confirmActiveTransaction(this);
|
|
|
|
|
|
|
|
if (range === null) {
|
|
|
|
range = undefined;
|
|
|
|
}
|
|
|
|
if (range !== undefined && !(range instanceof BridgeIDBKeyRange)) {
|
|
|
|
range = BridgeIDBKeyRange.only(valueToKey(range));
|
|
|
|
}
|
|
|
|
|
|
|
|
const request = new BridgeIDBRequest();
|
2021-02-08 19:59:19 +01:00
|
|
|
request._source = this;
|
|
|
|
request.transaction = this._objectStore._transaction;
|
2021-02-08 15:23:44 +01:00
|
|
|
|
|
|
|
const cursor = new BridgeIDBCursor(
|
|
|
|
this,
|
2021-02-08 19:59:19 +01:00
|
|
|
this._objectStore.name,
|
2021-02-08 15:23:44 +01:00
|
|
|
this._name,
|
|
|
|
range,
|
|
|
|
direction,
|
|
|
|
request,
|
|
|
|
true,
|
|
|
|
);
|
|
|
|
|
2021-02-08 19:59:19 +01:00
|
|
|
return this._objectStore._transaction._execRequestAsync({
|
2021-02-08 15:23:44 +01:00
|
|
|
operation: cursor._iterate.bind(cursor),
|
|
|
|
request,
|
|
|
|
source: this,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
public get(key: BridgeIDBKeyRange | IDBValidKey) {
|
|
|
|
confirmActiveTransaction(this);
|
|
|
|
|
|
|
|
if (!(key instanceof BridgeIDBKeyRange)) {
|
|
|
|
key = BridgeIDBKeyRange._valueToKeyRange(key);
|
|
|
|
}
|
|
|
|
|
|
|
|
const getReq: RecordGetRequest = {
|
|
|
|
direction: "next",
|
|
|
|
indexName: this._name,
|
|
|
|
limit: 1,
|
|
|
|
range: key,
|
2021-02-08 19:59:19 +01:00
|
|
|
objectStoreName: this._objectStore._name,
|
2021-02-08 15:23:44 +01:00
|
|
|
resultLevel: ResultLevel.Full,
|
|
|
|
};
|
|
|
|
|
|
|
|
const operation = async () => {
|
|
|
|
const { btx } = this._confirmActiveTransaction();
|
|
|
|
const result = await this._backend.getRecords(btx, getReq);
|
|
|
|
if (result.count == 0) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
const values = result.values;
|
|
|
|
if (!values) {
|
|
|
|
throw Error("invariant violated");
|
|
|
|
}
|
2021-02-08 19:59:19 +01:00
|
|
|
return structuredRevive(values[0]);
|
2021-02-08 15:23:44 +01:00
|
|
|
};
|
|
|
|
|
2021-02-08 19:59:19 +01:00
|
|
|
return this._objectStore._transaction._execRequestAsync({
|
2021-02-08 15:23:44 +01:00
|
|
|
operation,
|
|
|
|
source: this,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// http://w3c.github.io/IndexedDB/#dom-idbindex-getall
|
2021-02-08 19:59:19 +01:00
|
|
|
public getAll(
|
|
|
|
query?: BridgeIDBKeyRange | IDBValidKey,
|
|
|
|
count?: number,
|
|
|
|
): IDBRequest<any[]> {
|
2021-02-08 15:23:44 +01:00
|
|
|
throw Error("not implemented");
|
|
|
|
}
|
|
|
|
|
|
|
|
// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#widl-IDBIndex-getKey-IDBRequest-any-key
|
|
|
|
public getKey(key: BridgeIDBKeyRange | IDBValidKey) {
|
|
|
|
confirmActiveTransaction(this);
|
|
|
|
|
|
|
|
if (!(key instanceof BridgeIDBKeyRange)) {
|
|
|
|
key = BridgeIDBKeyRange._valueToKeyRange(key);
|
|
|
|
}
|
|
|
|
|
|
|
|
const getReq: RecordGetRequest = {
|
|
|
|
direction: "next",
|
|
|
|
indexName: this._name,
|
|
|
|
limit: 1,
|
|
|
|
range: key,
|
2021-02-08 19:59:19 +01:00
|
|
|
objectStoreName: this._objectStore._name,
|
2021-02-08 15:23:44 +01:00
|
|
|
resultLevel: ResultLevel.OnlyKeys,
|
|
|
|
};
|
|
|
|
|
|
|
|
const operation = async () => {
|
|
|
|
const { btx } = this._confirmActiveTransaction();
|
|
|
|
const result = await this._backend.getRecords(btx, getReq);
|
|
|
|
if (result.count == 0) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
const primaryKeys = result.primaryKeys;
|
|
|
|
if (!primaryKeys) {
|
|
|
|
throw Error("invariant violated");
|
|
|
|
}
|
|
|
|
return primaryKeys[0];
|
|
|
|
};
|
|
|
|
|
2021-02-08 19:59:19 +01:00
|
|
|
return this._objectStore._transaction._execRequestAsync({
|
2021-02-08 15:23:44 +01:00
|
|
|
operation,
|
|
|
|
source: this,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// http://w3c.github.io/IndexedDB/#dom-idbindex-getallkeys
|
2021-02-08 19:59:19 +01:00
|
|
|
public getAllKeys(
|
|
|
|
query?: BridgeIDBKeyRange | IDBValidKey,
|
|
|
|
count?: number,
|
|
|
|
): IDBRequest<IDBValidKey[]> {
|
2021-02-08 15:23:44 +01:00
|
|
|
throw Error("not implemented");
|
|
|
|
}
|
|
|
|
|
|
|
|
// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#widl-IDBIndex-count-IDBRequest-any-key
|
|
|
|
public count(key: BridgeIDBKeyRange | IDBValidKey | null | undefined) {
|
|
|
|
confirmActiveTransaction(this);
|
|
|
|
|
|
|
|
if (key === null) {
|
|
|
|
key = undefined;
|
|
|
|
}
|
|
|
|
if (key !== undefined && !(key instanceof BridgeIDBKeyRange)) {
|
|
|
|
key = BridgeIDBKeyRange.only(valueToKey(key));
|
|
|
|
}
|
|
|
|
|
|
|
|
const getReq: RecordGetRequest = {
|
|
|
|
direction: "next",
|
|
|
|
indexName: this._name,
|
|
|
|
limit: 1,
|
|
|
|
range: key,
|
2021-02-08 19:59:19 +01:00
|
|
|
objectStoreName: this._objectStore._name,
|
2021-02-08 15:23:44 +01:00
|
|
|
resultLevel: ResultLevel.OnlyCount,
|
|
|
|
};
|
|
|
|
|
|
|
|
const operation = async () => {
|
|
|
|
const { btx } = this._confirmActiveTransaction();
|
|
|
|
const result = await this._backend.getRecords(btx, getReq);
|
|
|
|
return result.count;
|
|
|
|
};
|
|
|
|
|
2021-02-08 19:59:19 +01:00
|
|
|
return this._objectStore._transaction._execRequestAsync({
|
2021-02-08 15:23:44 +01:00
|
|
|
operation,
|
|
|
|
source: this,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
public toString() {
|
|
|
|
return "[object IDBIndex]";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#range-concept
|
|
|
|
/** @public */
|
|
|
|
export class BridgeIDBKeyRange {
|
|
|
|
public static only(value: IDBValidKey) {
|
|
|
|
if (arguments.length === 0) {
|
|
|
|
throw new TypeError();
|
|
|
|
}
|
|
|
|
value = valueToKey(value);
|
|
|
|
return new BridgeIDBKeyRange(value, value, false, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
static lowerBound(lower: IDBValidKey, open: boolean = false) {
|
|
|
|
if (arguments.length === 0) {
|
|
|
|
throw new TypeError();
|
|
|
|
}
|
|
|
|
lower = valueToKey(lower);
|
|
|
|
return new BridgeIDBKeyRange(lower, undefined, open, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
static upperBound(upper: IDBValidKey, open: boolean = false) {
|
|
|
|
if (arguments.length === 0) {
|
|
|
|
throw new TypeError();
|
|
|
|
}
|
|
|
|
upper = valueToKey(upper);
|
|
|
|
return new BridgeIDBKeyRange(undefined, upper, true, open);
|
|
|
|
}
|
|
|
|
|
|
|
|
static bound(
|
|
|
|
lower: IDBValidKey,
|
|
|
|
upper: IDBValidKey,
|
|
|
|
lowerOpen: boolean = false,
|
|
|
|
upperOpen: boolean = false,
|
|
|
|
) {
|
|
|
|
if (arguments.length < 2) {
|
|
|
|
throw new TypeError();
|
|
|
|
}
|
|
|
|
|
|
|
|
const cmpResult = compareKeys(lower, upper);
|
|
|
|
if (cmpResult === 1 || (cmpResult === 0 && (lowerOpen || upperOpen))) {
|
|
|
|
throw new DataError();
|
|
|
|
}
|
|
|
|
|
|
|
|
lower = valueToKey(lower);
|
|
|
|
upper = valueToKey(upper);
|
|
|
|
return new BridgeIDBKeyRange(lower, upper, lowerOpen, upperOpen);
|
|
|
|
}
|
|
|
|
|
|
|
|
readonly lower: IDBValidKey | undefined;
|
|
|
|
readonly upper: IDBValidKey | undefined;
|
|
|
|
readonly lowerOpen: boolean;
|
|
|
|
readonly upperOpen: boolean;
|
|
|
|
|
|
|
|
constructor(
|
|
|
|
lower: IDBValidKey | undefined,
|
|
|
|
upper: IDBValidKey | undefined,
|
|
|
|
lowerOpen: boolean,
|
|
|
|
upperOpen: boolean,
|
|
|
|
) {
|
|
|
|
this.lower = lower;
|
|
|
|
this.upper = upper;
|
|
|
|
this.lowerOpen = lowerOpen;
|
|
|
|
this.upperOpen = upperOpen;
|
|
|
|
}
|
|
|
|
|
|
|
|
// https://w3c.github.io/IndexedDB/#dom-idbkeyrange-includes
|
|
|
|
includes(key: IDBValidKey) {
|
|
|
|
if (arguments.length === 0) {
|
|
|
|
throw new TypeError();
|
|
|
|
}
|
|
|
|
key = valueToKey(key);
|
|
|
|
|
|
|
|
if (this.lower !== undefined) {
|
|
|
|
const cmpResult = compareKeys(this.lower, key);
|
|
|
|
|
|
|
|
if (cmpResult === 1 || (cmpResult === 0 && this.lowerOpen)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (this.upper !== undefined) {
|
|
|
|
const cmpResult = compareKeys(this.upper, key);
|
|
|
|
|
|
|
|
if (cmpResult === -1 || (cmpResult === 0 && this.upperOpen)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
toString() {
|
|
|
|
return "[object IDBKeyRange]";
|
|
|
|
}
|
|
|
|
|
|
|
|
static _valueToKeyRange(value: any, nullDisallowedFlag: boolean = false) {
|
|
|
|
if (value instanceof BridgeIDBKeyRange) {
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (value === null || value === undefined) {
|
|
|
|
if (nullDisallowedFlag) {
|
|
|
|
throw new DataError();
|
|
|
|
}
|
|
|
|
return new BridgeIDBKeyRange(undefined, undefined, false, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
const key = valueToKey(value);
|
|
|
|
|
|
|
|
return BridgeIDBKeyRange.only(key);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#object-store
|
|
|
|
/** @public */
|
2021-02-08 19:59:19 +01:00
|
|
|
export class BridgeIDBObjectStore implements IDBObjectStore {
|
2021-02-08 15:23:44 +01:00
|
|
|
_indexesCache: Map<string, BridgeIDBIndex> = new Map();
|
|
|
|
|
2021-02-08 19:59:19 +01:00
|
|
|
_transaction: BridgeIDBTransaction;
|
|
|
|
|
|
|
|
get transaction(): IDBTransaction {
|
|
|
|
return this._transaction;
|
|
|
|
}
|
2021-02-08 15:23:44 +01:00
|
|
|
|
|
|
|
get autoIncrement(): boolean {
|
|
|
|
return this._schema.objectStores[this._name].autoIncrement;
|
|
|
|
}
|
|
|
|
|
2021-02-08 19:59:19 +01:00
|
|
|
get _indexNames(): FakeDOMStringList {
|
2021-02-08 15:23:44 +01:00
|
|
|
return fakeDOMStringList(
|
|
|
|
Object.keys(this._schema.objectStores[this._name].indexes),
|
2021-02-16 10:31:55 +01:00
|
|
|
).sort();
|
2021-02-08 19:59:19 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
get indexNames(): DOMStringList {
|
|
|
|
return this._indexNames as DOMStringList;
|
2021-02-08 15:23:44 +01:00
|
|
|
}
|
|
|
|
|
2021-02-08 19:59:19 +01:00
|
|
|
get keyPath(): IDBKeyPath | IDBKeyPath[] {
|
|
|
|
return this._schema.objectStores[this._name].keyPath!;
|
2021-02-08 15:23:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
_name: string;
|
|
|
|
|
|
|
|
get _schema(): Schema {
|
2021-02-08 19:59:19 +01:00
|
|
|
return this._transaction._db._schema;
|
2021-02-08 15:23:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
_deleted: boolean = false;
|
|
|
|
|
|
|
|
constructor(transaction: BridgeIDBTransaction, name: string) {
|
|
|
|
this._name = name;
|
2021-02-08 19:59:19 +01:00
|
|
|
this._transaction = transaction;
|
2021-02-08 15:23:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
get name() {
|
|
|
|
return this._name;
|
|
|
|
}
|
|
|
|
|
|
|
|
get _backend(): Backend {
|
2021-02-08 19:59:19 +01:00
|
|
|
return this._transaction._db._backend;
|
2021-02-08 15:23:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
get _backendConnection(): DatabaseConnection {
|
2021-02-08 19:59:19 +01:00
|
|
|
return this._transaction._db._backendConnection;
|
2021-02-08 15:23:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
_confirmActiveTransaction(): { btx: DatabaseTransaction } {
|
2021-02-08 19:59:19 +01:00
|
|
|
const btx = this._transaction._backendTransaction;
|
2021-02-08 15:23:44 +01:00
|
|
|
if (!btx) {
|
|
|
|
throw new InvalidStateError();
|
|
|
|
}
|
|
|
|
return { btx };
|
|
|
|
}
|
|
|
|
|
|
|
|
// http://w3c.github.io/IndexedDB/#dom-idbobjectstore-name
|
|
|
|
set name(newName: any) {
|
2021-02-08 19:59:19 +01:00
|
|
|
const transaction = this._transaction;
|
2021-02-08 15:23:44 +01:00
|
|
|
|
2021-02-16 10:31:55 +01:00
|
|
|
if (!transaction._db._upgradeTransaction) {
|
2021-02-08 15:23:44 +01:00
|
|
|
throw new InvalidStateError();
|
|
|
|
}
|
|
|
|
|
|
|
|
let { btx } = this._confirmActiveTransaction();
|
|
|
|
|
|
|
|
newName = String(newName);
|
|
|
|
|
|
|
|
const oldName = this._name;
|
|
|
|
|
|
|
|
if (newName === oldName) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this._backend.renameObjectStore(btx, oldName, newName);
|
2021-02-08 19:59:19 +01:00
|
|
|
this._transaction._db._schema = this._backend.getSchema(
|
2021-02-08 15:23:44 +01:00
|
|
|
this._backendConnection,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
public _store(value: any, key: IDBValidKey | undefined, overwrite: boolean) {
|
|
|
|
if (BridgeIDBFactory.enableTracing) {
|
|
|
|
console.log(`TRACE: IDBObjectStore._store`);
|
|
|
|
}
|
2021-02-08 19:59:19 +01:00
|
|
|
if (this._transaction.mode === "readonly") {
|
2021-02-08 15:23:44 +01:00
|
|
|
throw new ReadOnlyError();
|
|
|
|
}
|
|
|
|
const operation = async () => {
|
|
|
|
const { btx } = this._confirmActiveTransaction();
|
|
|
|
const result = await this._backend.storeRecord(btx, {
|
|
|
|
objectStoreName: this._name,
|
|
|
|
key: key,
|
2021-02-08 19:59:19 +01:00
|
|
|
value: structuredEncapsulate(value),
|
2021-02-08 15:23:44 +01:00
|
|
|
storeLevel: overwrite
|
|
|
|
? StoreLevel.AllowOverwrite
|
|
|
|
: StoreLevel.NoOverwrite,
|
|
|
|
});
|
|
|
|
return result.key;
|
|
|
|
};
|
|
|
|
|
2021-02-08 19:59:19 +01:00
|
|
|
return this._transaction._execRequestAsync({ operation, source: this });
|
2021-02-08 15:23:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public put(value: any, key?: IDBValidKey) {
|
|
|
|
if (arguments.length === 0) {
|
|
|
|
throw new TypeError();
|
|
|
|
}
|
|
|
|
return this._store(value, key, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
public add(value: any, key?: IDBValidKey) {
|
|
|
|
if (arguments.length === 0) {
|
|
|
|
throw new TypeError();
|
|
|
|
}
|
|
|
|
return this._store(value, key, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
public delete(key: IDBValidKey | BridgeIDBKeyRange) {
|
|
|
|
if (arguments.length === 0) {
|
|
|
|
throw new TypeError();
|
|
|
|
}
|
|
|
|
|
2021-02-08 19:59:19 +01:00
|
|
|
if (this._transaction.mode === "readonly") {
|
2021-02-08 15:23:44 +01:00
|
|
|
throw new ReadOnlyError();
|
|
|
|
}
|
|
|
|
|
|
|
|
let keyRange: BridgeIDBKeyRange;
|
|
|
|
|
|
|
|
if (key instanceof BridgeIDBKeyRange) {
|
|
|
|
keyRange = key;
|
|
|
|
} else {
|
|
|
|
keyRange = BridgeIDBKeyRange.only(valueToKey(key));
|
|
|
|
}
|
|
|
|
|
|
|
|
const operation = async () => {
|
|
|
|
const { btx } = this._confirmActiveTransaction();
|
|
|
|
return this._backend.deleteRecord(btx, this._name, keyRange);
|
|
|
|
};
|
|
|
|
|
2021-02-08 19:59:19 +01:00
|
|
|
return this._transaction._execRequestAsync({
|
2021-02-08 15:23:44 +01:00
|
|
|
operation,
|
|
|
|
source: this,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
public get(key?: BridgeIDBKeyRange | IDBValidKey) {
|
|
|
|
if (BridgeIDBFactory.enableTracing) {
|
|
|
|
console.log(`getting from object store ${this._name} key ${key}`);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (arguments.length === 0) {
|
|
|
|
throw new TypeError();
|
|
|
|
}
|
|
|
|
|
|
|
|
let keyRange: BridgeIDBKeyRange;
|
|
|
|
|
|
|
|
if (key instanceof BridgeIDBKeyRange) {
|
|
|
|
keyRange = key;
|
|
|
|
} else {
|
|
|
|
try {
|
|
|
|
keyRange = BridgeIDBKeyRange.only(valueToKey(key));
|
|
|
|
} catch (e) {
|
|
|
|
throw Error(
|
|
|
|
`invalid key (type ${typeof key}) for object store ${this._name}`,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const recordRequest: RecordGetRequest = {
|
|
|
|
objectStoreName: this._name,
|
|
|
|
indexName: undefined,
|
|
|
|
lastIndexPosition: undefined,
|
|
|
|
lastObjectStorePosition: undefined,
|
|
|
|
direction: "next",
|
|
|
|
limit: 1,
|
|
|
|
resultLevel: ResultLevel.Full,
|
|
|
|
range: keyRange,
|
|
|
|
};
|
|
|
|
|
|
|
|
const operation = async () => {
|
|
|
|
if (BridgeIDBFactory.enableTracing) {
|
|
|
|
console.log("running get operation:", recordRequest);
|
|
|
|
}
|
|
|
|
const { btx } = this._confirmActiveTransaction();
|
|
|
|
const result = await this._backend.getRecords(btx, recordRequest);
|
|
|
|
|
|
|
|
if (BridgeIDBFactory.enableTracing) {
|
|
|
|
console.log("get operation result count:", result.count);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (result.count === 0) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
const values = result.values;
|
|
|
|
if (!values) {
|
|
|
|
throw Error("invariant violated");
|
|
|
|
}
|
2021-02-08 19:59:19 +01:00
|
|
|
return structuredRevive(values[0]);
|
2021-02-08 15:23:44 +01:00
|
|
|
};
|
|
|
|
|
2021-02-08 19:59:19 +01:00
|
|
|
return this._transaction._execRequestAsync({
|
2021-02-08 15:23:44 +01:00
|
|
|
operation,
|
|
|
|
source: this,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// http://w3c.github.io/IndexedDB/#dom-idbobjectstore-getall
|
2021-02-08 19:59:19 +01:00
|
|
|
public getAll(
|
|
|
|
query?: BridgeIDBKeyRange | IDBValidKey,
|
|
|
|
count?: number,
|
|
|
|
): IDBRequest<any[]> {
|
2021-02-08 15:23:44 +01:00
|
|
|
throw Error("not implemented");
|
|
|
|
}
|
|
|
|
|
|
|
|
// http://w3c.github.io/IndexedDB/#dom-idbobjectstore-getkey
|
2021-02-08 19:59:19 +01:00
|
|
|
public getKey(
|
|
|
|
key?: BridgeIDBKeyRange | IDBValidKey,
|
|
|
|
): IDBRequest<IDBValidKey | undefined> {
|
2021-02-08 15:23:44 +01:00
|
|
|
throw Error("not implemented");
|
|
|
|
}
|
|
|
|
|
|
|
|
// http://w3c.github.io/IndexedDB/#dom-idbobjectstore-getallkeys
|
2021-02-08 19:59:19 +01:00
|
|
|
public getAllKeys(
|
|
|
|
query?: BridgeIDBKeyRange | IDBValidKey,
|
|
|
|
count?: number,
|
|
|
|
): IDBRequest<any[]> {
|
2021-02-08 15:23:44 +01:00
|
|
|
throw Error("not implemented");
|
|
|
|
}
|
|
|
|
|
2021-02-08 19:59:19 +01:00
|
|
|
public clear(): IDBRequest<undefined> {
|
2021-02-08 15:23:44 +01:00
|
|
|
throw Error("not implemented");
|
|
|
|
}
|
|
|
|
|
|
|
|
public openCursor(
|
|
|
|
range?: IDBKeyRange | IDBValidKey,
|
|
|
|
direction: IDBCursorDirection = "next",
|
|
|
|
) {
|
|
|
|
if (range === null) {
|
|
|
|
range = undefined;
|
|
|
|
}
|
|
|
|
if (range !== undefined && !(range instanceof BridgeIDBKeyRange)) {
|
|
|
|
range = BridgeIDBKeyRange.only(valueToKey(range));
|
|
|
|
}
|
|
|
|
|
|
|
|
const request = new BridgeIDBRequest();
|
2021-02-08 19:59:19 +01:00
|
|
|
request._source = this;
|
|
|
|
request.transaction = this._transaction;
|
2021-02-08 15:23:44 +01:00
|
|
|
|
|
|
|
const cursor = new BridgeIDBCursorWithValue(
|
|
|
|
this,
|
|
|
|
this._name,
|
|
|
|
undefined,
|
|
|
|
range,
|
|
|
|
direction,
|
|
|
|
request,
|
|
|
|
);
|
|
|
|
|
2021-02-08 19:59:19 +01:00
|
|
|
return this._transaction._execRequestAsync({
|
2021-02-08 15:23:44 +01:00
|
|
|
operation: () => cursor._iterate(),
|
|
|
|
request,
|
|
|
|
source: this,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
public openKeyCursor(
|
|
|
|
range?: BridgeIDBKeyRange | IDBValidKey,
|
|
|
|
direction?: IDBCursorDirection,
|
|
|
|
) {
|
|
|
|
if (range === null) {
|
|
|
|
range = undefined;
|
|
|
|
}
|
|
|
|
if (range !== undefined && !(range instanceof BridgeIDBKeyRange)) {
|
|
|
|
range = BridgeIDBKeyRange.only(valueToKey(range));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!direction) {
|
|
|
|
direction = "next";
|
|
|
|
}
|
|
|
|
|
|
|
|
const request = new BridgeIDBRequest();
|
2021-02-08 19:59:19 +01:00
|
|
|
request._source = this;
|
|
|
|
request.transaction = this._transaction;
|
2021-02-08 15:23:44 +01:00
|
|
|
|
|
|
|
const cursor = new BridgeIDBCursor(
|
|
|
|
this,
|
|
|
|
this._name,
|
|
|
|
undefined,
|
|
|
|
range,
|
|
|
|
direction,
|
|
|
|
request,
|
|
|
|
true,
|
|
|
|
);
|
|
|
|
|
2021-02-08 19:59:19 +01:00
|
|
|
return this._transaction._execRequestAsync({
|
2021-02-08 15:23:44 +01:00
|
|
|
operation: cursor._iterate.bind(cursor),
|
|
|
|
request,
|
|
|
|
source: this,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// tslint:disable-next-line max-line-length
|
|
|
|
// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#widl-IDBObjectStore-createIndex-IDBIndex-DOMString-name-DOMString-sequence-DOMString--keyPath-IDBIndexParameters-optionalParameters
|
|
|
|
public createIndex(
|
|
|
|
indexName: string,
|
|
|
|
keyPath: IDBKeyPath,
|
|
|
|
optionalParameters: { multiEntry?: boolean; unique?: boolean } = {},
|
|
|
|
) {
|
|
|
|
if (arguments.length < 2) {
|
|
|
|
throw new TypeError();
|
|
|
|
}
|
|
|
|
|
2021-02-16 10:31:55 +01:00
|
|
|
if (!this._transaction._db._upgradeTransaction) {
|
2021-02-08 15:23:44 +01:00
|
|
|
throw new InvalidStateError();
|
|
|
|
}
|
|
|
|
|
|
|
|
const { btx } = this._confirmActiveTransaction();
|
|
|
|
|
|
|
|
const multiEntry =
|
|
|
|
optionalParameters.multiEntry !== undefined
|
|
|
|
? optionalParameters.multiEntry
|
|
|
|
: false;
|
|
|
|
const unique =
|
|
|
|
optionalParameters.unique !== undefined
|
|
|
|
? optionalParameters.unique
|
|
|
|
: false;
|
|
|
|
|
2021-02-08 19:59:19 +01:00
|
|
|
if (this._transaction.mode !== "versionchange") {
|
2021-02-08 15:23:44 +01:00
|
|
|
throw new InvalidStateError();
|
|
|
|
}
|
|
|
|
|
2021-02-08 19:59:19 +01:00
|
|
|
if (this._indexNames.indexOf(indexName) >= 0) {
|
2021-02-08 15:23:44 +01:00
|
|
|
throw new ConstraintError();
|
|
|
|
}
|
|
|
|
|
|
|
|
validateKeyPath(keyPath);
|
|
|
|
|
|
|
|
if (Array.isArray(keyPath) && multiEntry) {
|
|
|
|
throw new InvalidAccessError();
|
|
|
|
}
|
|
|
|
|
|
|
|
this._backend.createIndex(
|
|
|
|
btx,
|
|
|
|
indexName,
|
|
|
|
this._name,
|
2021-02-08 19:59:19 +01:00
|
|
|
normalizeKeyPath(keyPath),
|
2021-02-08 15:23:44 +01:00
|
|
|
multiEntry,
|
|
|
|
unique,
|
|
|
|
);
|
|
|
|
|
|
|
|
return new BridgeIDBIndex(this, indexName);
|
|
|
|
}
|
|
|
|
|
|
|
|
// https://w3c.github.io/IndexedDB/#dom-idbobjectstore-index
|
|
|
|
public index(name: string) {
|
|
|
|
if (arguments.length === 0) {
|
|
|
|
throw new TypeError();
|
|
|
|
}
|
|
|
|
|
2021-02-16 10:31:55 +01:00
|
|
|
if (this._transaction._finished) {
|
2021-02-08 15:23:44 +01:00
|
|
|
throw new InvalidStateError();
|
|
|
|
}
|
|
|
|
|
|
|
|
const index = this._indexesCache.get(name);
|
|
|
|
if (index !== undefined) {
|
|
|
|
return index;
|
|
|
|
}
|
|
|
|
|
|
|
|
return new BridgeIDBIndex(this, name);
|
|
|
|
}
|
|
|
|
|
|
|
|
public deleteIndex(indexName: string) {
|
|
|
|
if (arguments.length === 0) {
|
|
|
|
throw new TypeError();
|
|
|
|
}
|
|
|
|
|
2021-02-08 19:59:19 +01:00
|
|
|
if (this._transaction.mode !== "versionchange") {
|
2021-02-08 15:23:44 +01:00
|
|
|
throw new InvalidStateError();
|
|
|
|
}
|
|
|
|
|
2021-02-16 10:31:55 +01:00
|
|
|
if (!this._transaction._db._upgradeTransaction) {
|
2021-02-08 15:23:44 +01:00
|
|
|
throw new InvalidStateError();
|
|
|
|
}
|
|
|
|
|
|
|
|
const { btx } = this._confirmActiveTransaction();
|
|
|
|
|
|
|
|
const index = this._indexesCache.get(indexName);
|
|
|
|
if (index !== undefined) {
|
|
|
|
index._deleted = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
this._backend.deleteIndex(btx, this._name, indexName);
|
|
|
|
}
|
|
|
|
|
|
|
|
// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#widl-IDBObjectStore-count-IDBRequest-any-key
|
|
|
|
public count(key?: IDBValidKey | BridgeIDBKeyRange) {
|
|
|
|
if (key === null) {
|
|
|
|
key = undefined;
|
|
|
|
}
|
|
|
|
if (key !== undefined && !(key instanceof BridgeIDBKeyRange)) {
|
|
|
|
key = BridgeIDBKeyRange.only(valueToKey(key));
|
|
|
|
}
|
|
|
|
|
|
|
|
const recordGetRequest: RecordGetRequest = {
|
|
|
|
direction: "next",
|
|
|
|
indexName: undefined,
|
|
|
|
lastIndexPosition: undefined,
|
|
|
|
limit: -1,
|
|
|
|
objectStoreName: this._name,
|
|
|
|
lastObjectStorePosition: undefined,
|
|
|
|
range: key,
|
|
|
|
resultLevel: ResultLevel.OnlyCount,
|
|
|
|
};
|
|
|
|
|
|
|
|
const operation = async () => {
|
|
|
|
const { btx } = this._confirmActiveTransaction();
|
|
|
|
const result = await this._backend.getRecords(btx, recordGetRequest);
|
|
|
|
return result.count;
|
|
|
|
};
|
|
|
|
|
2021-02-08 19:59:19 +01:00
|
|
|
return this._transaction._execRequestAsync({ operation, source: this });
|
2021-02-08 15:23:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public toString() {
|
|
|
|
return "[object IDBObjectStore]";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @public */
|
2021-02-08 19:59:19 +01:00
|
|
|
export class BridgeIDBRequest extends FakeEventTarget implements IDBRequest {
|
2021-02-08 15:23:44 +01:00
|
|
|
_result: any = null;
|
|
|
|
_error: Error | null | undefined = null;
|
2021-02-08 19:59:19 +01:00
|
|
|
get source(): IDBObjectStore | IDBIndex | IDBCursor {
|
|
|
|
if (this._source) {
|
|
|
|
return this._source;
|
|
|
|
}
|
|
|
|
throw Error("source is null");
|
|
|
|
}
|
|
|
|
_source:
|
|
|
|
| BridgeIDBCursor
|
|
|
|
| BridgeIDBIndex
|
|
|
|
| BridgeIDBObjectStore
|
|
|
|
| null = null;
|
2021-02-08 15:23:44 +01:00
|
|
|
transaction: BridgeIDBTransaction | null = null;
|
|
|
|
readyState: "done" | "pending" = "pending";
|
|
|
|
onsuccess: EventListener | null = null;
|
|
|
|
onerror: EventListener | null = null;
|
|
|
|
|
|
|
|
get error() {
|
|
|
|
if (this.readyState === "pending") {
|
|
|
|
throw new InvalidStateError();
|
|
|
|
}
|
|
|
|
return this._error;
|
|
|
|
}
|
|
|
|
|
|
|
|
set error(value: any) {
|
|
|
|
this._error = value;
|
|
|
|
}
|
|
|
|
|
|
|
|
get result() {
|
|
|
|
if (this.readyState === "pending") {
|
|
|
|
throw new InvalidStateError();
|
|
|
|
}
|
|
|
|
return this._result;
|
|
|
|
}
|
|
|
|
|
|
|
|
set result(value: any) {
|
|
|
|
this._result = value;
|
|
|
|
}
|
|
|
|
|
|
|
|
toString() {
|
|
|
|
return "[object IDBRequest]";
|
|
|
|
}
|
|
|
|
|
|
|
|
_finishWithError(err: Error) {
|
|
|
|
this.result = undefined;
|
|
|
|
this.readyState = "done";
|
|
|
|
|
|
|
|
this.error = new Error(err.message);
|
|
|
|
this.error.name = err.name;
|
|
|
|
|
|
|
|
const event = new FakeEvent("error", {
|
|
|
|
bubbles: true,
|
|
|
|
cancelable: true,
|
|
|
|
});
|
|
|
|
event.eventPath = [];
|
2021-02-16 10:31:55 +01:00
|
|
|
|
2021-02-08 15:23:44 +01:00
|
|
|
this.dispatchEvent(event);
|
|
|
|
}
|
|
|
|
|
|
|
|
_finishWithResult(result: any) {
|
|
|
|
this.result = result;
|
|
|
|
this.readyState = "done";
|
|
|
|
|
|
|
|
const event = new FakeEvent("success");
|
|
|
|
event.eventPath = [];
|
|
|
|
this.dispatchEvent(event);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @public */
|
2021-02-08 19:59:19 +01:00
|
|
|
export class BridgeIDBOpenDBRequest
|
|
|
|
extends BridgeIDBRequest
|
|
|
|
implements IDBOpenDBRequest {
|
2021-02-08 15:23:44 +01:00
|
|
|
public onupgradeneeded: EventListener | null = null;
|
|
|
|
public onblocked: EventListener | null = null;
|
|
|
|
|
|
|
|
constructor() {
|
|
|
|
super();
|
|
|
|
// https://www.w3.org/TR/IndexedDB/#open-requests
|
2021-02-08 19:59:19 +01:00
|
|
|
this._source = null;
|
2021-02-08 15:23:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public toString() {
|
|
|
|
return "[object IDBOpenDBRequest]";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#transaction
|
|
|
|
/** @public */
|
2021-02-08 19:59:19 +01:00
|
|
|
export class BridgeIDBTransaction
|
|
|
|
extends FakeEventTarget
|
|
|
|
implements IDBTransaction {
|
2021-02-16 10:31:55 +01:00
|
|
|
_committed: boolean = false;
|
|
|
|
/**
|
|
|
|
* A transaction is active as long as new operations can be
|
|
|
|
* placed against it.
|
|
|
|
*/
|
|
|
|
_active: boolean = false;
|
|
|
|
_started: boolean = false;
|
|
|
|
_aborted: boolean = false;
|
|
|
|
_objectStoresCache: Map<string, BridgeIDBObjectStore> = new Map();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* https://www.w3.org/TR/IndexedDB-2/#transaction-lifetime-concept
|
|
|
|
*
|
|
|
|
* When a transaction is committed or aborted, it is said to be finished.
|
|
|
|
*/
|
|
|
|
get _finished(): boolean {
|
|
|
|
return this._committed || this._aborted;
|
|
|
|
}
|
|
|
|
|
|
|
|
_openRequest: BridgeIDBOpenDBRequest | null = null;
|
2021-02-08 15:23:44 +01:00
|
|
|
|
2021-02-16 10:31:55 +01:00
|
|
|
_backendTransaction?: DatabaseTransaction;
|
2021-02-08 15:23:44 +01:00
|
|
|
|
2021-02-16 10:31:55 +01:00
|
|
|
_objectStoreNames: FakeDOMStringList;
|
2021-02-08 19:59:19 +01:00
|
|
|
get objectStoreNames(): DOMStringList {
|
|
|
|
return this._objectStoreNames as DOMStringList;
|
|
|
|
}
|
2021-02-16 10:31:55 +01:00
|
|
|
mode: IDBTransactionMode;
|
|
|
|
_db: BridgeIDBDatabase;
|
2021-02-08 19:59:19 +01:00
|
|
|
|
|
|
|
get db(): IDBDatabase {
|
2021-02-16 10:31:55 +01:00
|
|
|
return this._db;
|
2021-02-08 19:59:19 +01:00
|
|
|
}
|
|
|
|
|
2021-02-16 10:31:55 +01:00
|
|
|
_error: Error | null = null;
|
2021-02-08 19:59:19 +01:00
|
|
|
|
|
|
|
get error(): DOMException {
|
|
|
|
return this._error as DOMException;
|
|
|
|
}
|
|
|
|
|
2021-02-08 15:23:44 +01:00
|
|
|
public onabort: EventListener | null = null;
|
|
|
|
public oncomplete: EventListener | null = null;
|
|
|
|
public onerror: EventListener | null = null;
|
|
|
|
|
|
|
|
private _waitPromise: Promise<void>;
|
|
|
|
private _resolveWait: () => void;
|
|
|
|
|
|
|
|
public _scope: Set<string>;
|
|
|
|
private _requests: Array<{
|
2021-02-16 10:31:55 +01:00
|
|
|
operation: () => Promise<void>;
|
2021-02-08 15:23:44 +01:00
|
|
|
request: BridgeIDBRequest;
|
|
|
|
}> = [];
|
|
|
|
|
|
|
|
get _backend(): Backend {
|
2021-02-08 19:59:19 +01:00
|
|
|
return this._db._backend;
|
2021-02-08 15:23:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
constructor(
|
|
|
|
storeNames: string[],
|
|
|
|
mode: IDBTransactionMode,
|
|
|
|
db: BridgeIDBDatabase,
|
|
|
|
backendTransaction?: DatabaseTransaction,
|
2021-02-16 10:31:55 +01:00
|
|
|
openRequest?: BridgeIDBOpenDBRequest,
|
2021-02-08 15:23:44 +01:00
|
|
|
) {
|
|
|
|
super();
|
|
|
|
|
|
|
|
const myOpenPromise = openPromise<void>();
|
|
|
|
this._waitPromise = myOpenPromise.promise;
|
|
|
|
this._resolveWait = myOpenPromise.resolve;
|
|
|
|
|
|
|
|
this._scope = new Set(storeNames);
|
|
|
|
this._backendTransaction = backendTransaction;
|
|
|
|
this.mode = mode;
|
2021-02-08 19:59:19 +01:00
|
|
|
this._db = db;
|
|
|
|
this._objectStoreNames = fakeDOMStringList(Array.from(this._scope).sort());
|
2021-02-08 15:23:44 +01:00
|
|
|
|
2021-02-08 19:59:19 +01:00
|
|
|
this._db._transactions.push(this);
|
2021-02-16 10:31:55 +01:00
|
|
|
|
|
|
|
this._openRequest = openRequest ?? null;
|
2021-02-08 15:23:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#dfn-steps-for-aborting-a-transaction
|
|
|
|
async _abort(errName: string | null) {
|
2021-02-16 10:31:55 +01:00
|
|
|
if (BridgeIDBFactory.enableTracing) {
|
|
|
|
console.log("TRACE: aborting transaction");
|
|
|
|
}
|
|
|
|
|
|
|
|
this._aborted = true;
|
2021-02-08 15:23:44 +01:00
|
|
|
|
|
|
|
if (errName !== null) {
|
|
|
|
const e = new Error();
|
|
|
|
e.name = errName;
|
2021-02-08 19:59:19 +01:00
|
|
|
this._error = e;
|
2021-02-08 15:23:44 +01:00
|
|
|
}
|
|
|
|
|
2021-02-16 10:31:55 +01:00
|
|
|
if (BridgeIDBFactory.enableTracing) {
|
|
|
|
console.log(`TRACE: aborting ${this._requests.length} requests`);
|
|
|
|
}
|
|
|
|
|
2021-02-08 15:23:44 +01:00
|
|
|
// Should this directly remove from _requests?
|
|
|
|
for (const { request } of this._requests) {
|
2021-02-16 10:31:55 +01:00
|
|
|
console.log("ready state:", request.readyState);
|
2021-02-08 15:23:44 +01:00
|
|
|
if (request.readyState !== "done") {
|
2021-02-16 10:31:55 +01:00
|
|
|
// This will cancel execution of this request's operation
|
|
|
|
request.readyState = "done";
|
|
|
|
if (BridgeIDBFactory.enableTracing) {
|
|
|
|
console.log("dispatching error event");
|
2021-02-08 15:23:44 +01:00
|
|
|
}
|
2021-02-16 10:31:55 +01:00
|
|
|
request.result = undefined;
|
|
|
|
request.error = new AbortError();
|
|
|
|
|
|
|
|
const event = new FakeEvent("error", {
|
|
|
|
bubbles: true,
|
|
|
|
cancelable: true,
|
|
|
|
});
|
|
|
|
event.eventPath = [request, this, this._db];
|
|
|
|
console.log("dispatching error event for request after abort");
|
|
|
|
request.dispatchEvent(event);
|
2021-02-08 15:23:44 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-16 10:31:55 +01:00
|
|
|
// ("abort a transaction", step 5.1)
|
|
|
|
if (this._openRequest) {
|
|
|
|
this._db._upgradeTransaction = null;
|
|
|
|
}
|
|
|
|
|
2021-02-08 15:23:44 +01:00
|
|
|
// Only roll back if we actually executed the scheduled operations.
|
|
|
|
const maybeBtx = this._backendTransaction;
|
|
|
|
if (maybeBtx) {
|
|
|
|
await this._backend.rollback(maybeBtx);
|
|
|
|
}
|
|
|
|
|
2021-02-16 10:31:55 +01:00
|
|
|
this._db._refreshSchema();
|
|
|
|
|
2021-02-08 15:23:44 +01:00
|
|
|
queueTask(() => {
|
|
|
|
const event = new FakeEvent("abort", {
|
|
|
|
bubbles: true,
|
|
|
|
cancelable: false,
|
|
|
|
});
|
2021-02-08 19:59:19 +01:00
|
|
|
event.eventPath = [this._db];
|
2021-02-08 15:23:44 +01:00
|
|
|
this.dispatchEvent(event);
|
|
|
|
});
|
2021-02-16 10:31:55 +01:00
|
|
|
|
|
|
|
if (this._openRequest) {
|
|
|
|
this._openRequest.transaction = null;
|
|
|
|
this._openRequest.result = undefined;
|
|
|
|
this._openRequest.readyState = "pending";
|
|
|
|
}
|
2021-02-08 15:23:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public abort() {
|
2021-02-16 10:31:55 +01:00
|
|
|
if (this._finished) {
|
2021-02-08 15:23:44 +01:00
|
|
|
throw new InvalidStateError();
|
|
|
|
}
|
|
|
|
this._abort(null);
|
|
|
|
}
|
|
|
|
|
|
|
|
// http://w3c.github.io/IndexedDB/#dom-idbtransaction-objectstore
|
2021-02-08 19:59:19 +01:00
|
|
|
public objectStore(name: string): BridgeIDBObjectStore {
|
2021-02-16 10:31:55 +01:00
|
|
|
if (!this._active) {
|
2021-02-08 15:23:44 +01:00
|
|
|
throw new InvalidStateError();
|
|
|
|
}
|
|
|
|
|
|
|
|
const objectStore = this._objectStoresCache.get(name);
|
|
|
|
if (objectStore !== undefined) {
|
|
|
|
return objectStore;
|
|
|
|
}
|
|
|
|
|
|
|
|
return new BridgeIDBObjectStore(this, name);
|
|
|
|
}
|
|
|
|
|
|
|
|
// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#dfn-steps-for-asynchronously-executing-a-request
|
|
|
|
public _execRequestAsync(obj: RequestObj) {
|
|
|
|
const source = obj.source;
|
|
|
|
const operation = obj.operation;
|
|
|
|
let request = obj.hasOwnProperty("request") ? obj.request : null;
|
|
|
|
|
2021-02-16 10:31:55 +01:00
|
|
|
if (!this._active) {
|
2021-02-08 15:23:44 +01:00
|
|
|
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 BridgeIDBRequest();
|
|
|
|
} else {
|
|
|
|
request = new BridgeIDBRequest();
|
2021-02-08 19:59:19 +01:00
|
|
|
request._source = source;
|
2021-02-08 15:23:44 +01:00
|
|
|
request.transaction = (source as any).transaction;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
this._requests.push({
|
|
|
|
operation,
|
|
|
|
request,
|
|
|
|
});
|
|
|
|
|
|
|
|
return request;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Actually execute the scheduled work for this transaction.
|
|
|
|
*/
|
|
|
|
public async _start() {
|
|
|
|
if (BridgeIDBFactory.enableTracing) {
|
|
|
|
console.log(
|
|
|
|
`TRACE: IDBTransaction._start, ${this._requests.length} queued`,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
this._started = true;
|
|
|
|
|
|
|
|
if (!this._backendTransaction) {
|
|
|
|
this._backendTransaction = await this._backend.beginTransaction(
|
2021-02-08 19:59:19 +01:00
|
|
|
this._db._backendConnection,
|
2021-02-08 15:23:44 +01:00
|
|
|
Array.from(this._scope),
|
|
|
|
this.mode,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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) {
|
2021-02-08 19:59:19 +01:00
|
|
|
if (!request._source) {
|
2021-02-08 15:23:44 +01:00
|
|
|
// Special requests like indexes that just need to run some code, with error handling already built into
|
|
|
|
// operation
|
|
|
|
await operation();
|
|
|
|
} else {
|
|
|
|
let event;
|
|
|
|
try {
|
|
|
|
BridgeIDBFactory.enableTracing &&
|
|
|
|
console.log("TRACE: running operation in transaction");
|
|
|
|
const result = await operation();
|
|
|
|
BridgeIDBFactory.enableTracing &&
|
|
|
|
console.log(
|
|
|
|
"TRACE: operation in transaction finished with success",
|
|
|
|
);
|
|
|
|
request.readyState = "done";
|
|
|
|
request.result = result;
|
|
|
|
request.error = undefined;
|
|
|
|
|
2021-02-16 10:31:55 +01:00
|
|
|
// https://www.w3.org/TR/IndexedDB-2/#fire-error-event
|
|
|
|
this._active = true;
|
2021-02-08 15:23:44 +01:00
|
|
|
event = new FakeEvent("success", {
|
|
|
|
bubbles: false,
|
|
|
|
cancelable: false,
|
|
|
|
});
|
|
|
|
|
|
|
|
try {
|
2021-02-08 19:59:19 +01:00
|
|
|
event.eventPath = [request, this, this._db];
|
2021-02-08 15:23:44 +01:00
|
|
|
request.dispatchEvent(event);
|
|
|
|
} catch (err) {
|
2021-02-16 10:31:55 +01:00
|
|
|
if (BridgeIDBFactory.enableTracing) {
|
|
|
|
console.log("TRACING: caught error in transaction success event handler");
|
2021-02-08 15:23:44 +01:00
|
|
|
}
|
2021-02-16 10:31:55 +01:00
|
|
|
this._abort("AbortError");
|
|
|
|
this._active = false;
|
2021-02-08 15:23:44 +01:00
|
|
|
throw err;
|
|
|
|
}
|
|
|
|
} catch (err) {
|
|
|
|
if (BridgeIDBFactory.enableTracing) {
|
|
|
|
console.log("TRACING: error during operation: ", err);
|
|
|
|
}
|
|
|
|
request.readyState = "done";
|
|
|
|
request.result = undefined;
|
|
|
|
request.error = err;
|
|
|
|
|
|
|
|
// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#dfn-fire-an-error-event
|
2021-02-16 10:31:55 +01:00
|
|
|
this._active = true;
|
2021-02-08 15:23:44 +01:00
|
|
|
event = new FakeEvent("error", {
|
|
|
|
bubbles: true,
|
|
|
|
cancelable: true,
|
|
|
|
});
|
|
|
|
|
|
|
|
try {
|
2021-02-08 19:59:19 +01:00
|
|
|
event.eventPath = [this._db, this];
|
2021-02-08 15:23:44 +01:00
|
|
|
request.dispatchEvent(event);
|
|
|
|
} catch (err) {
|
2021-02-16 10:31:55 +01:00
|
|
|
this._abort("AbortError");
|
2021-02-08 15:23:44 +01:00
|
|
|
throw err;
|
|
|
|
}
|
|
|
|
if (!event.canceled) {
|
|
|
|
this._abort(err.name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// On to the next one
|
|
|
|
if (this._requests.length > 0) {
|
|
|
|
this._start();
|
|
|
|
} else {
|
|
|
|
// Give it another chance for new handlers to be set before finishing
|
|
|
|
queueTask(() => this._start());
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-02-16 10:31:55 +01:00
|
|
|
if (!this._finished && !this._committed) {
|
2021-02-08 15:23:44 +01:00
|
|
|
if (BridgeIDBFactory.enableTracing) {
|
|
|
|
console.log("finishing transaction");
|
|
|
|
}
|
|
|
|
|
|
|
|
await this._backend.commit(this._backendTransaction);
|
2021-02-16 10:31:55 +01:00
|
|
|
this._committed = true;
|
2021-02-08 19:59:19 +01:00
|
|
|
if (!this._error) {
|
2021-02-08 15:23:44 +01:00
|
|
|
if (BridgeIDBFactory.enableTracing) {
|
|
|
|
console.log("dispatching 'complete' event on transaction");
|
|
|
|
}
|
|
|
|
const event = new FakeEvent("complete");
|
2021-02-08 19:59:19 +01:00
|
|
|
event.eventPath = [this, this._db];
|
2021-02-08 15:23:44 +01:00
|
|
|
this.dispatchEvent(event);
|
|
|
|
}
|
|
|
|
|
2021-02-08 19:59:19 +01:00
|
|
|
const idx = this._db._transactions.indexOf(this);
|
2021-02-08 15:23:44 +01:00
|
|
|
if (idx < 0) {
|
|
|
|
throw Error("invariant failed");
|
|
|
|
}
|
2021-02-08 19:59:19 +01:00
|
|
|
this._db._transactions.splice(idx, 1);
|
2021-02-08 15:23:44 +01:00
|
|
|
|
|
|
|
this._resolveWait();
|
|
|
|
}
|
2021-02-16 10:31:55 +01:00
|
|
|
if (this._aborted) {
|
|
|
|
this._resolveWait();
|
|
|
|
}
|
2021-02-08 15:23:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public commit() {
|
2021-02-16 10:31:55 +01:00
|
|
|
// The current spec doesn't even have an explicit commit method.
|
|
|
|
// We still support it, effectively as a "no-operation" that
|
|
|
|
// prevents new operations from being scheduled.
|
|
|
|
if (!this._active) {
|
2021-02-08 15:23:44 +01:00
|
|
|
throw new InvalidStateError();
|
|
|
|
}
|
2021-02-16 10:31:55 +01:00
|
|
|
this._active = false;
|
2021-02-08 15:23:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public toString() {
|
|
|
|
return "[object IDBRequest]";
|
|
|
|
}
|
|
|
|
|
|
|
|
_waitDone(): Promise<void> {
|
|
|
|
return this._waitPromise;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export class BridgeIDBVersionChangeEvent extends FakeEvent {
|
|
|
|
public newVersion: number | null;
|
|
|
|
public oldVersion: number;
|
|
|
|
|
|
|
|
constructor(
|
|
|
|
type: "blocked" | "success" | "upgradeneeded" | "versionchange",
|
|
|
|
parameters: { newVersion?: number | null; oldVersion?: number } = {},
|
|
|
|
) {
|
|
|
|
super(type);
|
|
|
|
|
|
|
|
this.newVersion =
|
|
|
|
parameters.newVersion !== undefined ? parameters.newVersion : null;
|
|
|
|
this.oldVersion =
|
|
|
|
parameters.oldVersion !== undefined ? parameters.oldVersion : 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
public toString() {
|
|
|
|
return "[object IDBVersionChangeEvent]";
|
|
|
|
}
|
|
|
|
}
|