idb: make more tests pass, implement Cursor.advance()
This commit is contained in:
parent
69b62c62a0
commit
4b4640dbcb
@ -46,11 +46,10 @@ type Key = IDBValidKey;
|
|||||||
type Value = unknown;
|
type Value = unknown;
|
||||||
|
|
||||||
enum TransactionLevel {
|
enum TransactionLevel {
|
||||||
Disconnected = 0,
|
None = 0,
|
||||||
Connected = 1,
|
Read = 1,
|
||||||
Read = 2,
|
Write = 2,
|
||||||
Write = 3,
|
VersionChange = 3,
|
||||||
VersionChange = 4,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ObjectStore {
|
interface ObjectStore {
|
||||||
@ -83,12 +82,18 @@ interface Database {
|
|||||||
|
|
||||||
txLevel: TransactionLevel;
|
txLevel: TransactionLevel;
|
||||||
|
|
||||||
|
txOwnerConnectionCookie?: string;
|
||||||
|
txOwnerTransactionCookie?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Object stores that the transaction is allowed to access.
|
* Object stores that the transaction is allowed to access.
|
||||||
*/
|
*/
|
||||||
txRestrictObjectStores: string[] | undefined;
|
txRestrictObjectStores: string[] | undefined;
|
||||||
|
|
||||||
connectionCookie: string | undefined;
|
/**
|
||||||
|
* Connection cookies of current connections.
|
||||||
|
*/
|
||||||
|
connectionCookies: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
@ -245,7 +250,7 @@ export class MemoryBackend implements Backend {
|
|||||||
private disconnectCond: AsyncCondition = new AsyncCondition();
|
private disconnectCond: AsyncCondition = new AsyncCondition();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Conditation that is triggered whenever a transaction finishes.
|
* Condition that is triggered whenever a transaction finishes.
|
||||||
*/
|
*/
|
||||||
private transactionDoneCond: AsyncCondition = new AsyncCondition();
|
private transactionDoneCond: AsyncCondition = new AsyncCondition();
|
||||||
|
|
||||||
@ -327,8 +332,8 @@ export class MemoryBackend implements Backend {
|
|||||||
deleted: false,
|
deleted: false,
|
||||||
committedObjectStores: objectStores,
|
committedObjectStores: objectStores,
|
||||||
committedSchema: structuredClone(schema),
|
committedSchema: structuredClone(schema),
|
||||||
connectionCookie: undefined,
|
connectionCookies: [],
|
||||||
txLevel: TransactionLevel.Disconnected,
|
txLevel: TransactionLevel.None,
|
||||||
txRestrictObjectStores: undefined,
|
txRestrictObjectStores: undefined,
|
||||||
};
|
};
|
||||||
this.databases[dbName] = db;
|
this.databases[dbName] = db;
|
||||||
@ -425,9 +430,9 @@ export class MemoryBackend implements Backend {
|
|||||||
if (myDb.txLevel < TransactionLevel.VersionChange) {
|
if (myDb.txLevel < TransactionLevel.VersionChange) {
|
||||||
throw new InvalidStateError();
|
throw new InvalidStateError();
|
||||||
}
|
}
|
||||||
if (myDb.connectionCookie !== tx.transactionCookie) {
|
// if (myDb.connectionCookie !== tx.transactionCookie) {
|
||||||
throw new InvalidAccessError();
|
// throw new InvalidAccessError();
|
||||||
}
|
// }
|
||||||
myDb.deleted = true;
|
myDb.deleted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -449,20 +454,18 @@ export class MemoryBackend implements Backend {
|
|||||||
committedSchema: schema,
|
committedSchema: schema,
|
||||||
deleted: false,
|
deleted: false,
|
||||||
committedObjectStores: {},
|
committedObjectStores: {},
|
||||||
txLevel: TransactionLevel.Disconnected,
|
txLevel: TransactionLevel.None,
|
||||||
connectionCookie: undefined,
|
connectionCookies: [],
|
||||||
txRestrictObjectStores: undefined,
|
txRestrictObjectStores: undefined,
|
||||||
};
|
};
|
||||||
this.databases[name] = database;
|
this.databases[name] = database;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (database.txLevel !== TransactionLevel.Disconnected) {
|
if (database.connectionCookies.includes(connectionCookie)) {
|
||||||
await this.disconnectCond.wait();
|
throw Error("already connected");
|
||||||
}
|
}
|
||||||
|
|
||||||
database.txLevel = TransactionLevel.Connected;
|
database.connectionCookies.push(connectionCookie);
|
||||||
database.txRestrictObjectStores = undefined;
|
|
||||||
database.connectionCookie = connectionCookie;
|
|
||||||
|
|
||||||
const myConn: Connection = {
|
const myConn: Connection = {
|
||||||
dbName: name,
|
dbName: name,
|
||||||
@ -494,7 +497,7 @@ export class MemoryBackend implements Backend {
|
|||||||
throw Error("db not found");
|
throw Error("db not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
while (myDb.txLevel !== TransactionLevel.Connected) {
|
while (myDb.txLevel !== TransactionLevel.None) {
|
||||||
if (this.enableTracing) {
|
if (this.enableTracing) {
|
||||||
console.log(`TRACING: beginTransaction -- waiting for others to close`);
|
console.log(`TRACING: beginTransaction -- waiting for others to close`);
|
||||||
}
|
}
|
||||||
@ -533,11 +536,13 @@ export class MemoryBackend implements Backend {
|
|||||||
throw Error("db not found");
|
throw Error("db not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
while (myDb.txLevel !== TransactionLevel.Connected) {
|
while (myDb.txLevel !== TransactionLevel.None) {
|
||||||
await this.transactionDoneCond.wait();
|
await this.transactionDoneCond.wait();
|
||||||
}
|
}
|
||||||
|
|
||||||
myDb.txLevel = TransactionLevel.VersionChange;
|
myDb.txLevel = TransactionLevel.VersionChange;
|
||||||
|
myDb.txOwnerConnectionCookie = conn.connectionCookie;
|
||||||
|
myDb.txOwnerTransactionCookie = transactionCookie;
|
||||||
myDb.txRestrictObjectStores = undefined;
|
myDb.txRestrictObjectStores = undefined;
|
||||||
|
|
||||||
this.connectionsByTransaction[transactionCookie] = myConn;
|
this.connectionsByTransaction[transactionCookie] = myConn;
|
||||||
@ -557,11 +562,13 @@ export class MemoryBackend implements Backend {
|
|||||||
}
|
}
|
||||||
if (!myConn.deleted) {
|
if (!myConn.deleted) {
|
||||||
const myDb = this.databases[myConn.dbName];
|
const myDb = this.databases[myConn.dbName];
|
||||||
if (myDb.txLevel != TransactionLevel.Connected) {
|
// if (myDb.connectionCookies.includes(conn.connectionCookie)) {
|
||||||
throw Error("invalid state");
|
// throw Error("invalid state");
|
||||||
}
|
// }
|
||||||
myDb.txLevel = TransactionLevel.Disconnected;
|
// FIXME: what if we're still in a transaction?
|
||||||
myDb.txRestrictObjectStores = undefined;
|
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();
|
||||||
@ -1390,7 +1397,7 @@ export class MemoryBackend implements Backend {
|
|||||||
throw Error("db not found");
|
throw Error("db not found");
|
||||||
}
|
}
|
||||||
if (db.txLevel < TransactionLevel.Write) {
|
if (db.txLevel < TransactionLevel.Write) {
|
||||||
throw Error("only allowed while running a transaction");
|
throw Error("store operation only allowed while running a transaction");
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
db.txRestrictObjectStores &&
|
db.txRestrictObjectStores &&
|
||||||
@ -1588,9 +1595,9 @@ export class MemoryBackend implements Backend {
|
|||||||
throw Error("db not found");
|
throw Error("db not found");
|
||||||
}
|
}
|
||||||
if (db.txLevel < TransactionLevel.Read) {
|
if (db.txLevel < TransactionLevel.Read) {
|
||||||
throw Error("only allowed while running a transaction");
|
throw Error("rollback is only allowed while running a transaction");
|
||||||
}
|
}
|
||||||
db.txLevel = TransactionLevel.Connected;
|
db.txLevel = TransactionLevel.None;
|
||||||
db.txRestrictObjectStores = undefined;
|
db.txRestrictObjectStores = undefined;
|
||||||
myConn.modifiedSchema = structuredClone(db.committedSchema);
|
myConn.modifiedSchema = structuredClone(db.committedSchema);
|
||||||
myConn.objectStoreMap = this.makeObjectStoreMap(db);
|
myConn.objectStoreMap = this.makeObjectStoreMap(db);
|
||||||
@ -1633,7 +1640,7 @@ export class MemoryBackend implements Backend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
db.committedSchema = structuredClone(myConn.modifiedSchema);
|
db.committedSchema = structuredClone(myConn.modifiedSchema);
|
||||||
db.txLevel = TransactionLevel.Connected;
|
db.txLevel = TransactionLevel.None;
|
||||||
db.txRestrictObjectStores = undefined;
|
db.txRestrictObjectStores = undefined;
|
||||||
|
|
||||||
db.committedObjectStores = {};
|
db.committedObjectStores = {};
|
||||||
|
@ -312,7 +312,43 @@ 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) {
|
||||||
throw Error("not implemented");
|
const transaction = this._effectiveObjectStore._transaction;
|
||||||
|
|
||||||
|
if (!transaction._active) {
|
||||||
|
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 (this._request) {
|
||||||
|
this._request.readyState = "pending";
|
||||||
|
}
|
||||||
|
|
||||||
|
const operation = async () => {
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
await this._iterate();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
transaction._execRequestAsync({
|
||||||
|
operation,
|
||||||
|
request: this._request,
|
||||||
|
source: this.source,
|
||||||
|
});
|
||||||
|
|
||||||
|
this._gotValue = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -760,8 +796,23 @@ export class BridgeIDBFactory {
|
|||||||
queueTask(async () => {
|
queueTask(async () => {
|
||||||
let dbconn: DatabaseConnection;
|
let dbconn: DatabaseConnection;
|
||||||
try {
|
try {
|
||||||
|
if (BridgeIDBFactory.enableTracing) {
|
||||||
|
console.log(
|
||||||
|
"TRACE: connecting to database",
|
||||||
|
);
|
||||||
|
}
|
||||||
dbconn = await this.backend.connectDatabase(name);
|
dbconn = await this.backend.connectDatabase(name);
|
||||||
|
if (BridgeIDBFactory.enableTracing) {
|
||||||
|
console.log(
|
||||||
|
"TRACE: connected!",
|
||||||
|
);
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
if (BridgeIDBFactory.enableTracing) {
|
||||||
|
console.log(
|
||||||
|
"TRACE: caught exception while trying to connect with backend",
|
||||||
|
);
|
||||||
|
}
|
||||||
request._finishWithError(err);
|
request._finishWithError(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -796,11 +847,24 @@ export class BridgeIDBFactory {
|
|||||||
cancelable: false,
|
cancelable: false,
|
||||||
});
|
});
|
||||||
event2.eventPath = [];
|
event2.eventPath = [];
|
||||||
|
if (BridgeIDBFactory.enableTracing) {
|
||||||
|
console.log(
|
||||||
|
"open() requested same version, dispatching 'success' event on transaction",
|
||||||
|
);
|
||||||
|
}
|
||||||
request.dispatchEvent(event2);
|
request.dispatchEvent(event2);
|
||||||
} else if (existingVersion < requestedVersion) {
|
} else if (existingVersion < requestedVersion) {
|
||||||
// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#dfn-steps-for-running-a-versionchange-transaction
|
// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#dfn-steps-for-running-a-versionchange-transaction
|
||||||
|
|
||||||
for (const otherConn of this.connections) {
|
for (const otherConn of this.connections) {
|
||||||
|
if (otherConn._closePending) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (BridgeIDBFactory.enableTracing) {
|
||||||
|
console.log(
|
||||||
|
"dispatching 'versionchange' event to other connection",
|
||||||
|
);
|
||||||
|
}
|
||||||
const event = new BridgeIDBVersionChangeEvent("versionchange", {
|
const event = new BridgeIDBVersionChangeEvent("versionchange", {
|
||||||
newVersion: version,
|
newVersion: version,
|
||||||
oldVersion: existingVersion,
|
oldVersion: existingVersion,
|
||||||
@ -809,6 +873,11 @@ export class BridgeIDBFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this._anyOpen()) {
|
if (this._anyOpen()) {
|
||||||
|
if (BridgeIDBFactory.enableTracing) {
|
||||||
|
console.log(
|
||||||
|
"other connections are still open, dispatching 'blocked' event to other connection",
|
||||||
|
);
|
||||||
|
}
|
||||||
const event = new BridgeIDBVersionChangeEvent("blocked", {
|
const event = new BridgeIDBVersionChangeEvent("blocked", {
|
||||||
newVersion: version,
|
newVersion: version,
|
||||||
oldVersion: existingVersion,
|
oldVersion: existingVersion,
|
||||||
@ -835,6 +904,10 @@ export class BridgeIDBFactory {
|
|||||||
|
|
||||||
db._upgradeTransaction = transaction;
|
db._upgradeTransaction = transaction;
|
||||||
|
|
||||||
|
if (BridgeIDBFactory.enableTracing) {
|
||||||
|
console.log("dispatching upgradeneeded event");
|
||||||
|
}
|
||||||
|
|
||||||
const event = new BridgeIDBVersionChangeEvent("upgradeneeded", {
|
const event = new BridgeIDBVersionChangeEvent("upgradeneeded", {
|
||||||
newVersion: version,
|
newVersion: version,
|
||||||
oldVersion: existingVersion,
|
oldVersion: existingVersion,
|
||||||
@ -866,6 +939,10 @@ export class BridgeIDBFactory {
|
|||||||
event2.eventPath = [];
|
event2.eventPath = [];
|
||||||
request.dispatchEvent(event2);
|
request.dispatchEvent(event2);
|
||||||
} else {
|
} else {
|
||||||
|
if (BridgeIDBFactory.enableTracing) {
|
||||||
|
console.log("dispatching 'success' event for opening db");
|
||||||
|
}
|
||||||
|
|
||||||
const event2 = new FakeEvent("success", {
|
const event2 = new FakeEvent("success", {
|
||||||
bubbles: false,
|
bubbles: false,
|
||||||
cancelable: false,
|
cancelable: false,
|
||||||
@ -1801,10 +1878,10 @@ export class BridgeIDBRequest extends FakeEventTarget implements IDBRequest {
|
|||||||
_result: any = null;
|
_result: any = null;
|
||||||
_error: Error | null | undefined = null;
|
_error: Error | null | undefined = null;
|
||||||
get source(): IDBObjectStore | IDBIndex | IDBCursor {
|
get source(): IDBObjectStore | IDBIndex | IDBCursor {
|
||||||
if (this._source) {
|
if (!this._source) {
|
||||||
return this._source;
|
throw Error("internal invariant failed: source is null");
|
||||||
}
|
}
|
||||||
throw Error("source is null");
|
return this._source;
|
||||||
}
|
}
|
||||||
_source:
|
_source:
|
||||||
| BridgeIDBCursor
|
| BridgeIDBCursor
|
||||||
@ -1875,6 +1952,16 @@ export class BridgeIDBOpenDBRequest
|
|||||||
public onupgradeneeded: EventListener | null = null;
|
public onupgradeneeded: EventListener | null = null;
|
||||||
public onblocked: EventListener | null = null;
|
public onblocked: EventListener | null = null;
|
||||||
|
|
||||||
|
get source(): IDBObjectStore | IDBIndex | IDBCursor {
|
||||||
|
// This is a type safety violation, but it is required by the
|
||||||
|
// IndexedDB standard.
|
||||||
|
// On the one hand, IDBOpenDBRequest implements IDBRequest.
|
||||||
|
// But that's technically impossible, as the "source" of the
|
||||||
|
// IDBOpenDB request may be null, while the one from IDBRequest
|
||||||
|
// may not be null.
|
||||||
|
return this._source as any;
|
||||||
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
// https://www.w3.org/TR/IndexedDB/#open-requests
|
// https://www.w3.org/TR/IndexedDB/#open-requests
|
||||||
|
@ -20,7 +20,7 @@ test("WPT idbfactory-open.htm", async (t) => {
|
|||||||
// IDBFactory.open() - database 'name' and 'version' are correctly set
|
// IDBFactory.open() - database 'name' and 'version' are correctly set
|
||||||
test("WPT idbfactory-open2.htm", async (t) => {
|
test("WPT idbfactory-open2.htm", async (t) => {
|
||||||
await new Promise<void>((resolve, reject) => {
|
await new Promise<void>((resolve, reject) => {
|
||||||
var database_name = __filename + "-database_name";
|
var database_name = t.title + "-database_name";
|
||||||
var open_rq = createdb(t, database_name, 13);
|
var open_rq = createdb(t, database_name, 13);
|
||||||
|
|
||||||
open_rq.onupgradeneeded = function (e) {};
|
open_rq.onupgradeneeded = function (e) {};
|
||||||
@ -28,7 +28,7 @@ test("WPT idbfactory-open2.htm", async (t) => {
|
|||||||
var db = e.target.result;
|
var db = e.target.result;
|
||||||
t.deepEqual(db.name, database_name, "db.name");
|
t.deepEqual(db.name, database_name, "db.name");
|
||||||
t.deepEqual(db.version, 13, "db.version");
|
t.deepEqual(db.version, 13, "db.version");
|
||||||
resolve;
|
resolve();
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
t.pass();
|
t.pass();
|
||||||
@ -63,7 +63,7 @@ test("WPT idbfactory-open3.htm", async (t) => {
|
|||||||
test("WPT idbfactory-open4.htm", async (t) => {
|
test("WPT idbfactory-open4.htm", async (t) => {
|
||||||
const indexedDB = idbFactory;
|
const indexedDB = idbFactory;
|
||||||
await new Promise<void>((resolve, reject) => {
|
await new Promise<void>((resolve, reject) => {
|
||||||
var open_rq = createdb(t, __filename + "-database_name");
|
var open_rq = createdb(t, t.title + "-database_name");
|
||||||
|
|
||||||
open_rq.onupgradeneeded = function (e: any) {
|
open_rq.onupgradeneeded = function (e: any) {
|
||||||
t.deepEqual(e.target.result.version, 1, "db.version");
|
t.deepEqual(e.target.result.version, 1, "db.version");
|
||||||
@ -80,7 +80,7 @@ test("WPT idbfactory-open4.htm", async (t) => {
|
|||||||
test("WPT idbfactory-open5.htm", async (t) => {
|
test("WPT idbfactory-open5.htm", async (t) => {
|
||||||
const indexedDB = idbFactory;
|
const indexedDB = idbFactory;
|
||||||
await new Promise<void>((resolve, reject) => {
|
await new Promise<void>((resolve, reject) => {
|
||||||
var open_rq = createdb(t, __filename + "-database_name");
|
var open_rq = createdb(t, t.title + "-database_name");
|
||||||
|
|
||||||
open_rq.onupgradeneeded = function () {};
|
open_rq.onupgradeneeded = function () {};
|
||||||
open_rq.onsuccess = function (e: any) {
|
open_rq.onsuccess = function (e: any) {
|
||||||
@ -100,7 +100,6 @@ test("WPT idbfactory-open6.htm", async (t) => {
|
|||||||
const indexedDB = idbFactory;
|
const indexedDB = idbFactory;
|
||||||
await new Promise<void>((resolve, reject) => {
|
await new Promise<void>((resolve, reject) => {
|
||||||
var open_rq = createdb(t, undefined, 13);
|
var open_rq = createdb(t, undefined, 13);
|
||||||
var did_upgrade = false;
|
|
||||||
var open_rq2: any;
|
var open_rq2: any;
|
||||||
|
|
||||||
open_rq.onupgradeneeded = function () {};
|
open_rq.onupgradeneeded = function () {};
|
||||||
@ -115,8 +114,10 @@ test("WPT idbfactory-open6.htm", async (t) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function open_previous_db(e: any) {
|
function open_previous_db(e: any) {
|
||||||
|
t.log("opening previous DB");
|
||||||
var open_rq3 = indexedDB.open(e.target.result.name, 13);
|
var open_rq3 = indexedDB.open(e.target.result.name, 13);
|
||||||
open_rq3.onerror = function (e: any) {
|
open_rq3.onerror = function (e: any) {
|
||||||
|
t.log("got open error");
|
||||||
t.deepEqual(e.target.error.name, "VersionError", "e.target.error.name");
|
t.deepEqual(e.target.error.name, "VersionError", "e.target.error.name");
|
||||||
open_rq2.result.close();
|
open_rq2.result.close();
|
||||||
resolve();
|
resolve();
|
||||||
@ -506,6 +507,7 @@ test("WPT idbfactory-open12.htm", async (t) => {
|
|||||||
* Second test
|
* Second test
|
||||||
*/
|
*/
|
||||||
db.onversionchange = function () {
|
db.onversionchange = function () {
|
||||||
|
t.log("onversionchange called");
|
||||||
db.close();
|
db.close();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user