export { bootstrap }; /** * @typedef {Object} Reference * @property {Array} dependents * @property {string} path * @property {string} object_id * @property {string} content_address */ async function load_bootstrap() { let response = await fetch("/api/v1/ref/all/username"); if (!response.ok) { throw new Error("Network response was not ok: " + response.statusText); } return await response.json(); } /** * @param {String} dbName * @param {Array} storeNames * @returns {Promise} */ async function openDatabase(dbName, storeNames) { return await new Promise((resolve, reject) => { const request = indexedDB.open(dbName, 1); request.onupgradeneeded = (event) => { const db = event.target.result; for (var storeName of storeNames) { // Create the object store if it doesn't exist if (!db.objectStoreNames.contains(storeName)) { db.createObjectStore(storeName); } } }; request.onsuccess = (event) => { const db = event.target.result; resolve(db); }; request.onerror = (event) => { reject(event.target.error); }; }); } /** * Stores a reference object in the IndexedDB. * @param {IDBObjectStore} store * @param {Object} reference * @param {string} root_path * @returns {Promise} */ function storeObject(store, reference, root_path) { return new Promise((resolve, reject) => { const request = store.put(JSON.stringify(reference), root_path); request.onerror = (evt) => { reject(evt.target.error); console.log("Failed to store object", evt); }; request.onsuccess = (evt) => { resolve(evt.target.result); }; }); } /** * @param {IDBObjectStore} refStore * @param {Object} reference * @returns {Promise>} An array of references */ function load_reference_paths(refStore, reference) { return new Promise(async (resolve, reject) => { let references = []; references.push(reference); if (reference.dependents) { for (var dep of reference.dependents) { references = references.concat(await load_reference_paths(refStore, dep)); } } await storeObject(refStore, reference, reference.path); resolve(references); }); } /** * @param {IDBDatabase} db * @param {string} storeName * @param {Array} references */ async function load_objects_and_store(db, references, storeName) { let objects = [] for (var ref of references) { /** @type {Response} */ if (ref.dependents && ref.dependents.length != 0) { continue; // not a leaf object } let response = await fetch("/api/v1/object/" + ref.content_address); if (!response.ok) { throw new Error("Network response was not ok: " + response.statusText); } const object = await response.text(); objects.push({ id: ref.content_address, content: object }); } const objectTrxAndStore = await getStoreAndTransaction(db, storeName); for (var obj of objects) { await storeObject(objectTrxAndStore.store, obj.content, obj.id); } await new Promise((resolve, reject) => { objectTrxAndStore.trx.oncomplete = () => resolve(); objectTrxAndStore.trx.onerror = (event) => reject(event.target.error); }); } /** * @param {string} storeName * @param {IDBDatabase} db * @returns {Promise<{trx: IDBTransaction, store: IDBObjectStore}>} The transaction and object store. */ async function getStoreAndTransaction(db, storeName) { const transaction = db.transaction([storeName], "readwrite"); return { trx: transaction, store: transaction.objectStore(storeName) }; } /** * @returns {Number} The number of milliseconds it took to bootstrap. */ async function bootstrap() { const refStoreName = "references"; const objectStoreName = "objects"; const databaseName = "MerkleStore"; const start = new Date().getTime(); const root = await load_bootstrap(); const db = await openDatabase(databaseName, [refStoreName, objectStoreName]); const refTrxAndStore = await getStoreAndTransaction(db, refStoreName); // Use a promise to wait for the transaction to complete const transactionComplete = new Promise((resolve, reject) => { refTrxAndStore.trx.oncomplete = () => resolve(); refTrxAndStore.trx.onerror = (event) => reject(event.target.error); }); const refs = await load_reference_paths(refTrxAndStore.store, root); // Wait for the transaction to complete await transactionComplete; await load_objects_and_store(db, refs, objectStoreName); const end = new Date().getTime(); return end - start; }