diff options
Diffstat (limited to 'packages')
| -rw-r--r-- | packages/idb-bridge/package.json | 4 | ||||
| -rw-r--r-- | packages/idb-bridge/src/MemoryBackend.ts | 27 | ||||
| -rw-r--r-- | packages/idb-bridge/src/backend-interface.ts | 6 | ||||
| -rw-r--r-- | packages/idb-bridge/src/bridge-idb.ts | 316 | ||||
| -rw-r--r-- | packages/idb-bridge/src/idb-wpt-ported/README | 3 | ||||
| -rw-r--r-- | packages/idb-bridge/src/idb-wpt-ported/keypath.test.ts | 191 | ||||
| -rw-r--r-- | packages/idb-bridge/src/idb-wpt-ported/value.test.ts | 46 | ||||
| -rw-r--r-- | packages/idb-bridge/src/idb-wpt-ported/wptsupport.ts | 30 | ||||
| -rw-r--r-- | packages/idb-bridge/src/util/injectKey.ts | 4 | ||||
| -rw-r--r-- | packages/idb-bridge/src/util/makeStoreKeyValue.ts | 10 | ||||
| -rw-r--r-- | packages/idb-bridge/src/util/normalizeKeyPath.ts | 41 | ||||
| -rw-r--r-- | packages/idb-bridge/src/util/structuredClone.ts | 21 | ||||
| -rw-r--r-- | packages/taler-wallet-core/src/util/query.ts | 6 | 
13 files changed, 558 insertions, 147 deletions
| diff --git a/packages/idb-bridge/package.json b/packages/idb-bridge/package.json index cfa0fa9f6..5f7b3d114 100644 --- a/packages/idb-bridge/package.json +++ b/packages/idb-bridge/package.json @@ -25,7 +25,9 @@      "typescript": "^4.1.3"    },    "dependencies": { -    "tslib": "^2.1.0" +    "tslib": "^2.1.0", +    "typeson": "^5.18.2", +    "typeson-registry": "^1.0.0-alpha.38"    },    "ava": {      "require": [ diff --git a/packages/idb-bridge/src/MemoryBackend.ts b/packages/idb-bridge/src/MemoryBackend.ts index 6a52a555f..2b4437bcf 100644 --- a/packages/idb-bridge/src/MemoryBackend.ts +++ b/packages/idb-bridge/src/MemoryBackend.ts @@ -27,7 +27,11 @@ import {    StoreLevel,    RecordStoreResponse,  } from "./backend-interface"; -import structuredClone from "./util/structuredClone"; +import { +  structuredClone, +  structuredEncapsulate, +  structuredRevive, +} from "./util/structuredClone";  import {    InvalidStateError,    InvalidAccessError, @@ -39,7 +43,12 @@ import compareKeys from "./util/cmp";  import { StoreKeyResult, makeStoreKeyValue } from "./util/makeStoreKeyValue";  import getIndexKeys from "./util/getIndexKeys";  import openPromise from "./util/openPromise"; -import { IDBKeyPath, IDBKeyRange, IDBTransactionMode, IDBValidKey } from "./idbtypes"; +import { +  IDBKeyPath, +  IDBKeyRange, +  IDBTransactionMode, +  IDBValidKey, +} from "./idbtypes";  import { BridgeIDBKeyRange } from "./bridge-idb";  type Key = IDBValidKey; @@ -742,7 +751,7 @@ export class MemoryBackend implements Backend {    createObjectStore(      btx: DatabaseTransaction,      name: string, -    keyPath: string | string[] | null, +    keyPath: string[] | null,      autoIncrement: boolean,    ): void {      if (this.enableTracing) { @@ -776,9 +785,6 @@ export class MemoryBackend implements Backend {      if (!schema) {        throw Error("no schema for versionchange tx");      } -    if (Array.isArray(keyPath)) { -      throw Error("array key path not supported for object stores"); -    }      schema.objectStores[name] = {        autoIncrement,        keyPath, @@ -791,7 +797,7 @@ export class MemoryBackend implements Backend {      btx: DatabaseTransaction,      indexName: string,      objectStoreName: string, -    keyPath: IDBKeyPath, +    keyPath: string[],      multiEntry: boolean,      unique: boolean,    ): void { @@ -1401,9 +1407,10 @@ export class MemoryBackend implements Backend {          schema.objectStores[storeReq.objectStoreName].autoIncrement;        const keyPath = schema.objectStores[storeReq.objectStoreName].keyPath;        let storeKeyResult: StoreKeyResult; +      const revivedValue = structuredRevive(storeReq.value);        try {          storeKeyResult = makeStoreKeyValue( -          storeReq.value, +          revivedValue,            storeReq.key,            keygen,            autoIncrement, @@ -1413,7 +1420,9 @@ export class MemoryBackend implements Backend {          if (e instanceof DataError) {            const kp = JSON.stringify(keyPath);            const n = storeReq.objectStoreName; -          const m = `Could not extract key from value, objectStore=${n}, keyPath=${kp}`; +          const m = `Could not extract key from value, objectStore=${n}, keyPath=${kp}, value=${JSON.stringify( +            storeReq.value, +          )}`;            if (this.enableTracing) {              console.error(e);              console.error("value was:", storeReq.value); diff --git a/packages/idb-bridge/src/backend-interface.ts b/packages/idb-bridge/src/backend-interface.ts index 756a5b967..a9e3e960e 100644 --- a/packages/idb-bridge/src/backend-interface.ts +++ b/packages/idb-bridge/src/backend-interface.ts @@ -25,14 +25,14 @@ import {  /** @public */  export interface ObjectStoreProperties { -  keyPath: IDBKeyPath | null; +  keyPath: string[] | null;    autoIncrement: boolean;    indexes: { [nameame: string]: IndexProperties };  }  /** @public */  export interface IndexProperties { -  keyPath: IDBKeyPath; +  keyPath: string[];    multiEntry: boolean;    unique: boolean;  } @@ -199,7 +199,7 @@ export interface Backend {      btx: DatabaseTransaction,      indexName: string,      objectStoreName: string, -    keyPath: IDBKeyPath, +    keyPath: string | string[],      multiEntry: boolean,      unique: boolean,    ): void; diff --git a/packages/idb-bridge/src/bridge-idb.ts b/packages/idb-bridge/src/bridge-idb.ts index 2bced800d..ce09fcb8e 100644 --- a/packages/idb-bridge/src/bridge-idb.ts +++ b/packages/idb-bridge/src/bridge-idb.ts @@ -25,7 +25,23 @@ import {    Schema,    StoreLevel,  } from "./backend-interface"; -import { EventListener, IDBCursorDirection, IDBKeyPath, IDBKeyRange, IDBTransactionMode, IDBValidKey } from "./idbtypes"; +import { +  DOMException, +  DOMStringList, +  EventListener, +  IDBCursor, +  IDBCursorDirection, +  IDBDatabase, +  IDBIndex, +  IDBKeyPath, +  IDBKeyRange, +  IDBObjectStore, +  IDBOpenDBRequest, +  IDBRequest, +  IDBTransaction, +  IDBTransactionMode, +  IDBValidKey, +} from "./idbtypes";  import compareKeys from "./util/cmp";  import enforceRange from "./util/enforceRange";  import { @@ -42,9 +58,10 @@ import {  import { fakeDOMStringList } from "./util/fakeDOMStringList";  import FakeEvent from "./util/FakeEvent";  import FakeEventTarget from "./util/FakeEventTarget"; +import { normalizeKeyPath } from "./util/normalizeKeyPath";  import openPromise from "./util/openPromise";  import queueTask from "./util/queueTask"; -import structuredClone from "./util/structuredClone"; +import { structuredClone, structuredEncapsulate, structuredRevive } from "./util/structuredClone";  import validateKeyPath from "./util/validateKeyPath";  import valueToKey from "./util/valueToKey"; @@ -87,7 +104,7 @@ function simplifyRange(   *   * @public   */ -export class BridgeIDBCursor { +export class BridgeIDBCursor implements IDBCursor {    _request: BridgeIDBRequest | undefined;    private _gotValue: boolean = false; @@ -127,7 +144,7 @@ export class BridgeIDBCursor {      if (this.source instanceof BridgeIDBObjectStore) {        return this.source;      } -    return this.source.objectStore; +    return this.source._objectStore;    }    get _backend(): Backend { @@ -149,15 +166,23 @@ export class BridgeIDBCursor {      /* For babel */    } -  get key() { -    return this._key; +  get key(): IDBValidKey { +    const k = this._key; +    if (k === null || k === undefined) { +      throw Error("no key"); +    } +    return k;    }    set key(val) {      /* For babel */    } -  get primaryKey() { -    return this._primaryKey; +  get primaryKey(): IDBValidKey { +    const k = this._primaryKey; +    if (k === 0 || k === undefined) { +      throw Error("no key"); +    } +    return k;    }    set primaryKey(val) { @@ -221,7 +246,7 @@ export class BridgeIDBCursor {      this._primaryKey = response.primaryKeys![0];      if (!this._keyOnly) { -      this._value = response.values![0]; +      this._value = structuredRevive(response.values![0]);      }      this._gotValue = true; @@ -239,7 +264,7 @@ export class BridgeIDBCursor {        throw new TypeError();      } -    const transaction = this._effectiveObjectStore.transaction; +    const transaction = this._effectiveObjectStore._transaction;      if (transaction._state !== "active") {        throw new TransactionInactiveError(); @@ -266,7 +291,7 @@ export class BridgeIDBCursor {      const storeReq: RecordStoreRequest = {        key: this._primaryKey, -      value: value, +      value: structuredEncapsulate(value),        objectStoreName: this._objectStoreName,        storeLevel: StoreLevel.UpdateExisting,      }; @@ -295,7 +320,7 @@ export class BridgeIDBCursor {     * http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#widl-IDBCursor-continue-void-any-key     */    public continue(key?: IDBValidKey) { -    const transaction = this._effectiveObjectStore.transaction; +    const transaction = this._effectiveObjectStore._transaction;      if (transaction._state !== "active") {        throw new TransactionInactiveError(); @@ -357,7 +382,7 @@ export class BridgeIDBCursor {    }    public delete() { -    const transaction = this._effectiveObjectStore.transaction; +    const transaction = this._effectiveObjectStore._transaction;      if (transaction._state !== "active") {        throw new TransactionInactiveError(); @@ -547,7 +572,7 @@ export class BridgeIDBDatabase extends FakeEventTarget {      transaction._backend.createObjectStore(        backendTx,        name, -      keyPath, +      (keyPath !== null) ? normalizeKeyPath(keyPath) : null,        autoIncrement,      ); @@ -583,7 +608,7 @@ export class BridgeIDBDatabase extends FakeEventTarget {          return (            transaction._state === "active" &&            transaction.mode === "versionchange" && -          transaction.db === this +          transaction._db === this          );        },      ); @@ -656,7 +681,7 @@ export class BridgeIDBFactory {    // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#widl-IDBFactory-deleteDatabase-IDBOpenDBRequest-DOMString-name    public deleteDatabase(name: string): BridgeIDBOpenDBRequest {      const request = new BridgeIDBOpenDBRequest(); -    request.source = null; +    request._source = null;      queueTask(async () => {        const databases = await this.backend.getDatabases(); @@ -709,7 +734,7 @@ export class BridgeIDBFactory {    // 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 -  public open(name: string, version?: number) { +  public open(name: string, version?: number): BridgeIDBOpenDBRequest {      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 @@ -761,9 +786,7 @@ export class BridgeIDBFactory {          });          event2.eventPath = [request];          request.dispatchEvent(event2); -      } - -      if (existingVersion < requestedVersion) { +      } else if (existingVersion < requestedVersion) {          // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#dfn-steps-for-running-a-versionchange-transaction          for (const otherConn of this.connections) { @@ -803,7 +826,9 @@ export class BridgeIDBFactory {          request.transaction = transaction;          request.dispatchEvent(event); +        console.log("awaiting until initial transaction is done");          await transaction._waitDone(); +        console.log("initial transaction is done");          // We don't explicitly exit the versionchange transaction,          // since this is already done by the BridgeIDBTransaction. @@ -842,47 +867,51 @@ export class BridgeIDBFactory {  const confirmActiveTransaction = (    index: BridgeIDBIndex,  ): BridgeIDBTransaction => { -  if (index._deleted || index.objectStore._deleted) { +  if (index._deleted || index._objectStore._deleted) {      throw new InvalidStateError();    } -  if (index.objectStore.transaction._state !== "active") { +  if (index._objectStore._transaction._state !== "active") {      throw new TransactionInactiveError();    } -  return index.objectStore.transaction; +  return index._objectStore._transaction;  };  // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#idl-def-IDBIndex  /** @public */ -export class BridgeIDBIndex { -  objectStore: BridgeIDBObjectStore; +export class BridgeIDBIndex implements IDBIndex { +  _objectStore: BridgeIDBObjectStore; + +  get objectStore(): IDBObjectStore { +    return this._objectStore; +  }    get _schema(): Schema { -    return this.objectStore.transaction.db._schema; +    return this._objectStore._transaction._db._schema;    } -  get keyPath(): IDBKeyPath { -    return this._schema.objectStores[this.objectStore.name].indexes[this._name] +  get keyPath(): IDBKeyPath | IDBKeyPath[] { +    return this._schema.objectStores[this._objectStore.name].indexes[this._name]        .keyPath;    }    get multiEntry(): boolean { -    return this._schema.objectStores[this.objectStore.name].indexes[this._name] +    return this._schema.objectStores[this._objectStore.name].indexes[this._name]        .multiEntry;    }    get unique(): boolean { -    return this._schema.objectStores[this.objectStore.name].indexes[this._name] +    return this._schema.objectStores[this._objectStore.name].indexes[this._name]        .unique;    }    get _backend(): Backend { -    return this.objectStore._backend; +    return this._objectStore._backend;    }    _confirmActiveTransaction(): { btx: DatabaseTransaction } { -    return this.objectStore._confirmActiveTransaction(); +    return this._objectStore._confirmActiveTransaction();    }    private _name: string; @@ -891,7 +920,7 @@ export class BridgeIDBIndex {    constructor(objectStore: BridgeIDBObjectStore, name: string) {      this._name = name; -    this.objectStore = objectStore; +    this._objectStore = objectStore;    }    get name() { @@ -900,9 +929,9 @@ export class BridgeIDBIndex {    // https://w3c.github.io/IndexedDB/#dom-idbindex-name    set name(name: any) { -    const transaction = this.objectStore.transaction; +    const transaction = this._objectStore._transaction; -    if (!transaction.db._runningVersionchangeTransaction) { +    if (!transaction._db._runningVersionchangeTransaction) {        throw new InvalidStateError();      } @@ -919,9 +948,9 @@ export class BridgeIDBIndex {        return;      } -    this._backend.renameIndex(btx, this.objectStore.name, oldName, newName); +    this._backend.renameIndex(btx, this._objectStore.name, oldName, newName); -    if (this.objectStore.indexNames.indexOf(name) >= 0) { +    if (this._objectStore._indexNames.indexOf(name) >= 0) {        throw new ConstraintError();      }    } @@ -942,12 +971,12 @@ export class BridgeIDBIndex {      }      const request = new BridgeIDBRequest(); -    request.source = this; -    request.transaction = this.objectStore.transaction; +    request._source = this; +    request.transaction = this._objectStore._transaction;      const cursor = new BridgeIDBCursorWithValue(        this, -      this.objectStore.name, +      this._objectStore.name,        this._name,        range,        direction, @@ -958,7 +987,7 @@ export class BridgeIDBIndex {        return cursor._iterate();      }; -    return this.objectStore.transaction._execRequestAsync({ +    return this._objectStore._transaction._execRequestAsync({        operation,        request,        source: this, @@ -981,12 +1010,12 @@ export class BridgeIDBIndex {      }      const request = new BridgeIDBRequest(); -    request.source = this; -    request.transaction = this.objectStore.transaction; +    request._source = this; +    request.transaction = this._objectStore._transaction;      const cursor = new BridgeIDBCursor(        this, -      this.objectStore.name, +      this._objectStore.name,        this._name,        range,        direction, @@ -994,7 +1023,7 @@ export class BridgeIDBIndex {        true,      ); -    return this.objectStore.transaction._execRequestAsync({ +    return this._objectStore._transaction._execRequestAsync({        operation: cursor._iterate.bind(cursor),        request,        source: this, @@ -1013,7 +1042,7 @@ export class BridgeIDBIndex {        indexName: this._name,        limit: 1,        range: key, -      objectStoreName: this.objectStore._name, +      objectStoreName: this._objectStore._name,        resultLevel: ResultLevel.Full,      }; @@ -1027,17 +1056,20 @@ export class BridgeIDBIndex {        if (!values) {          throw Error("invariant violated");        } -      return values[0]; +      return structuredRevive(values[0]);      }; -    return this.objectStore.transaction._execRequestAsync({ +    return this._objectStore._transaction._execRequestAsync({        operation,        source: this,      });    }    // http://w3c.github.io/IndexedDB/#dom-idbindex-getall -  public getAll(query?: BridgeIDBKeyRange | IDBValidKey, count?: number) { +  public getAll( +    query?: BridgeIDBKeyRange | IDBValidKey, +    count?: number, +  ): IDBRequest<any[]> {      throw Error("not implemented");    } @@ -1054,7 +1086,7 @@ export class BridgeIDBIndex {        indexName: this._name,        limit: 1,        range: key, -      objectStoreName: this.objectStore._name, +      objectStoreName: this._objectStore._name,        resultLevel: ResultLevel.OnlyKeys,      }; @@ -1071,14 +1103,17 @@ export class BridgeIDBIndex {        return primaryKeys[0];      }; -    return this.objectStore.transaction._execRequestAsync({ +    return this._objectStore._transaction._execRequestAsync({        operation,        source: this,      });    }    // http://w3c.github.io/IndexedDB/#dom-idbindex-getallkeys -  public getAllKeys(query?: BridgeIDBKeyRange | IDBValidKey, count?: number) { +  public getAllKeys( +    query?: BridgeIDBKeyRange | IDBValidKey, +    count?: number, +  ): IDBRequest<IDBValidKey[]> {      throw Error("not implemented");    } @@ -1098,7 +1133,7 @@ export class BridgeIDBIndex {        indexName: this._name,        limit: 1,        range: key, -      objectStoreName: this.objectStore._name, +      objectStoreName: this._objectStore._name,        resultLevel: ResultLevel.OnlyCount,      }; @@ -1108,7 +1143,7 @@ export class BridgeIDBIndex {        return result.count;      }; -    return this.objectStore.transaction._execRequestAsync({ +    return this._objectStore._transaction._execRequestAsync({        operation,        source: this,      }); @@ -1231,36 +1266,44 @@ export class BridgeIDBKeyRange {  // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#object-store  /** @public */ -export class BridgeIDBObjectStore { +export class BridgeIDBObjectStore implements IDBObjectStore {    _indexesCache: Map<string, BridgeIDBIndex> = new Map(); -  transaction: BridgeIDBTransaction; +  _transaction: BridgeIDBTransaction; + +  get transaction(): IDBTransaction { +    return this._transaction; +  }    get autoIncrement(): boolean {      return this._schema.objectStores[this._name].autoIncrement;    } -  get indexNames(): FakeDOMStringList { +  get _indexNames(): FakeDOMStringList {      return fakeDOMStringList(        Object.keys(this._schema.objectStores[this._name].indexes), -    ).sort(); +    ).sort() +  } + +  get indexNames(): DOMStringList { +    return this._indexNames as DOMStringList;    } -  get keyPath(): IDBKeyPath | null { -    return this._schema.objectStores[this._name].keyPath; +  get keyPath(): IDBKeyPath | IDBKeyPath[] { +    return this._schema.objectStores[this._name].keyPath!;    }    _name: string;    get _schema(): Schema { -    return this.transaction.db._schema; +    return this._transaction._db._schema;    }    _deleted: boolean = false;    constructor(transaction: BridgeIDBTransaction, name: string) {      this._name = name; -    this.transaction = transaction; +    this._transaction = transaction;    }    get name() { @@ -1268,15 +1311,15 @@ export class BridgeIDBObjectStore {    }    get _backend(): Backend { -    return this.transaction.db._backend; +    return this._transaction._db._backend;    }    get _backendConnection(): DatabaseConnection { -    return this.transaction.db._backendConnection; +    return this._transaction._db._backendConnection;    }    _confirmActiveTransaction(): { btx: DatabaseTransaction } { -    const btx = this.transaction._backendTransaction; +    const btx = this._transaction._backendTransaction;      if (!btx) {        throw new InvalidStateError();      } @@ -1285,9 +1328,9 @@ export class BridgeIDBObjectStore {    // http://w3c.github.io/IndexedDB/#dom-idbobjectstore-name    set name(newName: any) { -    const transaction = this.transaction; +    const transaction = this._transaction; -    if (!transaction.db._runningVersionchangeTransaction) { +    if (!transaction._db._runningVersionchangeTransaction) {        throw new InvalidStateError();      } @@ -1302,7 +1345,7 @@ export class BridgeIDBObjectStore {      }      this._backend.renameObjectStore(btx, oldName, newName); -    this.transaction.db._schema = this._backend.getSchema( +    this._transaction._db._schema = this._backend.getSchema(        this._backendConnection,      );    } @@ -1311,7 +1354,7 @@ export class BridgeIDBObjectStore {      if (BridgeIDBFactory.enableTracing) {        console.log(`TRACE: IDBObjectStore._store`);      } -    if (this.transaction.mode === "readonly") { +    if (this._transaction.mode === "readonly") {        throw new ReadOnlyError();      }      const operation = async () => { @@ -1319,7 +1362,7 @@ export class BridgeIDBObjectStore {        const result = await this._backend.storeRecord(btx, {          objectStoreName: this._name,          key: key, -        value: value, +        value: structuredEncapsulate(value),          storeLevel: overwrite            ? StoreLevel.AllowOverwrite            : StoreLevel.NoOverwrite, @@ -1327,7 +1370,7 @@ export class BridgeIDBObjectStore {        return result.key;      }; -    return this.transaction._execRequestAsync({ operation, source: this }); +    return this._transaction._execRequestAsync({ operation, source: this });    }    public put(value: any, key?: IDBValidKey) { @@ -1349,7 +1392,7 @@ export class BridgeIDBObjectStore {        throw new TypeError();      } -    if (this.transaction.mode === "readonly") { +    if (this._transaction.mode === "readonly") {        throw new ReadOnlyError();      } @@ -1366,7 +1409,7 @@ export class BridgeIDBObjectStore {        return this._backend.deleteRecord(btx, this._name, keyRange);      }; -    return this.transaction._execRequestAsync({ +    return this._transaction._execRequestAsync({        operation,        source: this,      }); @@ -1424,31 +1467,39 @@ export class BridgeIDBObjectStore {        if (!values) {          throw Error("invariant violated");        } -      return values[0]; +      return structuredRevive(values[0]);      }; -    return this.transaction._execRequestAsync({ +    return this._transaction._execRequestAsync({        operation,        source: this,      });    }    // http://w3c.github.io/IndexedDB/#dom-idbobjectstore-getall -  public getAll(query?: BridgeIDBKeyRange | IDBValidKey, count?: number) { +  public getAll( +    query?: BridgeIDBKeyRange | IDBValidKey, +    count?: number, +  ): IDBRequest<any[]> {      throw Error("not implemented");    }    // http://w3c.github.io/IndexedDB/#dom-idbobjectstore-getkey -  public getKey(key?: BridgeIDBKeyRange | IDBValidKey) { +  public getKey( +    key?: BridgeIDBKeyRange | IDBValidKey, +  ): IDBRequest<IDBValidKey | undefined> {      throw Error("not implemented");    }    // http://w3c.github.io/IndexedDB/#dom-idbobjectstore-getallkeys -  public getAllKeys(query?: BridgeIDBKeyRange | IDBValidKey, count?: number) { +  public getAllKeys( +    query?: BridgeIDBKeyRange | IDBValidKey, +    count?: number, +  ): IDBRequest<any[]> {      throw Error("not implemented");    } -  public clear() { +  public clear(): IDBRequest<undefined> {      throw Error("not implemented");    } @@ -1464,8 +1515,8 @@ export class BridgeIDBObjectStore {      }      const request = new BridgeIDBRequest(); -    request.source = this; -    request.transaction = this.transaction; +    request._source = this; +    request.transaction = this._transaction;      const cursor = new BridgeIDBCursorWithValue(        this, @@ -1476,7 +1527,7 @@ export class BridgeIDBObjectStore {        request,      ); -    return this.transaction._execRequestAsync({ +    return this._transaction._execRequestAsync({        operation: () => cursor._iterate(),        request,        source: this, @@ -1499,8 +1550,8 @@ export class BridgeIDBObjectStore {      }      const request = new BridgeIDBRequest(); -    request.source = this; -    request.transaction = this.transaction; +    request._source = this; +    request.transaction = this._transaction;      const cursor = new BridgeIDBCursor(        this, @@ -1512,7 +1563,7 @@ export class BridgeIDBObjectStore {        true,      ); -    return this.transaction._execRequestAsync({ +    return this._transaction._execRequestAsync({        operation: cursor._iterate.bind(cursor),        request,        source: this, @@ -1530,7 +1581,7 @@ export class BridgeIDBObjectStore {        throw new TypeError();      } -    if (!this.transaction.db._runningVersionchangeTransaction) { +    if (!this._transaction._db._runningVersionchangeTransaction) {        throw new InvalidStateError();      } @@ -1545,11 +1596,11 @@ export class BridgeIDBObjectStore {          ? optionalParameters.unique          : false; -    if (this.transaction.mode !== "versionchange") { +    if (this._transaction.mode !== "versionchange") {        throw new InvalidStateError();      } -    if (this.indexNames.indexOf(indexName) >= 0) { +    if (this._indexNames.indexOf(indexName) >= 0) {        throw new ConstraintError();      } @@ -1563,7 +1614,7 @@ export class BridgeIDBObjectStore {        btx,        indexName,        this._name, -      keyPath, +      normalizeKeyPath(keyPath),        multiEntry,        unique,      ); @@ -1577,7 +1628,7 @@ export class BridgeIDBObjectStore {        throw new TypeError();      } -    if (this.transaction._state === "finished") { +    if (this._transaction._state === "finished") {        throw new InvalidStateError();      } @@ -1594,11 +1645,11 @@ export class BridgeIDBObjectStore {        throw new TypeError();      } -    if (this.transaction.mode !== "versionchange") { +    if (this._transaction.mode !== "versionchange") {        throw new InvalidStateError();      } -    if (!this.transaction.db._runningVersionchangeTransaction) { +    if (!this._transaction._db._runningVersionchangeTransaction) {        throw new InvalidStateError();      } @@ -1638,7 +1689,7 @@ export class BridgeIDBObjectStore {        return result.count;      }; -    return this.transaction._execRequestAsync({ operation, source: this }); +    return this._transaction._execRequestAsync({ operation, source: this });    }    public toString() { @@ -1647,10 +1698,20 @@ export class BridgeIDBObjectStore {  }  /** @public */ -export class BridgeIDBRequest extends FakeEventTarget { +export class BridgeIDBRequest extends FakeEventTarget implements IDBRequest {    _result: any = null;    _error: Error | null | undefined = null; -  source: BridgeIDBCursor | BridgeIDBIndex | BridgeIDBObjectStore | null = null; +  get source(): IDBObjectStore | IDBIndex | IDBCursor { +    if (this._source) { +      return this._source; +    } +    throw Error("source is null"); +  } +  _source: +    | BridgeIDBCursor +    | BridgeIDBIndex +    | BridgeIDBObjectStore +    | null = null;    transaction: BridgeIDBTransaction | null = null;    readyState: "done" | "pending" = "pending";    onsuccess: EventListener | null = null; @@ -1708,14 +1769,16 @@ export class BridgeIDBRequest extends FakeEventTarget {  }  /** @public */ -export class BridgeIDBOpenDBRequest extends BridgeIDBRequest { +export class BridgeIDBOpenDBRequest +  extends BridgeIDBRequest +  implements IDBOpenDBRequest {    public onupgradeneeded: EventListener | null = null;    public onblocked: EventListener | null = null;    constructor() {      super();      // https://www.w3.org/TR/IndexedDB/#open-requests -    this.source = null; +    this._source = null;    }    public toString() { @@ -1725,17 +1788,32 @@ export class BridgeIDBOpenDBRequest extends BridgeIDBRequest {  // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#transaction  /** @public */ -export class BridgeIDBTransaction extends FakeEventTarget { +export class BridgeIDBTransaction +  extends FakeEventTarget +  implements IDBTransaction {    public _state: "active" | "inactive" | "committing" | "finished" = "active";    public _started = false;    public _objectStoresCache: Map<string, BridgeIDBObjectStore> = new Map();    public _backendTransaction?: DatabaseTransaction; -  public objectStoreNames: FakeDOMStringList; +  public _objectStoreNames: FakeDOMStringList; +  get objectStoreNames(): DOMStringList { +    return this._objectStoreNames as DOMStringList; +  }    public mode: IDBTransactionMode; -  public db: BridgeIDBDatabase; -  public error: Error | null = null; +  public _db: BridgeIDBDatabase; + +  get db(): IDBDatabase { +    return this.db; +  } + +  public _error: Error | null = null; + +  get error(): DOMException { +    return this._error as DOMException; +  } +    public onabort: EventListener | null = null;    public oncomplete: EventListener | null = null;    public onerror: EventListener | null = null; @@ -1750,7 +1828,7 @@ export class BridgeIDBTransaction extends FakeEventTarget {    }> = [];    get _backend(): Backend { -    return this.db._backend; +    return this._db._backend;    }    constructor( @@ -1768,10 +1846,10 @@ export class BridgeIDBTransaction extends FakeEventTarget {      this._scope = new Set(storeNames);      this._backendTransaction = backendTransaction;      this.mode = mode; -    this.db = db; -    this.objectStoreNames = fakeDOMStringList(Array.from(this._scope).sort()); +    this._db = db; +    this._objectStoreNames = fakeDOMStringList(Array.from(this._scope).sort()); -    this.db._transactions.push(this); +    this._db._transactions.push(this);    }    // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#dfn-steps-for-aborting-a-transaction @@ -1781,14 +1859,14 @@ export class BridgeIDBTransaction extends FakeEventTarget {      if (errName !== null) {        const e = new Error();        e.name = errName; -      this.error = e; +      this._error = e;      }      // Should this directly remove from _requests?      for (const { request } of this._requests) {        if (request.readyState !== "done") {          request.readyState = "done"; // This will cancel execution of this request's operation -        if (request.source) { +        if (request._source) {            request.result = undefined;            request.error = new AbortError(); @@ -1796,7 +1874,7 @@ export class BridgeIDBTransaction extends FakeEventTarget {              bubbles: true,              cancelable: true,            }); -          event.eventPath = [this.db, this]; +          event.eventPath = [this._db, this];            request.dispatchEvent(event);          }        } @@ -1813,7 +1891,7 @@ export class BridgeIDBTransaction extends FakeEventTarget {          bubbles: true,          cancelable: false,        }); -      event.eventPath = [this.db]; +      event.eventPath = [this._db];        this.dispatchEvent(event);      });    } @@ -1828,7 +1906,7 @@ export class BridgeIDBTransaction extends FakeEventTarget {    }    // http://w3c.github.io/IndexedDB/#dom-idbtransaction-objectstore -  public objectStore(name: string) { +  public objectStore(name: string): BridgeIDBObjectStore {      if (this._state !== "active") {        throw new InvalidStateError();      } @@ -1858,7 +1936,7 @@ export class BridgeIDBTransaction extends FakeEventTarget {          request = new BridgeIDBRequest();        } else {          request = new BridgeIDBRequest(); -        request.source = source; +        request._source = source;          request.transaction = (source as any).transaction;        }      } @@ -1884,7 +1962,7 @@ export class BridgeIDBTransaction extends FakeEventTarget {      if (!this._backendTransaction) {        this._backendTransaction = await this._backend.beginTransaction( -        this.db._backendConnection, +        this._db._backendConnection,          Array.from(this._scope),          this.mode,        ); @@ -1905,7 +1983,7 @@ export class BridgeIDBTransaction extends FakeEventTarget {      }      if (request && operation) { -      if (!request.source) { +      if (!request._source) {          // Special requests like indexes that just need to run some code, with error handling already built into          // operation          await operation(); @@ -1933,7 +2011,7 @@ export class BridgeIDBTransaction extends FakeEventTarget {            });            try { -            event.eventPath = [request, this, this.db]; +            event.eventPath = [request, this, this._db];              request.dispatchEvent(event);            } catch (err) {              if (this._state !== "committing") { @@ -1959,7 +2037,7 @@ export class BridgeIDBTransaction extends FakeEventTarget {            });            try { -            event.eventPath = [this.db, this]; +            event.eventPath = [this._db, this];              request.dispatchEvent(event);            } catch (err) {              if (this._state !== "committing") { @@ -1994,20 +2072,20 @@ export class BridgeIDBTransaction extends FakeEventTarget {        this._state = "finished"; -      if (!this.error) { +      if (!this._error) {          if (BridgeIDBFactory.enableTracing) {            console.log("dispatching 'complete' event on transaction");          }          const event = new FakeEvent("complete"); -        event.eventPath = [this, this.db]; +        event.eventPath = [this, this._db];          this.dispatchEvent(event);        } -      const idx = this.db._transactions.indexOf(this); +      const idx = this._db._transactions.indexOf(this);        if (idx < 0) {          throw Error("invariant failed");        } -      this.db._transactions.splice(idx, 1); +      this._db._transactions.splice(idx, 1);        this._resolveWait();      } diff --git a/packages/idb-bridge/src/idb-wpt-ported/README b/packages/idb-bridge/src/idb-wpt-ported/README new file mode 100644 index 000000000..e0b665aab --- /dev/null +++ b/packages/idb-bridge/src/idb-wpt-ported/README @@ -0,0 +1,3 @@ +This directory contains test cases from the W3C Web Platform Tests suite for IndexedDB. + +The original code for these tests can be found here: https://github.com/web-platform-tests/wpt/tree/master/IndexedDB
\ No newline at end of file diff --git a/packages/idb-bridge/src/idb-wpt-ported/keypath.test.ts b/packages/idb-bridge/src/idb-wpt-ported/keypath.test.ts new file mode 100644 index 000000000..61e416a53 --- /dev/null +++ b/packages/idb-bridge/src/idb-wpt-ported/keypath.test.ts @@ -0,0 +1,191 @@ +import test from "ava"; +import { assert_key_equals, createdb } from "./wptsupport"; + +test("WPT test keypath.htm", async (t) => { +  function keypath( +    keypath: any, +    objects: any[], +    expected_keys: any[], +    desc?: string, +  ) { +    return new Promise<void>((resolve, reject) => { +      console.log("key path", keypath); +      console.log("checking", desc); +      let db: any; +      const store_name = "store-" + Date.now() + Math.random(); + +      var open_rq = createdb(t); +      open_rq.onupgradeneeded = function (e) { +        db = (e.target as any).result; +        var objStore = db.createObjectStore(store_name, { keyPath: keypath }); + +        for (var i = 0; i < objects.length; i++) objStore.add(objects[i]); +      }; + +      open_rq.onsuccess = function (e) { +        var actual_keys: any[] = [], +          rq = db.transaction(store_name).objectStore(store_name).openCursor(); + +        rq.onsuccess = (e: any) => { +          var cursor = e.target.result; + +          if (cursor) { +            actual_keys.push(cursor.key.valueOf()); +            cursor.continue(); +          } else { +            assert_key_equals(actual_keys, expected_keys, "keyorder array"); +            resolve(); +          } +        }; +      }; +    }); +  } + +  await keypath("my.key", [{ my: { key: 10 } }], [10]); + +  await keypath("my.køi", [{ my: { køi: 5 } }], [5]); + +  await keypath("my.key_ya", [{ my: { key_ya: 10 } }], [10]); + +  await keypath("public.key$ya", [{ public: { key$ya: 10 } }], [10]); + +  await keypath("true.$", [{ true: { $: 10 } }], [10]); + +  await keypath("my._", [{ my: { _: 10 } }], [10]); + +  await keypath("delete.a7", [{ delete: { a7: 10 } }], [10]); + +  await keypath( +    "p.p.p.p.p.p.p.p.p.p.p.p.p.p", +    [ +      { +        p: { +          p: { +            p: { +              p: { +                p: { +                  p: { p: { p: { p: { p: { p: { p: { p: { p: 10 } } } } } } } }, +                }, +              }, +            }, +          }, +        }, +      }, +    ], +    [10], +  ); + +  await keypath( +    "str.length", +    [{ str: "pony" }, { str: "my" }, { str: "little" }, { str: "" }], +    [0, 2, 4, 6], +  ); + +  await keypath( +    "arr.length", +    [ +      { arr: [0, 0, 0, 0] }, +      { arr: [{}, 0, "hei", "length", Infinity, []] }, +      { arr: [10, 10] }, +      { arr: [] }, +    ], +    [0, 2, 4, 6], +  ); + +  await keypath("length", [[10, 10], "123", { length: 20 }], [2, 3, 20]); + +  await keypath( +    "", +    [["bags"], "bean", 10], +    [10, "bean", ["bags"]], +    "'' uses value as key", +  ); + +  await keypath( +    [""], +    [["bags"], "bean", 10], +    [[10], ["bean"], [["bags"]]], +    "[''] uses value as [key]", +  ); + +  await keypath( +    ["x", "y"], +    [ +      { x: 10, y: 20 }, +      { y: 1.337, x: 100 }, +    ], +    [ +      [10, 20], +      [100, 1.337], +    ], +    "['x', 'y']", +  ); + +  await keypath( +    [["x"], ["y"]], +    [ +      { x: 10, y: 20 }, +      { y: 1.337, x: 100 }, +    ], +    [ +      [10, 20], +      [100, 1.337], +    ], +    "[['x'], 'y'] (stringifies)", +  ); + +  await keypath( +    [ +      "x", +      { +        toString: function () { +          return "y"; +        }, +      }, +    ], +    [ +      { x: 10, y: 20 }, +      { y: 1.337, x: 100 }, +    ], +    [ +      [10, 20], +      [100, 1.337], +    ], +    "['x', {toString->'y'}] (stringifies)", +  ); + +  await keypath( +    ["name", "type"], +    [ +      { name: "orange", type: "fruit" }, +      { name: "orange", type: ["telecom", "french"] }, +    ], +    [ +      ["orange", "fruit"], +      ["orange", ["telecom", "french"]], +    ], +  ); + +  await keypath( +    ["name", "type.name"], +    [ +      { name: "orange", type: { name: "fruit" } }, +      { name: "orange", type: { name: "telecom" } }, +    ], +    [ +      ["orange", "fruit"], +      ["orange", "telecom"], +    ], +  ); + +  const loop_array: any[] = []; +  loop_array.push(loop_array); +  await keypath( +    loop_array, +    ["a", 1, ["k"]], +    [[1], ["a"], [["k"]]], +    "array loop -> stringify becomes ['']", +  ); + +  t.pass(); +}); diff --git a/packages/idb-bridge/src/idb-wpt-ported/value.test.ts b/packages/idb-bridge/src/idb-wpt-ported/value.test.ts new file mode 100644 index 000000000..c4a8315c6 --- /dev/null +++ b/packages/idb-bridge/src/idb-wpt-ported/value.test.ts @@ -0,0 +1,46 @@ +import test from "ava"; +import { IDBVersionChangeEvent } from "../idbtypes"; +import { createdb } from "./wptsupport"; + +test.cb("WPT test value.htm, array", (t) => { +  const value = new Array(); +  const _instanceof = Array; + +  t.plan(1); + +  createdb(t).onupgradeneeded = function (e: IDBVersionChangeEvent) { +    (e.target as any).result.createObjectStore("store").add(value, 1); +    (e.target as any).onsuccess = (e: any) => { +      console.log("in first onsuccess"); +      e.target.result +        .transaction("store") +        .objectStore("store") +        .get(1).onsuccess = (e: any) => { +        t.assert(e.target.result instanceof _instanceof, "instanceof"); +        t.end(); +      }; +    }; +  }; +}); + +test.cb("WPT test value.htm, date", (t) => { +    const value = new Date(); +    const _instanceof = Date; +   +    t.plan(1); +   +    createdb(t).onupgradeneeded = function (e: IDBVersionChangeEvent) { +      (e.target as any).result.createObjectStore("store").add(value, 1); +      (e.target as any).onsuccess = (e: any) => { +        console.log("in first onsuccess"); +        e.target.result +          .transaction("store") +          .objectStore("store") +          .get(1).onsuccess = (e: any) => { +          t.assert(e.target.result instanceof _instanceof, "instanceof"); +          t.end(); +        }; +      }; +    }; +  }); +  
\ No newline at end of file diff --git a/packages/idb-bridge/src/idb-wpt-ported/wptsupport.ts b/packages/idb-bridge/src/idb-wpt-ported/wptsupport.ts new file mode 100644 index 000000000..10c11b7a6 --- /dev/null +++ b/packages/idb-bridge/src/idb-wpt-ported/wptsupport.ts @@ -0,0 +1,30 @@ +import { ExecutionContext } from "ava"; +import { BridgeIDBFactory } from ".."; +import { IDBOpenDBRequest } from "../idbtypes"; +import MemoryBackend from "../MemoryBackend"; +import compareKeys from "../util/cmp"; + +BridgeIDBFactory.enableTracing = true; +const idbFactory = new BridgeIDBFactory(new MemoryBackend()); + +const self = { +  indexedDB: idbFactory, +}; + +export function createdb( +  t: ExecutionContext<unknown>, +  dbname?: string, +  version?: number, +): IDBOpenDBRequest { +  var rq_open: IDBOpenDBRequest; +  dbname = dbname ? dbname : "testdb-" + new Date().getTime() + Math.random(); +  if (version) rq_open = self.indexedDB.open(dbname, version); +  else rq_open = self.indexedDB.open(dbname); +  return rq_open; +} + +export function assert_key_equals(actual: any, expected: any, description?: string) { +  if (0 != compareKeys(actual, expected)) { +    throw Error("expected keys to be the same"); +  } +} diff --git a/packages/idb-bridge/src/util/injectKey.ts b/packages/idb-bridge/src/util/injectKey.ts index 678f42d28..63c8deda4 100644 --- a/packages/idb-bridge/src/util/injectKey.ts +++ b/packages/idb-bridge/src/util/injectKey.ts @@ -16,10 +16,10 @@  */  import { IDBKeyPath, IDBValidKey } from "../idbtypes"; -import structuredClone from "./structuredClone"; +import { structuredClone } from "./structuredClone";  export function injectKey( -  keyPath: IDBKeyPath, +  keyPath: IDBKeyPath | IDBKeyPath[],    value: any,    key: IDBValidKey,  ): any { diff --git a/packages/idb-bridge/src/util/makeStoreKeyValue.ts b/packages/idb-bridge/src/util/makeStoreKeyValue.ts index b535bced5..2281e983d 100644 --- a/packages/idb-bridge/src/util/makeStoreKeyValue.ts +++ b/packages/idb-bridge/src/util/makeStoreKeyValue.ts @@ -17,7 +17,7 @@  import extractKey from "./extractKey";  import { DataError } from "./errors";  import valueToKey from "./valueToKey"; -import structuredClone from "./structuredClone"; +import { structuredClone } from "./structuredClone";  import injectKey from "./injectKey";  import { IDBKeyPath, IDBValidKey } from "../idbtypes"; @@ -32,7 +32,7 @@ export function makeStoreKeyValue(    key: IDBValidKey | undefined,    currentKeyGenerator: number,    autoIncrement: boolean, -  keyPath: IDBKeyPath | null, +  keyPath: IDBKeyPath | IDBKeyPath[] | null,  ): StoreKeyResult {    const haveKey = key !== null && key !== undefined;    const haveKeyPath = keyPath !== null && keyPath !== undefined; @@ -63,7 +63,11 @@ export function makeStoreKeyValue(          };        } else {          // (yes, no, no) -        throw new DataError(); +        return { +          key: key!, +          value: value, +          updatedKeyGenerator: currentKeyGenerator, +        };        }      }    } else { diff --git a/packages/idb-bridge/src/util/normalizeKeyPath.ts b/packages/idb-bridge/src/util/normalizeKeyPath.ts new file mode 100644 index 000000000..4e194b2d1 --- /dev/null +++ b/packages/idb-bridge/src/util/normalizeKeyPath.ts @@ -0,0 +1,41 @@ +/* + Copyright 2017 Jeremy Scheff + Copyright 2019 Florian Dold + + 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 { IDBKeyPath } from "../idbtypes"; + +export function normalizeKeyPath( +  keyPath: IDBKeyPath | IDBKeyPath[], +): string | string[] { +  if (Array.isArray(keyPath)) { +    const path: string[] = []; +    for (let item of keyPath) { +      // This doesn't make sense to me based on the spec, but it is needed to pass the W3C KeyPath tests (see same +      // comment in validateKeyPath) +      if ( +        item !== undefined && +        item !== null && +        typeof item !== "string" && +        (item as any).toString +      ) { +        item = (item as any).toString(); +      } +      path.push(item); +    } +    return path; +  } +  return keyPath; +} diff --git a/packages/idb-bridge/src/util/structuredClone.ts b/packages/idb-bridge/src/util/structuredClone.ts index c49d0377f..9bbeb7151 100644 --- a/packages/idb-bridge/src/util/structuredClone.ts +++ b/packages/idb-bridge/src/util/structuredClone.ts @@ -14,17 +14,24 @@   permissions and limitations under the License.  */ -function structuredCloneImpl(val: any, visited: WeakMap<any, boolean>): any { -  // FIXME: replace with real implementation! -  return JSON.parse(JSON.stringify(val)); +// @ts-ignore +import Typeson from "typeson"; +// @ts-ignore +import structuredCloningThrowing from "typeson-registry/dist/presets/structured-cloning-throwing"; + +const TSON = new Typeson().register(structuredCloningThrowing); + +export function structuredEncapsulate(val: any): any { +  return TSON.encapsulate(val); +} + +export function structuredRevive(val: any): any { +  return TSON.revive(val);  }  /**   * Structured clone for IndexedDB.   */  export function structuredClone(val: any): any { -  const visited: WeakMap<any, boolean> = new WeakMap<any, boolean>(); -  return structuredCloneImpl(val, visited); +  return structuredRevive(structuredEncapsulate(val));  } - -export default structuredClone; diff --git a/packages/taler-wallet-core/src/util/query.ts b/packages/taler-wallet-core/src/util/query.ts index d0b8c2ef6..71c809ec7 100644 --- a/packages/taler-wallet-core/src/util/query.ts +++ b/packages/taler-wallet-core/src/util/query.ts @@ -106,7 +106,7 @@ function transactionToPromise(tx: IDBTransaction): Promise<void> {      };      tx.onerror = () => {        console.error("Transaction failed:", stack); -      reject(tx.error); +      reject(tx._error);      };    });  } @@ -394,8 +394,8 @@ function runWithTransaction<T, StoreTypes extends Store<string, {}>>(        logger.error(`${stack}`);      };      tx.onabort = () => { -      if (tx.error) { -        logger.error("Transaction aborted with error:", tx.error); +      if (tx._error) { +        logger.error("Transaction aborted with error:", tx._error);        } else {          logger.error("Trasaction aborted (no error)");        } | 
