idb: more tests, fix DB deletion, exception ordering and transaction active checks
This commit is contained in:
parent
c800e80138
commit
e6946694f2
@ -131,11 +131,6 @@ interface Connection {
|
|||||||
|
|
||||||
modifiedSchema: Schema;
|
modifiedSchema: Schema;
|
||||||
|
|
||||||
/**
|
|
||||||
* Has the underlying database been deleted?
|
|
||||||
*/
|
|
||||||
deleted: boolean;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Map from the effective name of an object store during
|
* Map from the effective name of an object store during
|
||||||
* the transaction to the real name.
|
* the transaction to the real name.
|
||||||
@ -412,13 +407,9 @@ export class MemoryBackend implements Backend {
|
|||||||
return dbList;
|
return dbList;
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteDatabase(tx: DatabaseTransaction, name: string): Promise<void> {
|
async deleteDatabase(name: string): Promise<void> {
|
||||||
if (this.enableTracing) {
|
if (this.enableTracing) {
|
||||||
console.log("TRACING: deleteDatabase");
|
console.log(`TRACING: deleteDatabase(${name})`);
|
||||||
}
|
|
||||||
const myConn = this.connectionsByTransaction[tx.transactionCookie];
|
|
||||||
if (!myConn) {
|
|
||||||
throw Error("no connection associated with transaction");
|
|
||||||
}
|
}
|
||||||
const myDb = this.databases[name];
|
const myDb = this.databases[name];
|
||||||
if (!myDb) {
|
if (!myDb) {
|
||||||
@ -427,13 +418,13 @@ export class MemoryBackend implements Backend {
|
|||||||
if (myDb.committedSchema.databaseName !== name) {
|
if (myDb.committedSchema.databaseName !== name) {
|
||||||
throw Error("name does not match");
|
throw Error("name does not match");
|
||||||
}
|
}
|
||||||
if (myDb.txLevel < TransactionLevel.VersionChange) {
|
|
||||||
throw new InvalidStateError();
|
while (myDb.txLevel !== TransactionLevel.None) {
|
||||||
|
await this.transactionDoneCond.wait();
|
||||||
}
|
}
|
||||||
// if (myDb.connectionCookie !== tx.transactionCookie) {
|
|
||||||
// throw new InvalidAccessError();
|
|
||||||
// }
|
|
||||||
myDb.deleted = true;
|
myDb.deleted = true;
|
||||||
|
delete this.databases[name];
|
||||||
}
|
}
|
||||||
|
|
||||||
async connectDatabase(name: string): Promise<DatabaseConnection> {
|
async connectDatabase(name: string): Promise<DatabaseConnection> {
|
||||||
@ -469,7 +460,6 @@ export class MemoryBackend implements Backend {
|
|||||||
|
|
||||||
const myConn: Connection = {
|
const myConn: Connection = {
|
||||||
dbName: name,
|
dbName: name,
|
||||||
deleted: false,
|
|
||||||
objectStoreMap: this.makeObjectStoreMap(database),
|
objectStoreMap: this.makeObjectStoreMap(database),
|
||||||
modifiedSchema: structuredClone(database.committedSchema),
|
modifiedSchema: structuredClone(database.committedSchema),
|
||||||
};
|
};
|
||||||
@ -560,28 +550,38 @@ export class MemoryBackend implements Backend {
|
|||||||
if (!myConn) {
|
if (!myConn) {
|
||||||
throw Error("connection not found - already closed?");
|
throw Error("connection not found - already closed?");
|
||||||
}
|
}
|
||||||
if (!myConn.deleted) {
|
const myDb = this.databases[myConn.dbName];
|
||||||
const myDb = this.databases[myConn.dbName];
|
// FIXME: what if we're still in a transaction?
|
||||||
// if (myDb.connectionCookies.includes(conn.connectionCookie)) {
|
myDb.connectionCookies = myDb.connectionCookies.filter(
|
||||||
// throw Error("invalid state");
|
(x) => x != conn.connectionCookie,
|
||||||
// }
|
);
|
||||||
// FIXME: what if we're still in a transaction?
|
|
||||||
myDb.connectionCookies = myDb.connectionCookies.filter(
|
|
||||||
(x) => x != conn.connectionCookie,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
delete this.connections[conn.connectionCookie];
|
delete this.connections[conn.connectionCookie];
|
||||||
this.disconnectCond.trigger();
|
this.disconnectCond.trigger();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private requireConnection(dbConn: DatabaseConnection): Connection {
|
||||||
|
const myConn = this.connections[dbConn.connectionCookie];
|
||||||
|
if (!myConn) {
|
||||||
|
throw Error(`unknown connection (${dbConn.connectionCookie})`);
|
||||||
|
}
|
||||||
|
return myConn;
|
||||||
|
}
|
||||||
|
|
||||||
|
private requireConnectionFromTransaction(
|
||||||
|
btx: DatabaseTransaction,
|
||||||
|
): Connection {
|
||||||
|
const myConn = this.connectionsByTransaction[btx.transactionCookie];
|
||||||
|
if (!myConn) {
|
||||||
|
throw Error(`unknown transaction (${btx.transactionCookie})`);
|
||||||
|
}
|
||||||
|
return myConn;
|
||||||
|
}
|
||||||
|
|
||||||
getSchema(dbConn: DatabaseConnection): Schema {
|
getSchema(dbConn: DatabaseConnection): Schema {
|
||||||
if (this.enableTracing) {
|
if (this.enableTracing) {
|
||||||
console.log(`TRACING: getSchema`);
|
console.log(`TRACING: getSchema`);
|
||||||
}
|
}
|
||||||
const myConn = this.connections[dbConn.connectionCookie];
|
const myConn = this.requireConnection(dbConn);
|
||||||
if (!myConn) {
|
|
||||||
throw Error("unknown connection");
|
|
||||||
}
|
|
||||||
const db = this.databases[myConn.dbName];
|
const db = this.databases[myConn.dbName];
|
||||||
if (!db) {
|
if (!db) {
|
||||||
throw Error("db not found");
|
throw Error("db not found");
|
||||||
@ -590,10 +590,7 @@ export class MemoryBackend implements Backend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getCurrentTransactionSchema(btx: DatabaseTransaction): Schema {
|
getCurrentTransactionSchema(btx: DatabaseTransaction): Schema {
|
||||||
const myConn = this.connectionsByTransaction[btx.transactionCookie];
|
const myConn = this.requireConnectionFromTransaction(btx);
|
||||||
if (!myConn) {
|
|
||||||
throw Error("unknown connection");
|
|
||||||
}
|
|
||||||
const db = this.databases[myConn.dbName];
|
const db = this.databases[myConn.dbName];
|
||||||
if (!db) {
|
if (!db) {
|
||||||
throw Error("db not found");
|
throw Error("db not found");
|
||||||
@ -602,10 +599,7 @@ export class MemoryBackend implements Backend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getInitialTransactionSchema(btx: DatabaseTransaction): Schema {
|
getInitialTransactionSchema(btx: DatabaseTransaction): Schema {
|
||||||
const myConn = this.connectionsByTransaction[btx.transactionCookie];
|
const myConn = this.requireConnectionFromTransaction(btx);
|
||||||
if (!myConn) {
|
|
||||||
throw Error("unknown connection");
|
|
||||||
}
|
|
||||||
const db = this.databases[myConn.dbName];
|
const db = this.databases[myConn.dbName];
|
||||||
if (!db) {
|
if (!db) {
|
||||||
throw Error("db not found");
|
throw Error("db not found");
|
||||||
@ -622,10 +616,7 @@ export class MemoryBackend implements Backend {
|
|||||||
if (this.enableTracing) {
|
if (this.enableTracing) {
|
||||||
console.log(`TRACING: renameIndex(?, ${oldName}, ${newName})`);
|
console.log(`TRACING: renameIndex(?, ${oldName}, ${newName})`);
|
||||||
}
|
}
|
||||||
const myConn = this.connectionsByTransaction[btx.transactionCookie];
|
const myConn = this.requireConnectionFromTransaction(btx);
|
||||||
if (!myConn) {
|
|
||||||
throw Error("unknown connection");
|
|
||||||
}
|
|
||||||
const db = this.databases[myConn.dbName];
|
const db = this.databases[myConn.dbName];
|
||||||
if (!db) {
|
if (!db) {
|
||||||
throw Error("db not found");
|
throw Error("db not found");
|
||||||
@ -664,10 +655,7 @@ export class MemoryBackend implements Backend {
|
|||||||
if (this.enableTracing) {
|
if (this.enableTracing) {
|
||||||
console.log(`TRACING: deleteIndex(${indexName})`);
|
console.log(`TRACING: deleteIndex(${indexName})`);
|
||||||
}
|
}
|
||||||
const myConn = this.connectionsByTransaction[btx.transactionCookie];
|
const myConn = this.requireConnectionFromTransaction(btx);
|
||||||
if (!myConn) {
|
|
||||||
throw Error("unknown connection");
|
|
||||||
}
|
|
||||||
const db = this.databases[myConn.dbName];
|
const db = this.databases[myConn.dbName];
|
||||||
if (!db) {
|
if (!db) {
|
||||||
throw Error("db not found");
|
throw Error("db not found");
|
||||||
@ -698,10 +686,7 @@ export class MemoryBackend implements Backend {
|
|||||||
`TRACING: deleteObjectStore(${name}) in ${btx.transactionCookie}`,
|
`TRACING: deleteObjectStore(${name}) in ${btx.transactionCookie}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const myConn = this.connectionsByTransaction[btx.transactionCookie];
|
const myConn = this.requireConnectionFromTransaction(btx);
|
||||||
if (!myConn) {
|
|
||||||
throw Error("unknown connection");
|
|
||||||
}
|
|
||||||
const db = this.databases[myConn.dbName];
|
const db = this.databases[myConn.dbName];
|
||||||
if (!db) {
|
if (!db) {
|
||||||
throw Error("db not found");
|
throw Error("db not found");
|
||||||
@ -740,10 +725,7 @@ export class MemoryBackend implements Backend {
|
|||||||
console.log(`TRACING: renameObjectStore(?, ${oldName}, ${newName})`);
|
console.log(`TRACING: renameObjectStore(?, ${oldName}, ${newName})`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const myConn = this.connectionsByTransaction[btx.transactionCookie];
|
const myConn = this.requireConnectionFromTransaction(btx);
|
||||||
if (!myConn) {
|
|
||||||
throw Error("unknown connection");
|
|
||||||
}
|
|
||||||
const db = this.databases[myConn.dbName];
|
const db = this.databases[myConn.dbName];
|
||||||
if (!db) {
|
if (!db) {
|
||||||
throw Error("db not found");
|
throw Error("db not found");
|
||||||
@ -783,10 +765,7 @@ export class MemoryBackend implements Backend {
|
|||||||
`TRACING: createObjectStore(${btx.transactionCookie}, ${name})`,
|
`TRACING: createObjectStore(${btx.transactionCookie}, ${name})`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const myConn = this.connectionsByTransaction[btx.transactionCookie];
|
const myConn = this.requireConnectionFromTransaction(btx);
|
||||||
if (!myConn) {
|
|
||||||
throw Error("unknown connection");
|
|
||||||
}
|
|
||||||
const db = this.databases[myConn.dbName];
|
const db = this.databases[myConn.dbName];
|
||||||
if (!db) {
|
if (!db) {
|
||||||
throw Error("db not found");
|
throw Error("db not found");
|
||||||
@ -828,10 +807,7 @@ export class MemoryBackend implements Backend {
|
|||||||
if (this.enableTracing) {
|
if (this.enableTracing) {
|
||||||
console.log(`TRACING: createIndex(${indexName})`);
|
console.log(`TRACING: createIndex(${indexName})`);
|
||||||
}
|
}
|
||||||
const myConn = this.connectionsByTransaction[btx.transactionCookie];
|
const myConn = this.requireConnectionFromTransaction(btx);
|
||||||
if (!myConn) {
|
|
||||||
throw Error("unknown connection");
|
|
||||||
}
|
|
||||||
const db = this.databases[myConn.dbName];
|
const db = this.databases[myConn.dbName];
|
||||||
if (!db) {
|
if (!db) {
|
||||||
throw Error("db not found");
|
throw Error("db not found");
|
||||||
@ -892,10 +868,7 @@ export class MemoryBackend implements Backend {
|
|||||||
if (this.enableTracing) {
|
if (this.enableTracing) {
|
||||||
console.log(`TRACING: deleteRecord from store ${objectStoreName}`);
|
console.log(`TRACING: deleteRecord from store ${objectStoreName}`);
|
||||||
}
|
}
|
||||||
const myConn = this.connectionsByTransaction[btx.transactionCookie];
|
const myConn = this.requireConnectionFromTransaction(btx);
|
||||||
if (!myConn) {
|
|
||||||
throw Error("unknown connection");
|
|
||||||
}
|
|
||||||
const db = this.databases[myConn.dbName];
|
const db = this.databases[myConn.dbName];
|
||||||
if (!db) {
|
if (!db) {
|
||||||
throw Error("db not found");
|
throw Error("db not found");
|
||||||
@ -1057,10 +1030,7 @@ export class MemoryBackend implements Backend {
|
|||||||
console.log(`TRACING: getRecords`);
|
console.log(`TRACING: getRecords`);
|
||||||
console.log("query", req);
|
console.log("query", req);
|
||||||
}
|
}
|
||||||
const myConn = this.connectionsByTransaction[btx.transactionCookie];
|
const myConn = this.requireConnectionFromTransaction(btx);
|
||||||
if (!myConn) {
|
|
||||||
throw Error("unknown connection");
|
|
||||||
}
|
|
||||||
const db = this.databases[myConn.dbName];
|
const db = this.databases[myConn.dbName];
|
||||||
if (!db) {
|
if (!db) {
|
||||||
throw Error("db not found");
|
throw Error("db not found");
|
||||||
@ -1388,10 +1358,7 @@ export class MemoryBackend implements Backend {
|
|||||||
if (this.enableTracing) {
|
if (this.enableTracing) {
|
||||||
console.log(`TRACING: storeRecord`);
|
console.log(`TRACING: storeRecord`);
|
||||||
}
|
}
|
||||||
const myConn = this.connectionsByTransaction[btx.transactionCookie];
|
const myConn = this.requireConnectionFromTransaction(btx);
|
||||||
if (!myConn) {
|
|
||||||
throw Error("unknown connection");
|
|
||||||
}
|
|
||||||
const db = this.databases[myConn.dbName];
|
const db = this.databases[myConn.dbName];
|
||||||
if (!db) {
|
if (!db) {
|
||||||
throw Error("db not found");
|
throw Error("db not found");
|
||||||
@ -1626,10 +1593,7 @@ export class MemoryBackend implements Backend {
|
|||||||
if (this.enableTracing) {
|
if (this.enableTracing) {
|
||||||
console.log(`TRACING: commit`);
|
console.log(`TRACING: commit`);
|
||||||
}
|
}
|
||||||
const myConn = this.connectionsByTransaction[btx.transactionCookie];
|
const myConn = this.requireConnectionFromTransaction(btx);
|
||||||
if (!myConn) {
|
|
||||||
throw Error("unknown connection");
|
|
||||||
}
|
|
||||||
const db = this.databases[myConn.dbName];
|
const db = this.databases[myConn.dbName];
|
||||||
if (!db) {
|
if (!db) {
|
||||||
throw Error("db not found");
|
throw Error("db not found");
|
||||||
|
@ -21,7 +21,6 @@ import {
|
|||||||
IDBValidKey,
|
IDBValidKey,
|
||||||
} from "./idbtypes";
|
} from "./idbtypes";
|
||||||
|
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
export interface ObjectStoreProperties {
|
export interface ObjectStoreProperties {
|
||||||
keyPath: string[] | null;
|
keyPath: string[] | null;
|
||||||
@ -151,12 +150,7 @@ export interface Backend {
|
|||||||
newVersion: number,
|
newVersion: number,
|
||||||
): Promise<DatabaseTransaction>;
|
): Promise<DatabaseTransaction>;
|
||||||
|
|
||||||
/**
|
deleteDatabase(name: string): Promise<void>;
|
||||||
* Even though the standard interface for indexedDB doesn't require
|
|
||||||
* the client to run deleteDatabase in a version transaction, there is
|
|
||||||
* implicitly one running.
|
|
||||||
*/
|
|
||||||
deleteDatabase(btx: DatabaseTransaction, name: string): Promise<void>;
|
|
||||||
|
|
||||||
close(db: DatabaseConnection): Promise<void>;
|
close(db: DatabaseConnection): Promise<void>;
|
||||||
|
|
||||||
|
@ -195,7 +195,10 @@ export class BridgeIDBCursor implements IDBCursor {
|
|||||||
/**
|
/**
|
||||||
* https://w3c.github.io/IndexedDB/#iterate-a-cursor
|
* https://w3c.github.io/IndexedDB/#iterate-a-cursor
|
||||||
*/
|
*/
|
||||||
async _iterate(key?: IDBValidKey, primaryKey?: IDBValidKey): Promise<any> {
|
async _iterate(
|
||||||
|
key?: IDBValidKey,
|
||||||
|
primaryKey?: IDBValidKey,
|
||||||
|
): Promise<BridgeIDBCursor | null> {
|
||||||
BridgeIDBFactory.enableTracing &&
|
BridgeIDBFactory.enableTracing &&
|
||||||
console.log(
|
console.log(
|
||||||
`iterating cursor os=${this._objectStoreName},idx=${this._indexName}`,
|
`iterating cursor os=${this._objectStoreName},idx=${this._indexName}`,
|
||||||
@ -312,6 +315,10 @@ export class BridgeIDBCursor implements IDBCursor {
|
|||||||
* http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#widl-IDBCursor-advance-void-unsigned-long-count
|
* http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#widl-IDBCursor-advance-void-unsigned-long-count
|
||||||
*/
|
*/
|
||||||
public advance(count: number) {
|
public advance(count: number) {
|
||||||
|
if (typeof count !== "number" || count <= 0) {
|
||||||
|
throw TypeError("count must be positive number");
|
||||||
|
}
|
||||||
|
|
||||||
const transaction = this._effectiveObjectStore._transaction;
|
const transaction = this._effectiveObjectStore._transaction;
|
||||||
|
|
||||||
if (!transaction._active) {
|
if (!transaction._active) {
|
||||||
@ -337,9 +344,11 @@ export class BridgeIDBCursor implements IDBCursor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const operation = async () => {
|
const operation = async () => {
|
||||||
|
let res: IDBCursor | null = null;
|
||||||
for (let i = 0; i < count; i++) {
|
for (let i = 0; i < count; i++) {
|
||||||
await this._iterate();
|
res = await this._iterate();
|
||||||
}
|
}
|
||||||
|
return res;
|
||||||
};
|
};
|
||||||
|
|
||||||
transaction._execRequestAsync({
|
transaction._execRequestAsync({
|
||||||
@ -527,6 +536,11 @@ export class BridgeIDBDatabase extends FakeEventTarget implements IDBDatabase {
|
|||||||
|
|
||||||
_schema: Schema;
|
_schema: Schema;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name that can be set to identify the object store in logs.
|
||||||
|
*/
|
||||||
|
_debugName: string | undefined = undefined;
|
||||||
|
|
||||||
get name(): string {
|
get name(): string {
|
||||||
return this._schema.databaseName;
|
return this._schema.databaseName;
|
||||||
}
|
}
|
||||||
@ -686,12 +700,23 @@ export class BridgeIDBDatabase extends FakeEventTarget implements IDBDatabase {
|
|||||||
openRequest,
|
openRequest,
|
||||||
);
|
);
|
||||||
this._transactions.push(tx);
|
this._transactions.push(tx);
|
||||||
queueTask(() => tx._start());
|
|
||||||
|
queueTask(() => {
|
||||||
|
console.log("TRACE: calling auto-commit", this._getReadableName());
|
||||||
|
tx._start();
|
||||||
|
});
|
||||||
|
if (BridgeIDBFactory.enableTracing) {
|
||||||
|
console.log("TRACE: queued task to auto-commit", this._getReadableName());
|
||||||
|
}
|
||||||
// "When a transaction is created its active flag is initially set."
|
// "When a transaction is created its active flag is initially set."
|
||||||
tx._active = true;
|
tx._active = true;
|
||||||
return tx;
|
return tx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_getReadableName(): string {
|
||||||
|
return `${this.name}(${this._debugName ?? "??"})`;
|
||||||
|
}
|
||||||
|
|
||||||
public transaction(
|
public transaction(
|
||||||
storeNames: string | string[],
|
storeNames: string | string[],
|
||||||
mode?: IDBTransactionMode,
|
mode?: IDBTransactionMode,
|
||||||
@ -745,15 +770,7 @@ export class BridgeIDBFactory {
|
|||||||
const oldVersion = dbInfo.version;
|
const oldVersion = dbInfo.version;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const dbconn = await this.backend.connectDatabase(name);
|
await this.backend.deleteDatabase(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.result = undefined;
|
||||||
request.readyState = "done";
|
request.readyState = "done";
|
||||||
|
|
||||||
@ -797,15 +814,11 @@ export class BridgeIDBFactory {
|
|||||||
let dbconn: DatabaseConnection;
|
let dbconn: DatabaseConnection;
|
||||||
try {
|
try {
|
||||||
if (BridgeIDBFactory.enableTracing) {
|
if (BridgeIDBFactory.enableTracing) {
|
||||||
console.log(
|
console.log("TRACE: connecting to database");
|
||||||
"TRACE: connecting to database",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
dbconn = await this.backend.connectDatabase(name);
|
dbconn = await this.backend.connectDatabase(name);
|
||||||
if (BridgeIDBFactory.enableTracing) {
|
if (BridgeIDBFactory.enableTracing) {
|
||||||
console.log(
|
console.log("TRACE: connected!");
|
||||||
"TRACE: connected!",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (BridgeIDBFactory.enableTracing) {
|
if (BridgeIDBFactory.enableTracing) {
|
||||||
@ -1385,6 +1398,11 @@ export class BridgeIDBObjectStore implements IDBObjectStore {
|
|||||||
|
|
||||||
_transaction: BridgeIDBTransaction;
|
_transaction: BridgeIDBTransaction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name that can be set to identify the object store in logs.
|
||||||
|
*/
|
||||||
|
_debugName: string | undefined = undefined;
|
||||||
|
|
||||||
get transaction(): IDBTransaction {
|
get transaction(): IDBTransaction {
|
||||||
return this._transaction;
|
return this._transaction;
|
||||||
}
|
}
|
||||||
@ -1490,8 +1508,15 @@ export class BridgeIDBObjectStore implements IDBObjectStore {
|
|||||||
|
|
||||||
public _store(value: any, key: IDBValidKey | undefined, overwrite: boolean) {
|
public _store(value: any, key: IDBValidKey | undefined, overwrite: boolean) {
|
||||||
if (BridgeIDBFactory.enableTracing) {
|
if (BridgeIDBFactory.enableTracing) {
|
||||||
console.log(`TRACE: IDBObjectStore._store`);
|
console.log(
|
||||||
|
`TRACE: IDBObjectStore._store, db=${this._transaction._db._getReadableName()}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this._transaction._active) {
|
||||||
|
throw new TransactionInactiveError();
|
||||||
|
}
|
||||||
|
|
||||||
if (this._transaction.mode === "readonly") {
|
if (this._transaction.mode === "readonly") {
|
||||||
throw new ReadOnlyError();
|
throw new ReadOnlyError();
|
||||||
}
|
}
|
||||||
@ -1988,6 +2013,11 @@ export class BridgeIDBTransaction
|
|||||||
_aborted: boolean = false;
|
_aborted: boolean = false;
|
||||||
_objectStoresCache: Map<string, BridgeIDBObjectStore> = new Map();
|
_objectStoresCache: Map<string, BridgeIDBObjectStore> = new Map();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name that can be set to identify the transaction in logs.
|
||||||
|
*/
|
||||||
|
_debugName: string | undefined = undefined;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* https://www.w3.org/TR/IndexedDB-2/#transaction-lifetime-concept
|
* https://www.w3.org/TR/IndexedDB-2/#transaction-lifetime-concept
|
||||||
*
|
*
|
||||||
@ -2074,7 +2104,12 @@ export class BridgeIDBTransaction
|
|||||||
console.log("TRACE: aborting transaction");
|
console.log("TRACE: aborting transaction");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this._aborted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this._aborted = true;
|
this._aborted = true;
|
||||||
|
this._active = false;
|
||||||
|
|
||||||
if (errName !== null) {
|
if (errName !== null) {
|
||||||
const e = new Error();
|
const e = new Error();
|
||||||
@ -2116,6 +2151,7 @@ export class BridgeIDBTransaction
|
|||||||
this._db._schema = this._backend.getInitialTransactionSchema(maybeBtx);
|
this._db._schema = this._backend.getInitialTransactionSchema(maybeBtx);
|
||||||
// Only roll back if we actually executed the scheduled operations.
|
// Only roll back if we actually executed the scheduled operations.
|
||||||
await this._backend.rollback(maybeBtx);
|
await this._backend.rollback(maybeBtx);
|
||||||
|
this._backendTransaction = undefined;
|
||||||
} else {
|
} else {
|
||||||
this._db._schema = this._backend.getSchema(this._db._backendConnection);
|
this._db._schema = this._backend.getSchema(this._db._backendConnection);
|
||||||
}
|
}
|
||||||
@ -2208,17 +2244,11 @@ export class BridgeIDBTransaction
|
|||||||
`TRACE: IDBTransaction._start, ${this._requests.length} queued`,
|
`TRACE: IDBTransaction._start, ${this._requests.length} queued`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._started = true;
|
this._started = true;
|
||||||
|
|
||||||
if (!this._backendTransaction) {
|
// Remove from request queue - cursor ones will be added back if necessary
|
||||||
this._backendTransaction = await this._backend.beginTransaction(
|
// by cursor.continue and such
|
||||||
this._db._backendConnection,
|
|
||||||
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 operation;
|
||||||
let request;
|
let request;
|
||||||
while (this._requests.length > 0) {
|
while (this._requests.length > 0) {
|
||||||
@ -2233,9 +2263,25 @@ export class BridgeIDBTransaction
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (request && operation) {
|
if (request && operation) {
|
||||||
|
if (!this._backendTransaction && !this._aborted) {
|
||||||
|
if (BridgeIDBFactory.enableTracing) {
|
||||||
|
console.log("beginning backend transaction to process operation");
|
||||||
|
}
|
||||||
|
this._backendTransaction = await this._backend.beginTransaction(
|
||||||
|
this._db._backendConnection,
|
||||||
|
Array.from(this._scope),
|
||||||
|
this.mode,
|
||||||
|
);
|
||||||
|
if (BridgeIDBFactory.enableTracing) {
|
||||||
|
console.log(
|
||||||
|
`started backend transaction (${this._backendTransaction.transactionCookie})`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!request._source) {
|
if (!request._source) {
|
||||||
// Special requests like indexes that just need to run some code, with error handling already built into
|
// Special requests like indexes that just need to run some code,
|
||||||
// operation
|
// with error handling already built into operation
|
||||||
await operation();
|
await operation();
|
||||||
} else {
|
} else {
|
||||||
let event;
|
let event;
|
||||||
@ -2311,10 +2357,18 @@ export class BridgeIDBTransaction
|
|||||||
|
|
||||||
if (!this._finished && !this._committed) {
|
if (!this._finished && !this._committed) {
|
||||||
if (BridgeIDBFactory.enableTracing) {
|
if (BridgeIDBFactory.enableTracing) {
|
||||||
console.log("finishing transaction");
|
console.log(
|
||||||
|
`setting transaction to inactive, db=${this._db._getReadableName()}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this._backend.commit(this._backendTransaction);
|
this._active = false;
|
||||||
|
|
||||||
|
// We only have a backend transaction if any requests were placed
|
||||||
|
// against the transactions.
|
||||||
|
if (this._backendTransaction) {
|
||||||
|
await this._backend.commit(this._backendTransaction);
|
||||||
|
}
|
||||||
this._committed = true;
|
this._committed = true;
|
||||||
if (!this._error) {
|
if (!this._error) {
|
||||||
if (BridgeIDBFactory.enableTracing) {
|
if (BridgeIDBFactory.enableTracing) {
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import test from "ava";
|
import test from "ava";
|
||||||
import { BridgeIDBCursor } from "..";
|
import { BridgeIDBCursor } from "..";
|
||||||
|
import { BridgeIDBRequest } from "../bridge-idb";
|
||||||
|
import { InvalidStateError } from "../util/errors";
|
||||||
import { createdb } from "./wptsupport";
|
import { createdb } from "./wptsupport";
|
||||||
|
|
||||||
test("WPT test idbcursor_advance_index.htm", async (t) => {
|
test("WPT test idbcursor_advance_index.htm", async (t) => {
|
||||||
@ -34,6 +36,7 @@ test("WPT test idbcursor_advance_index.htm", async (t) => {
|
|||||||
cursor_rq.onsuccess = function (e: any) {
|
cursor_rq.onsuccess = function (e: any) {
|
||||||
var cursor = e.target.result;
|
var cursor = e.target.result;
|
||||||
t.log(cursor);
|
t.log(cursor);
|
||||||
|
t.true(e.target instanceof BridgeIDBRequest);
|
||||||
t.true(cursor instanceof BridgeIDBCursor);
|
t.true(cursor instanceof BridgeIDBCursor);
|
||||||
|
|
||||||
switch (count) {
|
switch (count) {
|
||||||
@ -51,7 +54,259 @@ test("WPT test idbcursor_advance_index.htm", async (t) => {
|
|||||||
t.fail("unexpected count");
|
t.fail("unexpected count");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// IDBCursor.advance() - attempt to pass a count parameter that is not a number
|
||||||
|
test("WPT test idbcursor_advance_index2.htm", async (t) => {
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
var db: any;
|
||||||
|
|
||||||
|
const records = [
|
||||||
|
{ pKey: "primaryKey_0", iKey: "indexKey_0" },
|
||||||
|
{ pKey: "primaryKey_1", iKey: "indexKey_1" },
|
||||||
|
];
|
||||||
|
|
||||||
|
var open_rq = createdb(t);
|
||||||
|
open_rq.onupgradeneeded = function (e: any) {
|
||||||
|
db = e.target.result;
|
||||||
|
var objStore = db.createObjectStore("test", { keyPath: "pKey" });
|
||||||
|
|
||||||
|
objStore.createIndex("index", "iKey");
|
||||||
|
|
||||||
|
for (var i = 0; i < records.length; i++) objStore.add(records[i]);
|
||||||
|
};
|
||||||
|
|
||||||
|
open_rq.onsuccess = function (e) {
|
||||||
|
var cursor_rq = db
|
||||||
|
.transaction("test")
|
||||||
|
.objectStore("test")
|
||||||
|
.index("index")
|
||||||
|
.openCursor();
|
||||||
|
|
||||||
|
cursor_rq.onsuccess = function (e: any) {
|
||||||
|
var cursor = e.target.result;
|
||||||
|
|
||||||
|
t.true(cursor != null, "cursor exist");
|
||||||
|
t.throws(
|
||||||
|
() => {
|
||||||
|
// Original test uses "document".
|
||||||
|
cursor.advance({ foo: 42 });
|
||||||
|
},
|
||||||
|
{ instanceOf: TypeError },
|
||||||
|
);
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// IDBCursor.advance() - index - attempt to advance backwards
|
||||||
|
test("WPT test idbcursor_advance_index3.htm", async (t) => {
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
var db: any;
|
||||||
|
|
||||||
|
const records = [
|
||||||
|
{ pKey: "primaryKey_0", iKey: "indexKey_0" },
|
||||||
|
{ pKey: "primaryKey_1", iKey: "indexKey_1" },
|
||||||
|
];
|
||||||
|
|
||||||
|
var open_rq = createdb(t);
|
||||||
|
open_rq.onupgradeneeded = function (e: any) {
|
||||||
|
db = e.target.result;
|
||||||
|
var objStore = db.createObjectStore("test", { keyPath: "pKey" });
|
||||||
|
|
||||||
|
objStore.createIndex("index", "iKey");
|
||||||
|
|
||||||
|
for (var i = 0; i < records.length; i++) objStore.add(records[i]);
|
||||||
|
};
|
||||||
|
|
||||||
|
open_rq.onsuccess = function (e) {
|
||||||
|
var cursor_rq = db
|
||||||
|
.transaction("test")
|
||||||
|
.objectStore("test")
|
||||||
|
.index("index")
|
||||||
|
.openCursor();
|
||||||
|
|
||||||
|
cursor_rq.onsuccess = function (e: any) {
|
||||||
|
var cursor = e.target.result;
|
||||||
|
|
||||||
|
t.true(cursor != null, "cursor exist");
|
||||||
|
t.throws(
|
||||||
|
() => {
|
||||||
|
cursor.advance(-1);
|
||||||
|
},
|
||||||
|
{ instanceOf: TypeError },
|
||||||
|
);
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// IDBCursor.advance() - index - iterate to the next record
|
||||||
|
test("WPT test idbcursor_advance_index5.htm", async (t) => {
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
var db: any;
|
||||||
|
let count = 0;
|
||||||
|
const records = [
|
||||||
|
{ pKey: "primaryKey_0", iKey: "indexKey_0" },
|
||||||
|
{ pKey: "primaryKey_1", iKey: "indexKey_1" },
|
||||||
|
{ pKey: "primaryKey_1-2", iKey: "indexKey_1" },
|
||||||
|
],
|
||||||
|
expected = [
|
||||||
|
{ pKey: "primaryKey_0", iKey: "indexKey_0" },
|
||||||
|
{ pKey: "primaryKey_1-2", iKey: "indexKey_1" },
|
||||||
|
];
|
||||||
|
|
||||||
|
var open_rq = createdb(t);
|
||||||
|
open_rq.onupgradeneeded = function (e: any) {
|
||||||
|
db = e.target.result;
|
||||||
|
var objStore = db.createObjectStore("test", { keyPath: "pKey" });
|
||||||
|
|
||||||
|
objStore.createIndex("index", "iKey");
|
||||||
|
|
||||||
|
for (var i = 0; i < records.length; i++) objStore.add(records[i]);
|
||||||
|
};
|
||||||
|
|
||||||
|
open_rq.onsuccess = function (e: any) {
|
||||||
|
var cursor_rq = db
|
||||||
|
.transaction("test")
|
||||||
|
.objectStore("test")
|
||||||
|
.index("index")
|
||||||
|
.openCursor();
|
||||||
|
|
||||||
|
cursor_rq.onsuccess = function (e: any) {
|
||||||
|
var cursor = e.target.result;
|
||||||
|
if (!cursor) {
|
||||||
|
t.deepEqual(count, expected.length, "cursor run count");
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
var record = cursor.value;
|
||||||
|
t.deepEqual(record.pKey, expected[count].pKey, "primary key");
|
||||||
|
t.deepEqual(record.iKey, expected[count].iKey, "index key");
|
||||||
|
|
||||||
|
cursor.advance(2);
|
||||||
|
count++;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// IDBCursor.advance() - index - throw TransactionInactiveError
|
||||||
|
test("WPT test idbcursor_advance_index7.htm", async (t) => {
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
var db: any;
|
||||||
|
const records = [
|
||||||
|
{ pKey: "primaryKey_0", iKey: "indexKey_0" },
|
||||||
|
{ pKey: "primaryKey_1", iKey: "indexKey_1" },
|
||||||
|
];
|
||||||
|
|
||||||
|
var open_rq = createdb(t);
|
||||||
|
open_rq.onupgradeneeded = function (event: any) {
|
||||||
|
db = event.target.result;
|
||||||
|
var objStore = db.createObjectStore("store", { keyPath: "pKey" });
|
||||||
|
objStore.createIndex("index", "iKey");
|
||||||
|
for (var i = 0; i < records.length; i++) {
|
||||||
|
objStore.add(records[i]);
|
||||||
|
}
|
||||||
|
var rq = objStore.index("index").openCursor();
|
||||||
|
rq.onsuccess = function (event: any) {
|
||||||
|
var cursor = event.target.result;
|
||||||
|
t.true(cursor instanceof BridgeIDBCursor);
|
||||||
|
|
||||||
|
event.target.transaction.abort();
|
||||||
|
t.throws(
|
||||||
|
() => {
|
||||||
|
cursor.advance(1);
|
||||||
|
},
|
||||||
|
{ name: "TransactionInactiveError" },
|
||||||
|
"Calling advance() should throws an exception TransactionInactiveError when the transaction is not active.",
|
||||||
|
);
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// IDBCursor.advance() - index - throw InvalidStateError
|
||||||
|
test("WPT test idbcursor_advance_index8.htm", async (t) => {
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
var db: any;
|
||||||
|
const records = [
|
||||||
|
{ pKey: "primaryKey_0", iKey: "indexKey_0" },
|
||||||
|
{ pKey: "primaryKey_1", iKey: "indexKey_1" },
|
||||||
|
];
|
||||||
|
|
||||||
|
var open_rq = createdb(t);
|
||||||
|
open_rq.onupgradeneeded = function (event: any) {
|
||||||
|
db = event.target.result;
|
||||||
|
var objStore = db.createObjectStore("store", { keyPath: "pKey" });
|
||||||
|
objStore.createIndex("index", "iKey");
|
||||||
|
for (var i = 0; i < records.length; i++) {
|
||||||
|
objStore.add(records[i]);
|
||||||
|
}
|
||||||
|
var rq = objStore.index("index").openCursor();
|
||||||
|
let called = false;
|
||||||
|
rq.onsuccess = function (event: any) {
|
||||||
|
if (called) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
called = true;
|
||||||
|
var cursor = event.target.result;
|
||||||
|
t.true(cursor instanceof BridgeIDBCursor);
|
||||||
|
|
||||||
|
cursor.advance(1);
|
||||||
|
t.throws(
|
||||||
|
() => {
|
||||||
|
cursor.advance(1);
|
||||||
|
},
|
||||||
|
{ name: "InvalidStateError" },
|
||||||
|
"Calling advance() should throw DOMException when the cursor is currently being iterated.",
|
||||||
|
);
|
||||||
|
t.pass();
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// IDBCursor.advance() - index - throw InvalidStateError caused by object store been deleted
|
||||||
|
test("WPT test idbcursor_advance_index9.htm", async (t) => {
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
var db: any;
|
||||||
|
const records = [
|
||||||
|
{ pKey: "primaryKey_0", iKey: "indexKey_0" },
|
||||||
|
{ pKey: "primaryKey_1", iKey: "indexKey_1" },
|
||||||
|
];
|
||||||
|
|
||||||
|
var open_rq = createdb(t);
|
||||||
|
open_rq.onupgradeneeded = function (event: any) {
|
||||||
|
db = event.target.result;
|
||||||
|
var objStore = db.createObjectStore("store", { keyPath: "pKey" });
|
||||||
|
objStore.createIndex("index", "iKey");
|
||||||
|
for (var i = 0; i < records.length; i++) {
|
||||||
|
objStore.add(records[i]);
|
||||||
|
}
|
||||||
|
var rq = objStore.index("index").openCursor();
|
||||||
|
rq.onsuccess = function (event: any) {
|
||||||
|
var cursor = event.target.result;
|
||||||
|
t.true(cursor instanceof BridgeIDBCursor, "cursor exist");
|
||||||
|
|
||||||
|
db.deleteObjectStore("store");
|
||||||
|
t.throws(
|
||||||
|
() => {
|
||||||
|
cursor.advance(1);
|
||||||
|
},
|
||||||
|
{ name: "InvalidStateError" },
|
||||||
|
"If the cursor's source or effective object store has been deleted, the implementation MUST throw a DOMException of type InvalidStateError",
|
||||||
|
);
|
||||||
|
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -0,0 +1,104 @@
|
|||||||
|
import test, { ExecutionContext } from "ava";
|
||||||
|
import { BridgeIDBCursor } from "..";
|
||||||
|
import { BridgeIDBRequest } from "../bridge-idb";
|
||||||
|
import { InvalidStateError } from "../util/errors";
|
||||||
|
import { createdb, indexeddb_test } from "./wptsupport";
|
||||||
|
|
||||||
|
async function t1(t: ExecutionContext, method: string): Promise<void> {
|
||||||
|
await indexeddb_test(
|
||||||
|
t,
|
||||||
|
(done, db) => {
|
||||||
|
const store = db.createObjectStore("s");
|
||||||
|
const store2 = db.createObjectStore("s2");
|
||||||
|
|
||||||
|
db.deleteObjectStore("s2");
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
t.throws(
|
||||||
|
() => {
|
||||||
|
(store2 as any)[method]("key", "value");
|
||||||
|
},
|
||||||
|
{ name: "InvalidStateError" },
|
||||||
|
'"has been deleted" check (InvalidStateError) should precede ' +
|
||||||
|
'"not active" check (TransactionInactiveError)',
|
||||||
|
);
|
||||||
|
done();
|
||||||
|
}, 0);
|
||||||
|
},
|
||||||
|
(done, db) => {},
|
||||||
|
"t1",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IDBObjectStore.${method} exception order: 'TransactionInactiveError vs. ReadOnlyError'
|
||||||
|
*/
|
||||||
|
async function t2(t: ExecutionContext, method: string): Promise<void> {
|
||||||
|
await indexeddb_test(
|
||||||
|
t,
|
||||||
|
(done, db) => {
|
||||||
|
const store = db.createObjectStore("s");
|
||||||
|
},
|
||||||
|
(done, db) => {
|
||||||
|
(db as any)._debugName = method;
|
||||||
|
const tx = db.transaction("s", "readonly");
|
||||||
|
const store = tx.objectStore("s");
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
t.throws(
|
||||||
|
() => {
|
||||||
|
console.log(`calling ${method}`);
|
||||||
|
(store as any)[method]("key", "value");
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "TransactionInactiveError",
|
||||||
|
},
|
||||||
|
'"not active" check (TransactionInactiveError) should precede ' +
|
||||||
|
'"read only" check (ReadOnlyError)',
|
||||||
|
);
|
||||||
|
|
||||||
|
done();
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
console.log(`queued task for ${method}`);
|
||||||
|
},
|
||||||
|
"t2",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IDBObjectStore.${method} exception order: 'ReadOnlyError vs. DataError'
|
||||||
|
*/
|
||||||
|
async function t3(t: ExecutionContext, method: string): Promise<void> {
|
||||||
|
await indexeddb_test(
|
||||||
|
t,
|
||||||
|
(done, db) => {
|
||||||
|
const store = db.createObjectStore("s");
|
||||||
|
},
|
||||||
|
(done, db) => {
|
||||||
|
const tx = db.transaction("s", "readonly");
|
||||||
|
const store = tx.objectStore("s");
|
||||||
|
|
||||||
|
t.throws(
|
||||||
|
() => {
|
||||||
|
(store as any)[method]({}, "value");
|
||||||
|
},
|
||||||
|
{ name: "ReadOnlyError" },
|
||||||
|
'"read only" check (ReadOnlyError) should precede ' +
|
||||||
|
"key/data check (DataError)",
|
||||||
|
);
|
||||||
|
|
||||||
|
done();
|
||||||
|
},
|
||||||
|
"t3",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
test("WPT idbobjectstore-add-put-exception-order.html (add, t1)", t1, "add");
|
||||||
|
test("WPT idbobjectstore-add-put-exception-order.html (put, t1)", t1, "put");
|
||||||
|
|
||||||
|
test("WPT idbobjectstore-add-put-exception-order.html (add, t2)", t2, "add");
|
||||||
|
test("WPT idbobjectstore-add-put-exception-order.html (put, t2)", t2, "put");
|
||||||
|
|
||||||
|
test("WPT idbobjectstore-add-put-exception-order.html (add, t3)", t3, "add");
|
||||||
|
test("WPT idbobjectstore-add-put-exception-order.html (put, t3)", t3, "put");
|
@ -422,3 +422,61 @@ export function format_value(val: any, seen?: any): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Usage:
|
||||||
|
// indexeddb_test(
|
||||||
|
// (test_object, db_connection, upgrade_tx, open_request) => {
|
||||||
|
// // Database creation logic.
|
||||||
|
// },
|
||||||
|
// (test_object, db_connection, open_request) => {
|
||||||
|
// // Test logic.
|
||||||
|
// test_object.done();
|
||||||
|
// },
|
||||||
|
// 'Test case description');
|
||||||
|
export function indexeddb_test(
|
||||||
|
t: ExecutionContext,
|
||||||
|
upgrade_func: (
|
||||||
|
done: () => void,
|
||||||
|
db: IDBDatabase,
|
||||||
|
tx: IDBTransaction,
|
||||||
|
open: IDBOpenDBRequest,
|
||||||
|
) => void,
|
||||||
|
open_func: (
|
||||||
|
done: () => void,
|
||||||
|
db: IDBDatabase,
|
||||||
|
open: IDBOpenDBRequest,
|
||||||
|
) => void,
|
||||||
|
dbsuffix?: string,
|
||||||
|
options?: any,
|
||||||
|
): Promise<void> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
options = Object.assign({ upgrade_will_abort: false }, options);
|
||||||
|
const dbname =
|
||||||
|
"testdb-" + new Date().getTime() + Math.random() + (dbsuffix ?? "");
|
||||||
|
var del = self.indexedDB.deleteDatabase(dbname);
|
||||||
|
del.onerror = () => t.fail("deleteDatabase should succeed");
|
||||||
|
var open = self.indexedDB.open(dbname, 1);
|
||||||
|
open.onupgradeneeded = function () {
|
||||||
|
var db = open.result;
|
||||||
|
t.teardown(function () {
|
||||||
|
// If open didn't succeed already, ignore the error.
|
||||||
|
open.onerror = function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
};
|
||||||
|
db.close();
|
||||||
|
self.indexedDB.deleteDatabase(db.name);
|
||||||
|
});
|
||||||
|
var tx = open.transaction!;
|
||||||
|
upgrade_func(resolve, db, tx, open);
|
||||||
|
};
|
||||||
|
if (options.upgrade_will_abort) {
|
||||||
|
open.onsuccess = () => t.fail("open should not succeed");
|
||||||
|
} else {
|
||||||
|
open.onerror = () => t.fail("open should succeed");
|
||||||
|
open.onsuccess = function () {
|
||||||
|
var db = open.result;
|
||||||
|
if (open_func) open_func(resolve, db, open);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user