UNPKG

@aws-amplify/core

Version:
176 lines (141 loc) 4.04 kB
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 import { DATABASE_NAME, STORE_NAME } from './constants'; import { getAddItemBytesSize } from './getAddItemBytesSize'; import { AddItemWithAuthPropertiesWeb, QueuedItem, QueuedStorage, } from './types'; export const createQueuedStorage = (): QueuedStorage => { let currentBytesSize = 0; let error: DOMException | undefined; const openDBPromise = new Promise<IDBDatabase>((resolve, reject) => { const { indexedDB } = window; const openRequest = indexedDB.open(DATABASE_NAME, 1); openRequest.onupgradeneeded = () => { const db = openRequest.result; if (!db.objectStoreNames.contains(STORE_NAME)) { db.createObjectStore(STORE_NAME, { keyPath: 'id', autoIncrement: true, }); } }; openRequest.onsuccess = async () => { const db = openRequest.result; const transaction = db.transaction(STORE_NAME, 'readonly'); const request = transaction.objectStore(STORE_NAME).getAll(); await promisifyIDBRequest(request); for (const item of request.result) { currentBytesSize += (item as QueuedItem).bytesSize; } resolve(openRequest.result); }; openRequest.onerror = () => { reject(openRequest.error); }; }).catch(err => { error = err; return undefined; }); const getDB = async () => { const db = await openDBPromise; if (!db) { throw error; } return db; }; const getStore = async (): Promise<IDBObjectStore> => { const db = await getDB(); const transaction = db.transaction(STORE_NAME, 'readwrite'); return transaction.objectStore(STORE_NAME); }; const _peek = async (n?: number): Promise<QueuedItem[]> => { const store = await getStore(); const request = store.getAll(undefined, n); await promisifyIDBRequest(request); return request.result.map(item => item as QueuedItem); }; return { async add( item, { dequeueBeforeEnqueue } = { dequeueBeforeEnqueue: false }, ) { if (dequeueBeforeEnqueue) { const itemsToDelete = await this.peek(1); await this.delete(itemsToDelete); } const store = await getStore(); const itemBytesSize = getAddItemBytesSize(item); const queuedItem: AddItemWithAuthPropertiesWeb = { ...item, bytesSize: itemBytesSize, }; const request = store.add(queuedItem); await promisifyIDBRequest(request); currentBytesSize += itemBytesSize; }, async peek(n) { return _peek(n); }, async peekAll() { return _peek(); }, async delete(items) { if (!items.length) { return; } const store = await getStore(); // delete by range to improve performance const keyRangesToDelete = findRanges( items .map(item => item.id) .filter((id): id is number => id !== undefined), ).map(([lower, upper]) => IDBKeyRange.bound(lower, upper)); await Promise.all( keyRangesToDelete.map(range => promisifyIDBRequest(store.delete(range)), ), ); for (const item of items) { currentBytesSize -= item.bytesSize; } }, async clear() { const store = await getStore(); await store.clear(); currentBytesSize = 0; }, isFull(maxBytesSizeInMiB) { return currentBytesSize >= maxBytesSizeInMiB * 1024 * 1024; }, }; }; const promisifyIDBRequest = <T>(request: IDBRequest<T>): Promise<void> => new Promise<void>((resolve, reject) => { request.onsuccess = () => { resolve(); }; request.onerror = () => { reject(request.error); }; }); const findRanges = (input: number[]): [number, number][] => { const nums = input.concat().sort((a, b) => a - b); const result: [number, number][] = []; let rangeLength = 1; for (let i = 1; i <= nums.length; i++) { if (i === nums.length || nums[i] - nums[i - 1] !== 1) { if (rangeLength === 1) { result.push([nums[i - rangeLength], nums[i - rangeLength]]); } else { result.push([nums[i - rangeLength], nums[i - 1]]); } rangeLength = 1; } else { rangeLength += 1; } } return result; };