import { API } from 'api' import { Node } from 'node' export const ROOT_NODE = '00000000-0000-0000-0000-000000000000' export class NodeStore { constructor() {//{{{ if (!('indexedDB' in window)) { throw 'Missing IndexedDB' } this.db = null }//}}} async initializeDB() {//{{{ return new Promise((resolve, reject) => { const req = indexedDB.open('notes', 3) // Schema upgrades for IndexedDB. // These can start from different points depending on updates to Notes2 since a device was online. req.onupgradeneeded = (event) => { let treeNodes let nodes let appState const db = event.target.result const trx = event.target.transaction for (let i = event.oldVersion + 1; i <= event.newVersion; i++) { console.log(`Upgrade to schema ${i}`) // The schema transformations. switch (i) { case 1: treeNodes = db.createObjectStore('treeNodes', { keyPath: 'UUID' }) treeNodes.createIndex('nameIndex', 'Name', { unique: false }) nodes = db.createObjectStore('nodes', { keyPath: 'UUID' }) nodes.createIndex('nameIndex', 'Name', { unique: false }) break case 2: trx.objectStore('treeNodes').createIndex('parentIndex', 'ParentUUID', { unique: false }) break case 3: appState = db.createObjectStore('appState', { keyPath: 'key' }) } } } req.onsuccess = (event) => { this.db = event.target.result this.initializeRootNode().then(() => resolve() ) } req.onerror = (event) => { reject(event.target.error) } }) }//}}} initializeRootNode() {//{{{ return new Promise((resolve, reject) => { // The root node is a magical node which displays as the first node if none is specified. // If not already existing, it will be created. const trx = this.db.transaction('nodes', 'readwrite') const nodes = trx.objectStore('nodes') const getRequest = nodes.get(ROOT_NODE) getRequest.onsuccess = (event) => { // Root node exists - nice! if (event.target.result !== undefined) { resolve(event.target.result) return } const putRequest = nodes.put({ UUID: ROOT_NODE, Name: 'Notes2', Content: 'Hello, World!', }) putRequest.onsuccess = (event) => { resolve(event.target.result) } putRequest.onerror = (event) => { reject(event.target.error) } } getRequest.onerror = (event) => reject(event.target.error) }) }//}}} async getAppState(key) {//{{{ return new Promise((resolve, reject) => { const trx = this.db.transaction('appState', 'readonly') const appState = trx.objectStore('appState') const getRequest = appState.get(key) getRequest.onsuccess = (event) => { if (event.target.result !== undefined) { resolve(event.target.result) } else { resolve(null) } } getRequest.onerror = (event) => reject(event.target.error) }) }//}}} async setAppState(key, value) {//{{{ return new Promise((resolve, reject) => { try { const t = this.db.transaction('appState', 'readwrite') const appState = t.objectStore('appState') t.onerror = (event) => { console.log('transaction error', event.target.error) reject(event.target.error) } t.oncomplete = () => { resolve() } const record = { key, value } const addReq = appState.put(record) addReq.onerror = (event) => { console.log('Error!', event.target.error, key, value) } } catch (e) { reject(e) } }) }//}}} async upsertTreeRecords(records) {//{{{ return new Promise((resolve, reject) => { const t = this.db.transaction('treeNodes', 'readwrite') const nodeStore = t.objectStore('treeNodes') t.onerror = (event) => { console.log('transaction error', event.target.error) reject(event.target.error) } t.oncomplete = () => { resolve() } // records is an object, not an array. for (const i in records) { const record = records[i] let addReq let op if (record.Deleted) { op = 'deleting' addReq = nodeStore.delete(record.UUID) } else { op = 'upserting' addReq = nodeStore.put(record) } addReq.onsuccess = () => { console.log(`${op} ${record.UUID} (${record.Name})`) } addReq.onerror = (event) => { console.log(`error ${op} ${record.UUID}`, event.target.error) reject(event.target.error) } } }) }//}}} async getTreeNodes(parent, newLevel) {//{{{ return new Promise((resolve, reject) => { const trx = this.db.transaction('treeNodes', 'readonly') const nodeStore = trx.objectStore('treeNodes') const index = nodeStore.index('parentIndex') const req = index.getAll(parent) req.onsuccess = (event) => { const nodes = [] for (const i in event.target.result) { const node = new Node(event.target.result[i], newLevel) nodes.push(node) } resolve(nodes) } req.onerror = (event) => reject(event.target.error) }) }//}}} async add(records) {//{{{ return new Promise((resolve, reject) => { try { const t = this.db.transaction('nodes', 'readwrite') const nodeStore = t.objectStore('nodes') t.onerror = (event) => { console.log('transaction error', event.target.error) reject(event.target.error) } t.oncomplete = () => { resolve() } // records is an object, not an array. for (const recordIdx in records) { const record = records[recordIdx] const addReq = nodeStore.put(record) addReq.onsuccess = () => { console.log('OK!', record.ID, record.Name) } addReq.onerror = (event) => { console.log('Error!', event.target.error, record.ID) } } } catch (e) { console.log(e) } }) }//}}} async get(uuid) {//{{{ return new Promise((resolve, reject) => { // Node is always returned from IndexedDB if existing there. // Otherwise an attempt to get it from backend is executed. const trx = this.db.transaction('nodes', 'readonly') const nodeStore = trx.objectStore('nodes') const getRequest = nodeStore.get(uuid) getRequest.onsuccess = (event) => { // Node found in IndexedDB and returned. if (event.target.result !== undefined) { const node = new Node(event.target.result, -1) resolve(node) return } // Node not found and a request to the backend is made. API.query("POST", `/node/retrieve/${uuid}`, {}) .then(res => { const trx = this.db.transaction('nodes', 'readwrite') const nodeStore = trx.objectStore('nodes') const putRequest = nodeStore.put(res.Node) const node = new Node(res.Node, -1) putRequest.onsuccess = () => resolve(node) putRequest.onerror = (event) => { reject(event.target.error) } }) .catch(e => reject(e)) } }) }//}}} } // vim: foldmethod=marker