From 537a112adb4db2d0d9339eb90f227f355d4a9ea6 Mon Sep 17 00:00:00 2001 From: Nolan Lawson Date: Thu, 8 Feb 2018 22:04:10 -0800 Subject: [PATCH] refactor database some more --- routes/_database/accountsAndRelationships.js | 19 ++ routes/_database/clear.js | 9 + routes/_database/database.js | 9 +- routes/_database/databaseCore.js | 264 +------------------ routes/_database/helpers.js | 22 ++ routes/_database/meta.js | 52 ++++ routes/_database/timelines.js | 126 +++++++++ 7 files changed, 234 insertions(+), 267 deletions(-) create mode 100644 routes/_database/accountsAndRelationships.js create mode 100644 routes/_database/clear.js create mode 100644 routes/_database/helpers.js create mode 100644 routes/_database/meta.js create mode 100644 routes/_database/timelines.js diff --git a/routes/_database/accountsAndRelationships.js b/routes/_database/accountsAndRelationships.js new file mode 100644 index 00000000..acf81e5b --- /dev/null +++ b/routes/_database/accountsAndRelationships.js @@ -0,0 +1,19 @@ +import { ACCOUNTS_STORE, RELATIONSHIPS_STORE } from './constants' +import { accountsCache, relationshipsCache } from './cache' +import { getGenericEntityWithId, setGenericEntityWithId } from './helpers' + +export async function getAccount(instanceName, accountId) { + return await getGenericEntityWithId(ACCOUNTS_STORE, accountsCache, instanceName, accountId) +} + +export async function setAccount(instanceName, account) { + return await setGenericEntityWithId(ACCOUNTS_STORE, accountsCache, instanceName, account) +} + +export async function getRelationship(instanceName, accountId) { + return await getGenericEntityWithId(RELATIONSHIPS_STORE, relationshipsCache, instanceName, accountId) +} + +export async function setRelationship(instanceName, relationship) { + return await setGenericEntityWithId(RELATIONSHIPS_STORE, relationshipsCache, instanceName, relationship) +} \ No newline at end of file diff --git a/routes/_database/clear.js b/routes/_database/clear.js new file mode 100644 index 00000000..20bab3d4 --- /dev/null +++ b/routes/_database/clear.js @@ -0,0 +1,9 @@ +import { accountsCache, clearCache, metaCache, statusesCache } from './cache' +import { deleteDatabase } from './databaseLifecycle' + +export async function clearDatabaseForInstance(instanceName) { + clearCache(statusesCache, instanceName) + clearCache(accountsCache, instanceName) + clearCache(metaCache, instanceName) + await deleteDatabase(instanceName) +} \ No newline at end of file diff --git a/routes/_database/database.js b/routes/_database/database.js index 40030431..9a2bca98 100644 --- a/routes/_database/database.js +++ b/routes/_database/database.js @@ -1,8 +1,3 @@ -/* -import worker from 'workerize-loader!./databaseCore' -export const database = process.browser && worker() -*/ +import * as database from './databaseCore' -import * as dbCore from './databaseCore' - -export { dbCore as database } \ No newline at end of file +export { database } \ No newline at end of file diff --git a/routes/_database/databaseCore.js b/routes/_database/databaseCore.js index ac237cec..a66dc52c 100644 --- a/routes/_database/databaseCore.js +++ b/routes/_database/databaseCore.js @@ -1,260 +1,4 @@ -import { - toReversePaddedBigInt -} from './utils' -import { - getDatabase, - dbPromise, - deleteDatabase, -} from './databaseLifecycle' - -import { - META_STORE, - STATUS_TIMELINES_STORE, - STATUSES_STORE, - ACCOUNTS_STORE, - RELATIONSHIPS_STORE, - NOTIFICATIONS_STORE, - NOTIFICATION_TIMELINES_STORE -} from './constants' - -import { - statusesCache, - relationshipsCache, - accountsCache, - metaCache, - notificationsCache, - clearCache, - getInCache, - hasInCache, - setInCache -} from './cache' - -// -// helpers -// - -async function getGenericEntityWithId(store, cache, instanceName, id) { - if (hasInCache(cache, instanceName, id)) { - return getInCache(cache, instanceName, id) - } - const db = await getDatabase(instanceName) - let result = await dbPromise(db, store, 'readonly', (store, callback) => { - store.get(id).onsuccess = (e) => callback(e.target.result) - }) - setInCache(cache, instanceName, id, result) - return result -} - -async function setGenericEntityWithId(store, cache, instanceName, entity) { - setInCache(cache, instanceName, entity.id, entity) - const db = await getDatabase(instanceName) - return await dbPromise(db, store, 'readwrite', (store) => { - store.put(entity) - }) -} - -// -// timelines/statuses/notifications -// - -function createKeyRange(timeline, maxId) { - let negBigInt = maxId && toReversePaddedBigInt(maxId) - let start = negBigInt ? (timeline + '\u0000' + negBigInt) : (timeline + '\u0000') - let end = timeline + '\u0000\uffff' - return IDBKeyRange.bound(start, end, false, false) -} - -async function getNotificationTimeline(instanceName, timeline, maxId, limit) { - let storeNames = [NOTIFICATION_TIMELINES_STORE, NOTIFICATIONS_STORE] - const db = await getDatabase(instanceName) - return await dbPromise(db, storeNames, 'readonly', (stores, callback) => { - let [ timelineStore, notificationsStore ] = stores - let keyRange = createKeyRange(timeline, maxId) - - timelineStore.getAll(keyRange, limit).onsuccess = e => { - let timelineResults = e.target.result - let res = new Array(timelineResults.length) - timelineResults.forEach((timelineResult, i) => { - notificationsStore.get(timelineResult.notificationId).onsuccess = e => { - res[i] = e.target.result - } - }) - callback(res) - } - }) -} - -async function getStatusTimeline(instanceName, timeline, maxId, limit) { - let storeNames = [STATUS_TIMELINES_STORE, STATUSES_STORE] - const db = await getDatabase(instanceName) - return await dbPromise(db, storeNames, 'readonly', (stores, callback) => { - let [ timelineStore, statusesStore ] = stores - let keyRange = createKeyRange(timeline, maxId) - - timelineStore.getAll(keyRange, limit).onsuccess = e => { - let timelineResults = e.target.result - let res = new Array(timelineResults.length) - timelineResults.forEach((timelineResult, i) => { - statusesStore.get(timelineResult.statusId).onsuccess = e => { - res[i] = e.target.result - } - }) - callback(res) - } - }) -} - -export async function getTimeline(instanceName, timeline, maxId = null, limit = 20) { - return timeline === 'notifications' ? - await getNotificationTimeline(instanceName, timeline, maxId, limit) : - await getStatusTimeline(instanceName, timeline, maxId, limit) -} - -function createTimelineId(timeline, id) { - // reverse chronological order, prefixed by timeline - return timeline + '\u0000' + toReversePaddedBigInt(id) -} - -async function insertTimelineNotifications(instanceName, timeline, notifications) { - let storeNames = [NOTIFICATION_TIMELINES_STORE, NOTIFICATIONS_STORE, ACCOUNTS_STORE] - for (let notification of notifications) { - setInCache(notificationsCache, instanceName, notification.id, notification) - setInCache(accountsCache, instanceName, notification.account.id, notification.account) - } - const db = await getDatabase(instanceName) - await dbPromise(db, storeNames, 'readwrite', (stores) => { - let [ timelineStore, notificationsStore, accountsStore ] = stores - for (let notification of notifications) { - notificationsStore.put(notification) - timelineStore.put({ - id: createTimelineId(timeline, notification.id), - notificationId: notification.id - }) - accountsStore.put(notification.account) - } - }) -} - -async function insertTimelineStatuses(instanceName, timeline, statuses) { - let storeNames = [STATUS_TIMELINES_STORE, STATUSES_STORE, ACCOUNTS_STORE] - for (let status of statuses) { - setInCache(statusesCache, instanceName, status.id, status) - setInCache(accountsCache, instanceName, status.account.id, status.account) - if (status.reblog) { - setInCache(accountsCache, instanceName, status.reblog.account.id, status.reblog.account) - } - } - const db = await getDatabase(instanceName) - await dbPromise(db, storeNames, 'readwrite', (stores) => { - let [ timelineStore, statusesStore, accountsStore ] = stores - for (let status of statuses) { - statusesStore.put(status) - timelineStore.put({ - id: createTimelineId(timeline, status.id), - statusId: status.id - }) - accountsStore.put(status.account) - if (status.reblog) { - accountsStore.put(status.reblog.account) - } - } - }) -} - -export async function insertTimelineItems(instanceName, timeline, timelineItems) { - return timeline === 'notifications' ? - await insertTimelineNotifications(instanceName, timeline, timelineItems) : - await insertTimelineStatuses(instanceName, timeline, timelineItems) -} - -export async function getStatus(instanceName, statusId) { - return await getGenericEntityWithId(STATUSES_STORE, statusesCache, instanceName, statusId) -} - -export async function getNotification(instanceName, notificationId) { - return await getGenericEntityWithId(NOTIFICATIONS_STORE, notificationsCache, instanceName, notificationId) -} - -// -// meta -// - -async function getMetaProperty(instanceName, key) { - if (hasInCache(metaCache, instanceName, key)) { - return getInCache(metaCache, instanceName, key) - } - const db = await getDatabase(instanceName) - let result = await dbPromise(db, META_STORE, 'readonly', (store, callback) => { - store.get(key).onsuccess = (e) => { - callback(e.target.result && e.target.result.value) - } - }) - setInCache(metaCache, instanceName, key, result) - return result -} - -async function setMetaProperty(instanceName, key, value) { - setInCache(metaCache, instanceName, key, value) - const db = await getDatabase(instanceName) - return await dbPromise(db, META_STORE, 'readwrite', (store) => { - store.put({ - key: key, - value: value - }) - }) -} - -export async function getInstanceVerifyCredentials(instanceName) { - return await getMetaProperty(instanceName, 'verifyCredentials') -} - -export async function setInstanceVerifyCredentials(instanceName, value) { - return await setMetaProperty(instanceName, 'verifyCredentials', value) -} - -export async function getInstanceInfo(instanceName) { - return await getMetaProperty(instanceName, 'instance') -} - -export async function setInstanceInfo(instanceName, value) { - return await setMetaProperty(instanceName, 'instance', value) -} - -export async function getLists(instanceName) { - return await getMetaProperty(instanceName, 'lists') -} - -export async function setLists(instanceName, value) { - return await setMetaProperty(instanceName, 'lists', value) -} - -// -// accounts/relationships -// - -export async function getAccount(instanceName, accountId) { - return await getGenericEntityWithId(ACCOUNTS_STORE, accountsCache, instanceName, accountId) -} - -export async function setAccount(instanceName, account) { - return await setGenericEntityWithId(ACCOUNTS_STORE, accountsCache, instanceName, account) -} - -export async function getRelationship(instanceName, accountId) { - return await getGenericEntityWithId(RELATIONSHIPS_STORE, relationshipsCache, instanceName, accountId) -} - -export async function setRelationship(instanceName, relationship) { - return await setGenericEntityWithId(RELATIONSHIPS_STORE, relationshipsCache, instanceName, relationship) -} - -// -// lifecycle -// - -export async function clearDatabaseForInstance(instanceName) { - clearCache(statusesCache, instanceName) - clearCache(accountsCache, instanceName) - clearCache(metaCache, instanceName) - await deleteDatabase(instanceName) -} \ No newline at end of file +export * from './accountsAndRelationships' +export * from './clear' +export * from './meta' +export * from './timelines' \ No newline at end of file diff --git a/routes/_database/helpers.js b/routes/_database/helpers.js new file mode 100644 index 00000000..584652dd --- /dev/null +++ b/routes/_database/helpers.js @@ -0,0 +1,22 @@ +import { dbPromise, getDatabase } from './databaseLifecycle' +import { getInCache, hasInCache, setInCache } from './cache' + +export async function getGenericEntityWithId(store, cache, instanceName, id) { + if (hasInCache(cache, instanceName, id)) { + return getInCache(cache, instanceName, id) + } + const db = await getDatabase(instanceName) + let result = await dbPromise(db, store, 'readonly', (store, callback) => { + store.get(id).onsuccess = (e) => callback(e.target.result) + }) + setInCache(cache, instanceName, id, result) + return result +} + +export async function setGenericEntityWithId(store, cache, instanceName, entity) { + setInCache(cache, instanceName, entity.id, entity) + const db = await getDatabase(instanceName) + return await dbPromise(db, store, 'readwrite', (store) => { + store.put(entity) + }) +} \ No newline at end of file diff --git a/routes/_database/meta.js b/routes/_database/meta.js new file mode 100644 index 00000000..aa0e1490 --- /dev/null +++ b/routes/_database/meta.js @@ -0,0 +1,52 @@ +import { dbPromise, getDatabase } from './databaseLifecycle' +import { META_STORE } from './constants' +import { metaCache, hasInCache, getInCache, setInCache } from './cache' + +async function getMetaProperty(instanceName, key) { + if (hasInCache(metaCache, instanceName, key)) { + return getInCache(metaCache, instanceName, key) + } + const db = await getDatabase(instanceName) + let result = await dbPromise(db, META_STORE, 'readonly', (store, callback) => { + store.get(key).onsuccess = (e) => { + callback(e.target.result && e.target.result.value) + } + }) + setInCache(metaCache, instanceName, key, result) + return result +} + +async function setMetaProperty(instanceName, key, value) { + setInCache(metaCache, instanceName, key, value) + const db = await getDatabase(instanceName) + return await dbPromise(db, META_STORE, 'readwrite', (store) => { + store.put({ + key: key, + value: value + }) + }) +} + +export async function getInstanceVerifyCredentials(instanceName) { + return await getMetaProperty(instanceName, 'verifyCredentials') +} + +export async function setInstanceVerifyCredentials(instanceName, value) { + return await setMetaProperty(instanceName, 'verifyCredentials', value) +} + +export async function getInstanceInfo(instanceName) { + return await getMetaProperty(instanceName, 'instance') +} + +export async function setInstanceInfo(instanceName, value) { + return await setMetaProperty(instanceName, 'instance', value) +} + +export async function getLists(instanceName) { + return await getMetaProperty(instanceName, 'lists') +} + +export async function setLists(instanceName, value) { + return await setMetaProperty(instanceName, 'lists', value) +} \ No newline at end of file diff --git a/routes/_database/timelines.js b/routes/_database/timelines.js new file mode 100644 index 00000000..468b9e2b --- /dev/null +++ b/routes/_database/timelines.js @@ -0,0 +1,126 @@ +import { toReversePaddedBigInt } from './utils' +import { dbPromise, getDatabase } from './databaseLifecycle' +import { accountsCache, notificationsCache, setInCache, statusesCache } from './cache' +import { + ACCOUNTS_STORE, NOTIFICATION_TIMELINES_STORE, NOTIFICATIONS_STORE, STATUS_TIMELINES_STORE, + STATUSES_STORE +} from './constants' +import { getGenericEntityWithId } from './helpers' + +function createKeyRange(timeline, maxId) { + let negBigInt = maxId && toReversePaddedBigInt(maxId) + let start = negBigInt ? (timeline + '\u0000' + negBigInt) : (timeline + '\u0000') + let end = timeline + '\u0000\uffff' + return IDBKeyRange.bound(start, end, false, false) +} + +async function getNotificationTimeline(instanceName, timeline, maxId, limit) { + let storeNames = [NOTIFICATION_TIMELINES_STORE, NOTIFICATIONS_STORE] + const db = await getDatabase(instanceName) + return await dbPromise(db, storeNames, 'readonly', (stores, callback) => { + let [ timelineStore, notificationsStore ] = stores + let keyRange = createKeyRange(timeline, maxId) + + timelineStore.getAll(keyRange, limit).onsuccess = e => { + let timelineResults = e.target.result + let res = new Array(timelineResults.length) + timelineResults.forEach((timelineResult, i) => { + notificationsStore.get(timelineResult.notificationId).onsuccess = e => { + res[i] = e.target.result + } + }) + callback(res) + } + }) +} + +async function getStatusTimeline(instanceName, timeline, maxId, limit) { + let storeNames = [STATUS_TIMELINES_STORE, STATUSES_STORE] + const db = await getDatabase(instanceName) + return await dbPromise(db, storeNames, 'readonly', (stores, callback) => { + let [ timelineStore, statusesStore ] = stores + let keyRange = createKeyRange(timeline, maxId) + + timelineStore.getAll(keyRange, limit).onsuccess = e => { + let timelineResults = e.target.result + let res = new Array(timelineResults.length) + timelineResults.forEach((timelineResult, i) => { + statusesStore.get(timelineResult.statusId).onsuccess = e => { + res[i] = e.target.result + } + }) + callback(res) + } + }) +} + +export async function getTimeline(instanceName, timeline, maxId = null, limit = 20) { + return timeline === 'notifications' ? + await getNotificationTimeline(instanceName, timeline, maxId, limit) : + await getStatusTimeline(instanceName, timeline, maxId, limit) +} + +function createTimelineId(timeline, id) { + // reverse chronological order, prefixed by timeline + return timeline + '\u0000' + toReversePaddedBigInt(id) +} + +async function insertTimelineNotifications(instanceName, timeline, notifications) { + let storeNames = [NOTIFICATION_TIMELINES_STORE, NOTIFICATIONS_STORE, ACCOUNTS_STORE] + for (let notification of notifications) { + setInCache(notificationsCache, instanceName, notification.id, notification) + setInCache(accountsCache, instanceName, notification.account.id, notification.account) + } + const db = await getDatabase(instanceName) + await dbPromise(db, storeNames, 'readwrite', (stores) => { + let [ timelineStore, notificationsStore, accountsStore ] = stores + for (let notification of notifications) { + notificationsStore.put(notification) + timelineStore.put({ + id: createTimelineId(timeline, notification.id), + notificationId: notification.id + }) + accountsStore.put(notification.account) + } + }) +} + +async function insertTimelineStatuses(instanceName, timeline, statuses) { + let storeNames = [STATUS_TIMELINES_STORE, STATUSES_STORE, ACCOUNTS_STORE] + for (let status of statuses) { + setInCache(statusesCache, instanceName, status.id, status) + setInCache(accountsCache, instanceName, status.account.id, status.account) + if (status.reblog) { + setInCache(accountsCache, instanceName, status.reblog.account.id, status.reblog.account) + } + } + const db = await getDatabase(instanceName) + await dbPromise(db, storeNames, 'readwrite', (stores) => { + let [ timelineStore, statusesStore, accountsStore ] = stores + for (let status of statuses) { + statusesStore.put(status) + timelineStore.put({ + id: createTimelineId(timeline, status.id), + statusId: status.id + }) + accountsStore.put(status.account) + if (status.reblog) { + accountsStore.put(status.reblog.account) + } + } + }) +} + +export async function insertTimelineItems(instanceName, timeline, timelineItems) { + return timeline === 'notifications' ? + await insertTimelineNotifications(instanceName, timeline, timelineItems) : + await insertTimelineStatuses(instanceName, timeline, timelineItems) +} + +export async function getStatus(instanceName, statusId) { + return await getGenericEntityWithId(STATUSES_STORE, statusesCache, instanceName, statusId) +} + +export async function getNotification(instanceName, notificationId) { + return await getGenericEntityWithId(NOTIFICATIONS_STORE, notificationsCache, instanceName, notificationId) +} \ No newline at end of file