diff options
| author | Darius Jahandarie <djahandarie@gmail.com> | 2023-12-06 03:53:16 +0000 | 
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-12-06 03:53:16 +0000 | 
| commit | bd5bc1a5db29903bc098995cd9262c4576bf76af (patch) | |
| tree | c9214189e0214480fcf6539ad1c6327aef6cbd1c /ext/js/data/database.js | |
| parent | fd6bba8a2a869eaf2b2c1fa49001f933fce3c618 (diff) | |
| parent | 23e6fb76319c9ed7c9bcdc3efba39bc5dd38f288 (diff) | |
Merge pull request #339 from toasted-nutbread/type-annotations
Type annotations
Diffstat (limited to 'ext/js/data/database.js')
| -rw-r--r-- | ext/js/data/database.js | 206 | 
1 files changed, 187 insertions, 19 deletions
| diff --git a/ext/js/data/database.js b/ext/js/data/database.js index 8e818d8b..026945ca 100644 --- a/ext/js/data/database.js +++ b/ext/js/data/database.js @@ -16,12 +16,22 @@   * along with this program.  If not, see <https://www.gnu.org/licenses/>.   */ +/** + * @template {string} TObjectStoreName + */  export class Database {      constructor() { +        /** @type {?IDBDatabase} */          this._db = null; +        /** @type {boolean} */          this._isOpening = false;      } +    /** +     * @param {string} databaseName +     * @param {number} version +     * @param {import('database').StructureDefinition<TObjectStoreName>[]} structure +     */      async open(databaseName, version, structure) {          if (this._db !== null) {              throw new Error('Database already open'); @@ -40,6 +50,9 @@ export class Database {          }      } +    /** +     * @throws {Error} +     */      close() {          if (this._db === null) {              throw new Error('Database is not open'); @@ -49,14 +62,26 @@ export class Database {          this._db = null;      } +    /** +     * @returns {boolean} +     */      isOpening() {          return this._isOpening;      } +    /** +     * @returns {boolean} +     */      isOpen() {          return this._db !== null;      } +    /** +     * @param {string[]} storeNames +     * @param {IDBTransactionMode} mode +     * @returns {IDBTransaction} +     * @throws {Error} +     */      transaction(storeNames, mode) {          if (this._db === null) {              throw new Error(this._isOpening ? 'Database not ready' : 'Database not open'); @@ -64,6 +89,13 @@ export class Database {          return this._db.transaction(storeNames, mode);      } +    /** +     * @param {TObjectStoreName} objectStoreName +     * @param {unknown[]} items +     * @param {number} start +     * @param {number} count +     * @returns {Promise<void>} +     */      bulkAdd(objectStoreName, items, start, count) {          return new Promise((resolve, reject) => {              if (start + count > items.length) { @@ -84,6 +116,15 @@ export class Database {          });      } +    /** +     * @template [TData=unknown] +     * @template [TResult=unknown] +     * @param {IDBObjectStore|IDBIndex} objectStoreOrIndex +     * @param {?IDBValidKey|IDBKeyRange} query +     * @param {(results: TResult[], data: TData) => void} onSuccess +     * @param {(reason: unknown, data: TData) => void} onError +     * @param {TData} data +     */      getAll(objectStoreOrIndex, query, onSuccess, onError, data) {          if (typeof objectStoreOrIndex.getAll === 'function') {              this._getAllFast(objectStoreOrIndex, query, onSuccess, onError, data); @@ -92,6 +133,12 @@ export class Database {          }      } +    /** +     * @param {IDBObjectStore|IDBIndex} objectStoreOrIndex +     * @param {IDBValidKey|IDBKeyRange} query +     * @param {(value: IDBValidKey[]) => void} onSuccess +     * @param {(reason?: unknown) => void} onError +     */      getAllKeys(objectStoreOrIndex, query, onSuccess, onError) {          if (typeof objectStoreOrIndex.getAllKeys === 'function') {              this._getAllKeysFast(objectStoreOrIndex, query, onSuccess, onError); @@ -100,6 +147,18 @@ export class Database {          }      } +    /** +     * @template TPredicateArg +     * @template [TResult=unknown] +     * @template [TResultDefault=unknown] +     * @param {TObjectStoreName} objectStoreName +     * @param {?string} indexName +     * @param {?IDBValidKey|IDBKeyRange} query +     * @param {?((value: TResult|TResultDefault, predicateArg: TPredicateArg) => boolean)} predicate +     * @param {TPredicateArg} predicateArg +     * @param {TResultDefault} defaultValue +     * @returns {Promise<TResult|TResultDefault>} +     */      find(objectStoreName, indexName, query, predicate, predicateArg, defaultValue) {          return new Promise((resolve, reject) => {              const transaction = this.transaction([objectStoreName], 'readonly'); @@ -109,12 +168,26 @@ export class Database {          });      } +    /** +     * @template TData +     * @template TPredicateArg +     * @template [TResult=unknown] +     * @template [TResultDefault=unknown] +     * @param {IDBObjectStore|IDBIndex} objectStoreOrIndex +     * @param {?IDBValidKey|IDBKeyRange} query +     * @param {(value: TResult|TResultDefault, data: TData) => void} resolve +     * @param {(reason: unknown, data: TData) => void} reject +     * @param {TData} data +     * @param {?((value: TResult, predicateArg: TPredicateArg) => boolean)} predicate +     * @param {TPredicateArg} predicateArg +     * @param {TResultDefault} defaultValue +     */      findFirst(objectStoreOrIndex, query, resolve, reject, data, predicate, predicateArg, defaultValue) {          const noPredicate = (typeof predicate !== 'function');          const request = objectStoreOrIndex.openCursor(query, 'next'); -        request.onerror = (e) => reject(e.target.error, data); +        request.onerror = (e) => reject(/** @type {IDBRequest<?IDBCursorWithValue>} */ (e.target).error, data);          request.onsuccess = (e) => { -            const cursor = e.target.result; +            const cursor = /** @type {IDBRequest<?IDBCursorWithValue>} */ (e.target).result;              if (cursor) {                  const {value} = cursor;                  if (noPredicate || predicate(value, predicateArg)) { @@ -128,19 +201,33 @@ export class Database {          };      } +    /** +     * @param {import('database').CountTarget[]} targets +     * @param {(results: number[]) => void} resolve +     * @param {(reason?: unknown) => void} reject +     */      bulkCount(targets, resolve, reject) {          const targetCount = targets.length;          if (targetCount <= 0) { -            resolve(); +            resolve([]);              return;          }          let completedCount = 0; +        /** @type {number[]} */          const results = new Array(targetCount).fill(null); -        const onError = (e) => reject(e.target.error); +        /** +         * @param {Event} e +         * @returns {void} +         */ +        const onError = (e) => reject(/** @type {IDBRequest<number>} */ (e.target).error); +        /** +         * @param {Event} e +         * @param {number} index +         */          const onSuccess = (e, index) => { -            const count = e.target.result; +            const count = /** @type {IDBRequest<number>} */ (e.target).result;              results[index] = count;              if (++completedCount >= targetCount) {                  resolve(results); @@ -156,6 +243,11 @@ export class Database {          }      } +    /** +     * @param {TObjectStoreName} objectStoreName +     * @param {IDBValidKey|IDBKeyRange} key +     * @returns {Promise<void>} +     */      delete(objectStoreName, key) {          return new Promise((resolve, reject) => {              const transaction = this._readWriteTransaction([objectStoreName], resolve, reject); @@ -165,12 +257,23 @@ export class Database {          });      } +    /** +     * @param {TObjectStoreName} objectStoreName +     * @param {?string} indexName +     * @param {IDBKeyRange} query +     * @param {?(keys: IDBValidKey[]) => IDBValidKey[]} filterKeys +     * @param {?(completedCount: number, totalCount: number) => void} onProgress +     * @returns {Promise<void>} +     */      bulkDelete(objectStoreName, indexName, query, filterKeys=null, onProgress=null) {          return new Promise((resolve, reject) => {              const transaction = this._readWriteTransaction([objectStoreName], resolve, reject);              const objectStore = transaction.objectStore(objectStoreName);              const objectStoreOrIndex = indexName !== null ? objectStore.index(indexName) : objectStore; +            /** +             * @param {IDBValidKey[]} keys +             */              const onGetKeys = (keys) => {                  try {                      if (typeof filterKeys === 'function') { @@ -187,10 +290,14 @@ export class Database {          });      } +    /** +     * @param {string} databaseName +     * @returns {Promise<void>} +     */      static deleteDatabase(databaseName) {          return new Promise((resolve, reject) => {              const request = indexedDB.deleteDatabase(databaseName); -            request.onerror = (e) => reject(e.target.error); +            request.onerror = (e) => reject(/** @type {IDBRequest} */ (e.target).error);              request.onsuccess = () => resolve();              request.onblocked = () => reject(new Error('Database deletion blocked'));          }); @@ -198,24 +305,37 @@ export class Database {      // Private +    /** +     * @param {string} name +     * @param {number} version +     * @param {import('database').UpdateFunction} onUpgradeNeeded +     * @returns {Promise<IDBDatabase>} +     */      _open(name, version, onUpgradeNeeded) {          return new Promise((resolve, reject) => {              const request = indexedDB.open(name, version);              request.onupgradeneeded = (event) => {                  try { -                    request.transaction.onerror = (e) => reject(e.target.error); -                    onUpgradeNeeded(request.result, request.transaction, event.oldVersion, event.newVersion); +                    const transaction = /** @type {IDBTransaction} */ (request.transaction); +                    transaction.onerror = (e) => reject(/** @type {IDBRequest} */ (e.target).error); +                    onUpgradeNeeded(request.result, transaction, event.oldVersion, event.newVersion);                  } catch (e) {                      reject(e);                  }              }; -            request.onerror = (e) => reject(e.target.error); +            request.onerror = (e) => reject(/** @type {IDBRequest} */ (e.target).error);              request.onsuccess = () => resolve(request.result);          });      } +    /** +     * @param {IDBDatabase} db +     * @param {IDBTransaction} transaction +     * @param {number} oldVersion +     * @param {import('database').StructureDefinition<TObjectStoreName>[]} upgrades +     */      _upgrade(db, transaction, oldVersion, upgrades) {          for (const {version, stores} of upgrades) {              if (oldVersion >= version) { continue; } @@ -238,6 +358,11 @@ export class Database {          }      } +    /** +     * @param {DOMStringList} list +     * @param {string} value +     * @returns {boolean} +     */      _listContains(list, value) {          for (let i = 0, ii = list.length; i < ii; ++i) {              if (list[i] === value) { return true; } @@ -245,18 +370,37 @@ export class Database {          return false;      } +    /** +     * @template [TData=unknown] +     * @template [TResult=unknown] +     * @param {IDBObjectStore|IDBIndex} objectStoreOrIndex +     * @param {?IDBValidKey|IDBKeyRange} query +     * @param {(results: TResult[], data: TData) => void} onSuccess +     * @param {(reason: unknown, data: TData) => void} onReject +     * @param {TData} data +     */      _getAllFast(objectStoreOrIndex, query, onSuccess, onReject, data) {          const request = objectStoreOrIndex.getAll(query); -        request.onerror = (e) => onReject(e.target.error, data); -        request.onsuccess = (e) => onSuccess(e.target.result, data); +        request.onerror = (e) => onReject(/** @type {IDBRequest<import('core').SafeAny[]>} */ (e.target).error, data); +        request.onsuccess = (e) => onSuccess(/** @type {IDBRequest<import('core').SafeAny[]>} */ (e.target).result, data);      } +    /** +     * @template [TData=unknown] +     * @template [TResult=unknown] +     * @param {IDBObjectStore|IDBIndex} objectStoreOrIndex +     * @param {?IDBValidKey|IDBKeyRange} query +     * @param {(results: TResult[], data: TData) => void} onSuccess +     * @param {(reason: unknown, data: TData) => void} onReject +     * @param {TData} data +     */      _getAllUsingCursor(objectStoreOrIndex, query, onSuccess, onReject, data) { +        /** @type {TResult[]} */          const results = [];          const request = objectStoreOrIndex.openCursor(query, 'next'); -        request.onerror = (e) => onReject(e.target.error, data); +        request.onerror = (e) => onReject(/** @type {IDBRequest<?IDBCursorWithValue>} */ (e.target).error, data);          request.onsuccess = (e) => { -            const cursor = e.target.result; +            const cursor = /** @type {IDBRequest<?IDBCursorWithValue>} */ (e.target).result;              if (cursor) {                  results.push(cursor.value);                  cursor.continue(); @@ -266,18 +410,31 @@ export class Database {          };      } +    /** +     * @param {IDBObjectStore|IDBIndex} objectStoreOrIndex +     * @param {IDBValidKey|IDBKeyRange} query +     * @param {(value: IDBValidKey[]) => void} onSuccess +     * @param {(reason?: unknown) => void} onError +     */      _getAllKeysFast(objectStoreOrIndex, query, onSuccess, onError) {          const request = objectStoreOrIndex.getAllKeys(query); -        request.onerror = (e) => onError(e.target.error); -        request.onsuccess = (e) => onSuccess(e.target.result); +        request.onerror = (e) => onError(/** @type {IDBRequest<IDBValidKey[]>} */ (e.target).error); +        request.onsuccess = (e) => onSuccess(/** @type {IDBRequest<IDBValidKey[]>} */ (e.target).result);      } +    /** +     * @param {IDBObjectStore|IDBIndex} objectStoreOrIndex +     * @param {IDBValidKey|IDBKeyRange} query +     * @param {(value: IDBValidKey[]) => void} onSuccess +     * @param {(reason?: unknown) => void} onError +     */      _getAllKeysUsingCursor(objectStoreOrIndex, query, onSuccess, onError) { +        /** @type {IDBValidKey[]} */          const results = [];          const request = objectStoreOrIndex.openKeyCursor(query, 'next'); -        request.onerror = (e) => onError(e.target.error); +        request.onerror = (e) => onError(/** @type {IDBRequest<?IDBCursor>} */ (e.target).error);          request.onsuccess = (e) => { -            const cursor = e.target.result; +            const cursor = /** @type {IDBRequest<?IDBCursor>} */ (e.target).result;              if (cursor) {                  results.push(cursor.primaryKey);                  cursor.continue(); @@ -287,6 +444,11 @@ export class Database {          };      } +    /** +     * @param {IDBObjectStore} objectStore +     * @param {IDBValidKey[]} keys +     * @param {?(completedCount: number, totalCount: number) => void} onProgress +     */      _bulkDeleteInternal(objectStore, keys, onProgress) {          const count = keys.length;          if (count === 0) { return; } @@ -295,7 +457,7 @@ export class Database {          const onSuccess = () => {              ++completedCount;              try { -                onProgress(completedCount, count); +                /** @type {(completedCount: number, totalCount: number) => void}} */ (onProgress)(completedCount, count);              } catch (e) {                  // NOP              } @@ -310,9 +472,15 @@ export class Database {          }      } +    /** +     * @param {string[]} storeNames +     * @param {() => void} resolve +     * @param {(reason?: unknown) => void} reject +     * @returns {IDBTransaction} +     */      _readWriteTransaction(storeNames, resolve, reject) {          const transaction = this.transaction(storeNames, 'readwrite'); -        transaction.onerror = (e) => reject(e.target.error); +        transaction.onerror = (e) => reject(/** @type {IDBTransaction} */ (e.target).error);          transaction.onabort = () => reject(new Error('Transaction aborted'));          transaction.oncomplete = () => resolve();          return transaction; |