2025-03-30 21:18:08 -04:00
|
|
|
export { bootstrap };
|
2025-04-01 20:38:35 -04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @typedef {Object} Reference
|
|
|
|
* @property {Array<Reference>} dependents
|
|
|
|
* @property {string} path
|
|
|
|
* @property {string} object_id
|
|
|
|
* @property {string} content_address
|
|
|
|
*/
|
2025-03-30 21:18:08 -04:00
|
|
|
|
|
|
|
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<String>} storeNames
|
|
|
|
* @returns {Promise<IDBDatabase>}
|
|
|
|
*/
|
|
|
|
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);
|
|
|
|
};
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2025-04-01 20:38:35 -04:00
|
|
|
* Stores a reference object in the IndexedDB.
|
2025-03-30 21:18:08 -04:00
|
|
|
* @param {IDBObjectStore} store
|
|
|
|
* @param {Object} reference
|
2025-04-01 20:38:35 -04:00
|
|
|
* @param {string} root_path
|
|
|
|
* @returns {Promise<any>}
|
|
|
|
*/
|
|
|
|
function storeObject(store, reference, root_path) {
|
|
|
|
return new Promise((resolve, reject) => {
|
2025-03-30 21:18:08 -04:00
|
|
|
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);
|
|
|
|
};
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2025-04-01 20:38:35 -04:00
|
|
|
/**
|
|
|
|
* @param {IDBObjectStore} refStore
|
|
|
|
* @param {Object} reference
|
|
|
|
* @returns {Promise<Array<Reference>>} 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<Reference>} references
|
|
|
|
*/
|
|
|
|
async function load_objects_and_store(db, references, storeName) {
|
|
|
|
let objects = []
|
|
|
|
for (var ref of references) {
|
|
|
|
/** @type {Response} */
|
2025-04-02 18:23:03 -04:00
|
|
|
if (ref.dependents && ref.dependents.length != 0) {
|
|
|
|
continue; // not a leaf object
|
|
|
|
}
|
2025-04-01 20:38:35 -04:00
|
|
|
let response = await fetch("/api/v1/object/" + ref.content_address);
|
|
|
|
if (!response.ok) {
|
|
|
|
throw new Error("Network response was not ok: " + response.statusText);
|
|
|
|
}
|
2025-04-01 21:34:58 -04:00
|
|
|
const object = await response.text();
|
2025-04-01 20:38:35 -04:00
|
|
|
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.
|
|
|
|
*/
|
2025-03-30 21:18:08 -04:00
|
|
|
async function bootstrap() {
|
2025-04-01 20:38:35 -04:00
|
|
|
const refStoreName = "references";
|
|
|
|
const objectStoreName = "objects";
|
2025-03-30 21:18:08 -04:00
|
|
|
const databaseName = "MerkleStore";
|
|
|
|
const start = new Date().getTime();
|
2025-04-01 21:34:58 -04:00
|
|
|
const root = await load_bootstrap();
|
2025-04-01 20:38:35 -04:00
|
|
|
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);
|
|
|
|
});
|
|
|
|
|
2025-04-01 21:34:58 -04:00
|
|
|
const refs = await load_reference_paths(refTrxAndStore.store, root);
|
2025-04-01 20:38:35 -04:00
|
|
|
|
|
|
|
// Wait for the transaction to complete
|
|
|
|
await transactionComplete;
|
|
|
|
|
|
|
|
await load_objects_and_store(db, refs, objectStoreName);
|
|
|
|
|
2025-04-01 21:34:58 -04:00
|
|
|
const end = new Date().getTime();
|
2025-03-30 21:18:08 -04:00
|
|
|
return end - start;
|
|
|
|
}
|