mirror of
https://github.com/coracle-social/coracle.git
synced 2024-09-19 11:43:35 +00:00
Switch from multiple instances of localforage to dumping everything into one key per table. Fix logout as well.
This commit is contained in:
parent
0d97800713
commit
3ea222b120
@ -1,10 +1,10 @@
|
||||
import type {Writable} from 'svelte/store'
|
||||
import {debounce} from 'throttle-debounce'
|
||||
import {partition, is, prop, find, without, pluck, all, identity} from 'ramda'
|
||||
import {omit, partition, is, find, without, pluck, all, identity} from 'ramda'
|
||||
import {writable, derived} from 'svelte/store'
|
||||
import {switcherFn, createMap, ensurePlural} from 'hurdak/lib/hurdak'
|
||||
import {createMap, ensurePlural} from 'hurdak/lib/hurdak'
|
||||
import {log, error} from 'src/util/logger'
|
||||
import {defer, where, now, timedelta, asyncIterableToArray} from 'src/util/misc'
|
||||
import {where, now, timedelta} from 'src/util/misc'
|
||||
|
||||
// Types
|
||||
|
||||
@ -56,8 +56,8 @@ const call = (topic, payload): Promise<Message> => {
|
||||
})
|
||||
}
|
||||
|
||||
const callLocalforage = async (storeName, method, ...args) => {
|
||||
const message = await call('localforage.call', {storeName, method, args})
|
||||
const callLocalforage = async (method, ...args) => {
|
||||
const message = await call('localforage.call', {method, args})
|
||||
|
||||
if (message.topic !== 'localforage.return') {
|
||||
throw new Error(`callLocalforage received invalid response: ${message}`)
|
||||
@ -66,55 +66,6 @@ const callLocalforage = async (storeName, method, ...args) => {
|
||||
return message.payload
|
||||
}
|
||||
|
||||
|
||||
// Methods that proxy localforage
|
||||
|
||||
const iterate = (storeName, where = {}) => ({
|
||||
[Symbol.asyncIterator]() {
|
||||
let done = false
|
||||
let promise = defer()
|
||||
const messages = []
|
||||
const channel = new Channel({
|
||||
onMessage: m => switcherFn(m.topic, {
|
||||
'localforage.item': () => {
|
||||
promise.resolve()
|
||||
messages.push(m.payload)
|
||||
},
|
||||
'localforage.iterationComplete': () => {
|
||||
done = true
|
||||
promise.resolve()
|
||||
channel.close()
|
||||
},
|
||||
default: () => {
|
||||
throw new Error(`Invalid topic ${m.topic}`)
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
channel.send('localforage.iterate', {storeName, where})
|
||||
|
||||
const next = async () => {
|
||||
if (done) {
|
||||
return {done}
|
||||
}
|
||||
|
||||
const [value] = messages.splice(0, 1)
|
||||
|
||||
if (value) {
|
||||
return {done, value}
|
||||
} else {
|
||||
promise = defer()
|
||||
|
||||
await promise
|
||||
|
||||
return next()
|
||||
}
|
||||
}
|
||||
|
||||
return {next}
|
||||
}
|
||||
})
|
||||
|
||||
// Local copy of data so we can provide a sync observable interface. The worker
|
||||
// is just for storing data and processing expensive queries
|
||||
|
||||
@ -145,7 +96,7 @@ class Table {
|
||||
;(async () => {
|
||||
const t = Date.now()
|
||||
|
||||
this._setAndNotify(await this.opts.initialize(this))
|
||||
this._setAndNotify(await this.opts.initialize(this) || {})
|
||||
|
||||
const {length: recordsCount} = Object.keys(this.data)
|
||||
const timeElapsed = Date.now() - t
|
||||
@ -155,6 +106,9 @@ class Table {
|
||||
this.ready.set(true)
|
||||
})()
|
||||
}
|
||||
_persist = debounce(10_000, () => {
|
||||
callLocalforage('setItem', this.name, this.data)
|
||||
})
|
||||
_setAndNotify(newData) {
|
||||
// Update our local copy
|
||||
this.data = newData
|
||||
@ -163,6 +117,9 @@ class Table {
|
||||
for (const cb of this.listeners) {
|
||||
cb(this.data)
|
||||
}
|
||||
|
||||
// Save to localstorage
|
||||
this._persist()
|
||||
}
|
||||
subscribe(cb) {
|
||||
this.listeners.push(cb)
|
||||
@ -179,8 +136,6 @@ class Table {
|
||||
}
|
||||
|
||||
this._setAndNotify({...this.data, ...newData})
|
||||
|
||||
callLocalforage(this.name, 'setItems', newData)
|
||||
}
|
||||
async bulkPatch(updates: Record<string, object>): Promise<void> {
|
||||
if (is(Array, updates)) {
|
||||
@ -195,7 +150,7 @@ class Table {
|
||||
this.bulkPut({...this.data, ...newData})
|
||||
}
|
||||
async bulkRemove(keys) {
|
||||
await callLocalforage(this.name, 'removeItems', keys)
|
||||
this._setAndNotify(omit(keys, this.data))
|
||||
}
|
||||
put(item) {
|
||||
return this.bulkPut(createMap(this.pk, [item]))
|
||||
@ -206,18 +161,15 @@ class Table {
|
||||
remove(k) {
|
||||
return this.bulkRemove([k])
|
||||
}
|
||||
drop() {
|
||||
return callLocalforage(this.name, 'dropInstance')
|
||||
async drop() {
|
||||
return callLocalforage('removeItem', this.name)
|
||||
}
|
||||
dump() {
|
||||
return callLocalforage(this.name, 'dump')
|
||||
async dump() {
|
||||
return callLocalforage('getItem', this.name)
|
||||
}
|
||||
toArray() {
|
||||
return Object.values(this.data)
|
||||
}
|
||||
iter(spec = {}) {
|
||||
return asyncIterableToArray(iterate(name, spec), prop('v'))
|
||||
}
|
||||
all(spec = {}) {
|
||||
return this.toArray().filter(where(spec))
|
||||
}
|
||||
@ -238,7 +190,7 @@ const relays = new Table('relays', 'url')
|
||||
const routes = new Table('routes', 'id', {
|
||||
initialize: async table => {
|
||||
const isValid = r => r.last_seen > now() - timedelta(7, 'days')
|
||||
const [valid, invalid] = partition(isValid, Object.values(await table.dump()))
|
||||
const [valid, invalid] = partition(isValid, Object.values(await table.dump() || {}))
|
||||
|
||||
// Delete stale routes asynchronously
|
||||
table.bulkRemove(pluck('id', invalid))
|
||||
|
@ -1,83 +1,22 @@
|
||||
import lf from 'localforage'
|
||||
import memoryStorageDriver from 'localforage-memoryStorageDriver'
|
||||
import {switcherFn} from 'hurdak/lib/hurdak'
|
||||
import {error, warn} from 'src/util/logger'
|
||||
import {where} from 'src/util/misc'
|
||||
import {error} from 'src/util/logger'
|
||||
|
||||
// Firefox private mode doesn't have access to any storage options
|
||||
lf.defineDriver(memoryStorageDriver)
|
||||
lf.setDriver([lf.INDEXEDDB, lf.WEBSQL, lf.LOCALSTORAGE, 'memoryStorageDriver'])
|
||||
|
||||
const stores = {}
|
||||
|
||||
const getStore = storeName => {
|
||||
if (!stores[storeName]) {
|
||||
stores[storeName] = lf.createInstance({name: 'coracle', storeName})
|
||||
}
|
||||
|
||||
if (stores[storeName].dropped) {
|
||||
warn(`Dropped instance ${storeName} was requested`)
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
return stores[storeName]
|
||||
}
|
||||
|
||||
addEventListener('message', async ({data: {topic, payload, channel}}) => {
|
||||
const reply = (topic, payload) => postMessage({channel, topic, payload})
|
||||
|
||||
switcherFn(topic, {
|
||||
'localforage.call': async () => {
|
||||
const {storeName, method, args} = payload
|
||||
const instance = getStore(storeName)
|
||||
const {method, args} = payload
|
||||
|
||||
if (instance) {
|
||||
const result = await switcherFn(method, {
|
||||
dump: () => new Promise(resolve => {
|
||||
const result = {}
|
||||
const result = await lf[method](...args)
|
||||
|
||||
instance.iterate(
|
||||
(v, k, i) => { result[k] = v },
|
||||
() => resolve(result),
|
||||
)
|
||||
}),
|
||||
setItems: async () => {
|
||||
for (const [k, v] of Object.entries(args[0])) {
|
||||
await instance.setItem(k, v)
|
||||
}
|
||||
},
|
||||
removeItems: async () => {
|
||||
for (const k of args[0]) {
|
||||
await instance.removeItem(k)
|
||||
}
|
||||
},
|
||||
drop: async () => {
|
||||
instance.dropped = true
|
||||
await instance.drop()
|
||||
},
|
||||
default: () => instance[method](...args),
|
||||
})
|
||||
|
||||
reply('localforage.return', result)
|
||||
}
|
||||
},
|
||||
'localforage.iterate': async () => {
|
||||
const matchesFilter = where(payload.where)
|
||||
const instance = getStore(payload.storeName)
|
||||
|
||||
if (instance) {
|
||||
instance.iterate(
|
||||
(v, k, i) => {
|
||||
if (matchesFilter(v)) {
|
||||
reply('localforage.item', {v, k, i})
|
||||
}
|
||||
},
|
||||
() => {
|
||||
reply('localforage.iterationComplete')
|
||||
},
|
||||
)
|
||||
}
|
||||
reply('localforage.return', result)
|
||||
},
|
||||
default: () => {
|
||||
throw new Error(`invalid topic: ${topic}`)
|
||||
|
@ -5,7 +5,7 @@
|
||||
import {renderContent, noEvent} from "src/util/html"
|
||||
import {displayPerson} from "src/util/nostr"
|
||||
import Anchor from 'src/partials/Anchor.svelte'
|
||||
import {getPubkeyWriteRelays} from 'src/agent/relays'
|
||||
import {getPubkeyWriteRelays, sampleRelays} from 'src/agent/relays'
|
||||
import user from 'src/agent/user'
|
||||
import {routes} from "src/app/ui"
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
const {petnamePubkeys} = user
|
||||
|
||||
const addPetname = pubkey => {
|
||||
const [{url}] = getPubkeyWriteRelays(pubkey)
|
||||
const [{url}] = sampleRelays(getPubkeyWriteRelays(pubkey))
|
||||
|
||||
user.addPetname(pubkey, url, displayPerson(person))
|
||||
}
|
||||
|
@ -3,17 +3,13 @@
|
||||
import Anchor from 'src/partials/Anchor.svelte'
|
||||
import Content from "src/partials/Content.svelte"
|
||||
import database from 'src/agent/database'
|
||||
import pool from 'src/agent/pool'
|
||||
|
||||
let confirmed = false
|
||||
|
||||
const confirm = async () => {
|
||||
confirmed = true
|
||||
|
||||
await Promise.all([
|
||||
...pool.getConnections().map(c => c.disconnect()),
|
||||
database.dropAll(),
|
||||
])
|
||||
await database.dropAll()
|
||||
|
||||
localStorage.clear()
|
||||
|
||||
|
@ -201,15 +201,6 @@ export const defer = () => {
|
||||
return Object.assign(p, {resolve, reject})
|
||||
}
|
||||
|
||||
export const asyncIterableToArray = async (it, f = identity) => {
|
||||
const result = []
|
||||
for await (const x of it) {
|
||||
result.push(f(x))
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
export const avg = xs => sum(xs) / xs.length
|
||||
|
||||
export const where = filters =>
|
||||
|
Loading…
Reference in New Issue
Block a user