2019-06-26 22:00:50 +02:00
|
|
|
import { StorageError } from "../common.js";
|
|
|
|
|
2019-07-01 10:00:29 +02:00
|
|
|
|
|
|
|
// storage keys are defined to be unsigned 32bit numbers in WebPlatform.js, which is assumed by idb
|
|
|
|
export function encodeUint32(n) {
|
|
|
|
const hex = n.toString(16);
|
|
|
|
return "0".repeat(8 - hex.length) + hex;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function decodeUint32(str) {
|
|
|
|
return parseInt(str, 16);
|
|
|
|
}
|
|
|
|
|
2019-02-07 00:19:14 +01:00
|
|
|
export function openDatabase(name, createObjectStore, version) {
|
2019-02-06 23:06:56 +01:00
|
|
|
const req = window.indexedDB.open(name, version);
|
|
|
|
req.onupgradeneeded = (ev) => {
|
|
|
|
const db = ev.target.result;
|
|
|
|
const oldVersion = ev.oldVersion;
|
|
|
|
createObjectStore(db, oldVersion, version);
|
|
|
|
};
|
|
|
|
return reqAsPromise(req);
|
2019-01-09 11:06:09 +01:00
|
|
|
}
|
|
|
|
|
2019-06-26 22:00:50 +02:00
|
|
|
function wrapError(err) {
|
|
|
|
return new StorageError(`wrapped DOMException`, err);
|
|
|
|
}
|
|
|
|
|
2019-01-09 11:06:09 +01:00
|
|
|
export function reqAsPromise(req) {
|
|
|
|
return new Promise((resolve, reject) => {
|
2019-02-06 23:06:33 +01:00
|
|
|
req.addEventListener("success", event => resolve(event.target.result));
|
2019-06-26 22:00:50 +02:00
|
|
|
req.addEventListener("error", event => reject(wrapError(event.target.error)));
|
2019-01-09 11:06:09 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
export function txnAsPromise(txn) {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
txn.addEventListener("complete", resolve);
|
2019-11-21 18:23:48 +01:00
|
|
|
txn.addEventListener("abort", event => reject(wrapError(event.target.error)));
|
2019-01-09 11:06:09 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-09-15 12:23:54 +02:00
|
|
|
export function iterateCursor(cursorRequest, processValue) {
|
2019-02-06 23:06:56 +01:00
|
|
|
// TODO: does cursor already have a value here??
|
2019-01-09 11:06:09 +01:00
|
|
|
return new Promise((resolve, reject) => {
|
2019-09-15 12:23:54 +02:00
|
|
|
cursorRequest.onerror = () => {
|
|
|
|
reject(new StorageError("Query failed", cursorRequest.error));
|
2019-01-09 11:06:09 +01:00
|
|
|
};
|
|
|
|
// collect results
|
2019-09-15 12:23:54 +02:00
|
|
|
cursorRequest.onsuccess = (event) => {
|
2019-01-09 11:06:09 +01:00
|
|
|
const cursor = event.target.result;
|
|
|
|
if (!cursor) {
|
|
|
|
resolve(false);
|
|
|
|
return; // end of results
|
|
|
|
}
|
2019-05-11 13:10:31 +02:00
|
|
|
const {done, jumpTo} = processValue(cursor.value, cursor.key);
|
|
|
|
if (done) {
|
2019-02-06 23:06:56 +01:00
|
|
|
resolve(true);
|
2019-06-26 22:02:00 +02:00
|
|
|
} else if(jumpTo) {
|
2019-05-11 13:10:31 +02:00
|
|
|
cursor.continue(jumpTo);
|
2019-06-26 22:02:00 +02:00
|
|
|
} else {
|
|
|
|
cursor.continue();
|
2019-01-09 11:06:09 +01:00
|
|
|
}
|
|
|
|
};
|
2019-06-26 22:00:50 +02:00
|
|
|
}).catch(err => {
|
|
|
|
throw new StorageError("iterateCursor failed", err);
|
2019-01-09 11:06:09 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function fetchResults(cursor, isDone) {
|
|
|
|
const results = [];
|
|
|
|
await iterateCursor(cursor, (value) => {
|
2019-02-06 23:06:56 +01:00
|
|
|
results.push(value);
|
2019-05-11 13:10:31 +02:00
|
|
|
return {done: isDone(results)};
|
2019-01-09 11:06:09 +01:00
|
|
|
});
|
|
|
|
return results;
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function select(db, storeName, toCursor, isDone) {
|
2019-02-06 23:06:56 +01:00
|
|
|
if (!isDone) {
|
|
|
|
isDone = () => false;
|
|
|
|
}
|
|
|
|
if (!toCursor) {
|
|
|
|
toCursor = store => store.openCursor();
|
|
|
|
}
|
|
|
|
const tx = db.transaction([storeName], "readonly");
|
|
|
|
const store = tx.objectStore(storeName);
|
|
|
|
const cursor = toCursor(store);
|
|
|
|
return await fetchResults(cursor, isDone);
|
2019-01-09 11:06:09 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
export async function updateSingletonStore(db, storeName, value) {
|
2019-02-06 23:06:56 +01:00
|
|
|
const tx = db.transaction([storeName], "readwrite");
|
|
|
|
const store = tx.objectStore(storeName);
|
|
|
|
const cursor = await reqAsPromise(store.openCursor());
|
|
|
|
if (cursor) {
|
|
|
|
return reqAsPromise(cursor.update(storeName));
|
|
|
|
} else {
|
|
|
|
return reqAsPromise(store.add(value));
|
|
|
|
}
|
2019-01-09 11:06:09 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
export async function findStoreValue(db, storeName, toCursor, matchesValue) {
|
2019-02-06 23:06:56 +01:00
|
|
|
if (!matchesValue) {
|
|
|
|
matchesValue = () => true;
|
|
|
|
}
|
|
|
|
if (!toCursor) {
|
|
|
|
toCursor = store => store.openCursor();
|
|
|
|
}
|
2019-01-09 11:06:09 +01:00
|
|
|
|
2019-02-06 23:06:56 +01:00
|
|
|
const tx = db.transaction([storeName], "readwrite");
|
|
|
|
const store = tx.objectStore(storeName);
|
|
|
|
const cursor = await reqAsPromise(toCursor(store));
|
|
|
|
let match;
|
|
|
|
const matched = await iterateCursor(cursor, (value) => {
|
|
|
|
if (matchesValue(value)) {
|
|
|
|
match = value;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
if (!matched) {
|
2019-06-26 22:00:50 +02:00
|
|
|
throw new StorageError("Value not found");
|
2019-02-06 23:06:56 +01:00
|
|
|
}
|
|
|
|
return match;
|
2019-05-11 13:10:31 +02:00
|
|
|
}
|