diff --git a/ROADMAP.md b/ROADMAP.md index aac30a7d..86fea029 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,5 +1,20 @@ # Current +- [ ] Fix re-connects +- [ ] Fix memory usage + - Re-write database + - Use LRU cache and persist that instead. Use purgeStale/dump/load + - Split state persistence elsewhere + - Keep it focused to abstract interface, split actual tables out elsewhere + - Put all other state in same place + - Re-write to use arrays with an index of id to index + - Fix compatibility, or clear data on first load of new version + - Add table of user events, derive profile from this using `watch`. + - Refine sync, set up some kind of system where we register tables with events coming in + - People.petnames is massive. Split people caches up + - Display/picture/about + - Minimal zapper info + - Drop petnames - [ ] Show loading/success on zap invoice screen - [ ] Fix iOS/safari/firefox - [ ] Update https://nostr.com/clients/coracle diff --git a/package.json b/package.json index 29fd4b8e..ec1b7c77 100644 --- a/package.json +++ b/package.json @@ -9,9 +9,9 @@ "preview": "vite preview", "check:es": "eslint src/*/** --quiet", "check:ts": "svelte-check --tsconfig ./tsconfig.json --threshold error", - "check:fmt": "prettier --check $(git diff --name-only --diff-filter d | grep -E 'js|svelte$' | xargs)", + "check:fmt": "prettier --check $(git diff --name-only --diff-filter d | grep -E 'js|ts|svelte$' | xargs)", "check": "run-p check:*", - "format": "prettier --write $(git diff --name-only --diff-filter d | grep -E 'js|svelte$' | xargs)", + "format": "prettier --write $(git diff --name-only --diff-filter d | grep -E 'js|ts|svelte$' | xargs)", "watch": "find src -type f | entr -r" }, "devDependencies": { diff --git a/src/App.svelte b/src/App.svelte index 878ccd46..127732db 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -14,7 +14,7 @@ import {timedelta, shuffle, now, sleep} from "src/util/misc" import {displayPerson, isLike} from "src/util/nostr" import cmd from "src/agent/cmd" - import database from "src/agent/database" + import {ready, onReady, relays} from "src/agent/state" import keys from "src/agent/keys" import network from "src/agent/network" import pool from "src/agent/pool" @@ -64,14 +64,12 @@ import AddRelay from "src/views/relays/AddRelay.svelte" import RelayCard from "src/views/relays/RelayCard.svelte" - Object.assign(window, {cmd, database, user, keys, network, pool, sync}) + Object.assign(window, {cmd, user, keys, network, pool, sync}) export let url = "" let scrollY - const {ready} = database - const closeModal = async () => { modal.clear() menuIsOpen.set(false) @@ -115,7 +113,7 @@ } }) - database.onReady(() => { + onReady(() => { initializeRelayList() if (user.getProfile()) { @@ -132,7 +130,7 @@ // Find relays with old/missing metadata and refresh them. Only pick a // few so we're not sending too many concurrent http requests const staleRelays = shuffle( - await database.relays.all({ + await relays.all({ "refreshed_at:lt": now() - timedelta(7, "days"), }) ).slice(0, 10) @@ -159,7 +157,7 @@ }) ) - database.relays.bulkPatch(createMap("url", freshRelays.filter(identity))) + relays.bulkPatch(freshRelays.filter(identity)) }, 30_000) return () => { diff --git a/src/agent/cmd.ts b/src/agent/cmd.ts index 8310a50a..7f59c76b 100644 --- a/src/agent/cmd.ts +++ b/src/agent/cmd.ts @@ -2,7 +2,7 @@ import {pick, last, prop, uniqBy} from 'ramda' import {get} from 'svelte/store' import {roomAttrs, displayPerson, findReplyId, findRootId} from 'src/util/nostr' import {getPubkeyWriteRelays, getRelayForPersonHint, sampleRelays} from 'src/agent/relays' -import database from 'src/agent/database' +import {getPersonWithFallback} from 'src/agent/state' import pool from 'src/agent/pool' import sync from 'src/agent/sync' import keys from 'src/agent/keys' @@ -43,7 +43,7 @@ const createDirectMessage = (pubkey, content) => const createNote = (content, mentions = [], topics = []) => { mentions = mentions.map(pubkey => { - const name = displayPerson(database.getPersonWithFallback(pubkey)) + const name = displayPerson(getPersonWithFallback(pubkey)) const [{url}] = sampleRelays(getPubkeyWriteRelays(pubkey)) return ["p", pubkey, url, name] diff --git a/src/agent/database.ts b/src/agent/database.ts index 69f3efd5..d9df45c9 100644 --- a/src/agent/database.ts +++ b/src/agent/database.ts @@ -1,10 +1,4 @@ -import type {Writable} from 'svelte/store' -import {throttle} from 'throttle-debounce' -import {omit, prop, partition, is, find, without, pluck, all, identity} from 'ramda' -import {writable, derived} from 'svelte/store' -import {createMap, isObject, ensurePlural} from 'hurdak/lib/hurdak' -import {log, error} from 'src/util/logger' -import {where, now, timedelta} from 'src/util/misc' +import {error} from 'src/util/logger' // Types @@ -56,7 +50,7 @@ const call = (topic, payload): Promise => { }) } -const callLocalforage = async (method, ...args) => { +export const lf = async (method, ...args) => { const message = await call('localforage.call', {method, args}) if (message.topic !== 'localforage.return') { @@ -65,244 +59,3 @@ const callLocalforage = async (method, ...args) => { return message.payload } - -// Local copy of data so we can provide a sync observable interface. The worker -// is just for storing data and processing expensive queries - -const registry = {} as Record - -type TableOpts = { - initialize?: (table: Table) => Promise -} - -class Table { - name: string - pk: string - opts: TableOpts - listeners: Array<(data: Record) => void> - data: Record - ready: Writable - constructor(name, pk, opts: TableOpts = {}) { - this.name = name - this.pk = pk - this.opts = {initialize: t => this.dump(), ...opts} - this.listeners = [] - this.data = {} - this.ready = writable(false) - - registry[name] = this - - // Sync from storage initially - ;(async () => { - const t = Date.now() - - this._setAndNotify(await this.opts.initialize(this) || {}) - - const {length: recordsCount} = Object.keys(this.data) - const timeElapsed = Date.now() - t - - log(`Table ${name} ready in ${timeElapsed}ms (${recordsCount} records)`) - - this.ready.set(true) - })() - } - _persist = throttle(4_000, () => { - callLocalforage('setItem', this.name, this.data) - }) - _setAndNotify(newData) { - if (!isObject(newData)) { - throw new Error(`Invalid data persisted`) - } - - // Update our local copy - this.data = newData - - // Notify subscribers - for (const cb of this.listeners) { - cb(this.data) - } - - // Save to localstorage - this._persist() - } - subscribe(cb) { - this.listeners.push(cb) - - cb(this.data) - - return () => { - this.listeners = without([cb], this.listeners) - } - } - async bulkPut(newData: Record): Promise { - if (is(Array, newData)) { - throw new Error(`Updates must be an object, not an array`) - } - - this._setAndNotify({...this.data, ...newData}) - } - async bulkPatch(updates: Record): Promise { - if (is(Array, updates)) { - throw new Error(`Updates must be an object, not an array`) - } - - const newData = {} - for (const [k, v] of Object.entries(updates)) { - newData[k] = {...this.data[k], ...v} - } - - this.bulkPut({...this.data, ...newData}) - } - async bulkRemove(keys) { - this._setAndNotify(omit(keys, this.data)) - } - put(item) { - return this.bulkPut(createMap(this.pk, [item])) - } - patch(item) { - return this.bulkPatch(createMap(this.pk, [item])) - } - remove(k) { - return this.bulkRemove([k]) - } - async drop() { - return callLocalforage('removeItem', this.name) - } - async dump() { - return callLocalforage('getItem', this.name) - } - toArray() { - return Object.values(this.data) - } - all(spec = {}) { - return this.toArray().filter(where(spec)) - } - one(spec = {}) { - return find(where(spec), this.toArray()) - } - get(k) { - return this.data[k] - } -} - -const people = new Table('people', 'pubkey') -const contacts = new Table('contacts', 'pubkey') - -const rooms = new Table('rooms', 'id', { - initialize: async table => { - // Remove rooms that our user hasn't joined - const rooms = Object.values(await table.dump() || {}) - const [valid, invalid] = partition(prop('joined'), rooms) - - if (invalid.length > 0) { - table.bulkRemove(pluck('id', invalid)) - } - - return createMap('id', valid) - }, -}) - -const alerts = new Table('alerts', 'id', { - initialize: async table => { - // TEMPORARY: we changed our alerts format, clear out the old version - const alerts = Object.values(await table.dump() || {}) - const [valid, invalid] = partition(alert => typeof alert.isMention === 'boolean', alerts) - - if (invalid.length > 0) { - table.bulkRemove(pluck('id', invalid)) - } - - return createMap('id', valid) - }, -}) - -const relays = new Table('relays', 'url') - -const routes = new Table('routes', 'id', { - initialize: async table => { - const isValid = r => r.last_seen > now() - timedelta(1, 'days') - const [valid, invalid] = partition(isValid, Object.values(await table.dump() || {})) - - // Delete stale routes asynchronously - table.bulkRemove(pluck('id', invalid)) - - return createMap('id', valid) - }, -}) - -// Helper to allow us to listen to changes of any given table - -const listener = (() => { - let listeners = [] - - for (const table of Object.values(registry) as Array) { - table.subscribe(() => listeners.forEach(f => f(table.name))) - } - - return { - subscribe: f => { - listeners.push(f) - - return () => { - listeners = without([f], listeners) - } - }, - } -})() - -// Helper to re-run a query every time a given table changes - -const watch = (names, f) => { - names = ensurePlural(names) - - const store = writable(null) - const tables = names.map(name => registry[name]) - - // Initialize synchronously if possible - const initialValue = f(...tables) - if (is(Promise, initialValue)) { - initialValue.then(v => store.set(v)) - } else { - store.set(initialValue) - } - - // Debounce refresh so we don't get UI lag - const refresh = throttle(300, async () => store.set(await f(...tables))) - - // Listen for changes - listener.subscribe(name => { - if (names.includes(name)) { - refresh() - } - }) - - return store -} - -// Other utilities - -const getPersonWithFallback = pubkey => people.get(pubkey) || {pubkey} - -const dropAll = async () => { - for (const table of Object.values(registry)) { - await table.drop() - - log(`Successfully dropped table ${table.name}`) - } -} - -const ready = derived(pluck('ready', Object.values(registry)), all(identity)) - -const onReady = cb => { - const unsub = ready.subscribe($ready => { - if ($ready) { - cb() - setTimeout(() => unsub()) - } - }) -} - -export default { - watch, getPersonWithFallback, dropAll, people, contacts, rooms, - alerts, relays, routes, ready, onReady, -} diff --git a/src/agent/network.ts b/src/agent/network.ts index 9ff1f9ce..5df28706 100644 --- a/src/agent/network.ts +++ b/src/agent/network.ts @@ -3,19 +3,19 @@ import {sortBy, assoc, uniq, uniqBy, prop, propEq, reject, groupBy, pluck} from import {personKinds, findReplyId} from 'src/util/nostr' import {log} from 'src/util/logger' import {chunk} from 'hurdak/lib/hurdak' -import {batch, timedelta, now} from 'src/util/misc' +import {batch, now, timedelta} from 'src/util/misc' import { getRelaysForEventParent, getAllPubkeyWriteRelays, aggregateScores, getRelaysForEventChildren, sampleRelays, } from 'src/agent/relays' -import database from 'src/agent/database' +import {people} from 'src/agent/state' import pool from 'src/agent/pool' import sync from 'src/agent/sync' const getStalePubkeys = pubkeys => { // If we're not reloading, only get pubkeys we don't already know about return uniq(pubkeys).filter(pubkey => { - const p = database.people.get(pubkey) + const p = people.get(pubkey) return !p || p.updated_at < now() - timedelta(1, 'days') }) @@ -39,11 +39,10 @@ const listen = ({relays, filter, onChunk = null, shouldProcess = true, delay = 5 const load = ({relays, filter, onChunk = null, shouldProcess = true, timeout = 5000}) => { return new Promise(resolve => { - const now = Date.now() const done = new Set() const allEvents = [] - const attemptToComplete = async () => { + const attemptToComplete = async isTimeout => { const sub = await subPromise // If we've already unsubscribed we're good @@ -52,7 +51,6 @@ const load = ({relays, filter, onChunk = null, shouldProcess = true, timeout = 5 } const isDone = done.size === relays.length - const isTimeout = Date.now() - now >= timeout if (isTimeout) { const timedOutRelays = reject(r => done.has(r.url), relays) @@ -78,7 +76,7 @@ const load = ({relays, filter, onChunk = null, shouldProcess = true, timeout = 5 } // If a relay takes too long, give up - setTimeout(attemptToComplete, timeout) + setTimeout(() => attemptToComplete(true), timeout) const subPromise = pool.subscribe({ relays, @@ -98,11 +96,11 @@ const load = ({relays, filter, onChunk = null, shouldProcess = true, timeout = 5 }), onEose: url => { done.add(url) - attemptToComplete() + attemptToComplete(false) }, onError: url => { done.add(url) - attemptToComplete() + attemptToComplete(false) }, }) }) as Promise diff --git a/src/agent/relays.ts b/src/agent/relays.ts index c693f62b..a3f93519 100644 --- a/src/agent/relays.ts +++ b/src/agent/relays.ts @@ -2,10 +2,10 @@ import type {Relay} from 'src/util/types' import LRUCache from 'lru-cache' import {warn} from 'src/util/logger' import {filter, pipe, pick, groupBy, objOf, map, assoc, sortBy, uniqBy, prop} from 'ramda' -import {first, createMap} from 'hurdak/lib/hurdak' +import {first} from 'hurdak/lib/hurdak' import {Tags, isRelay, findReplyId} from 'src/util/nostr' import {shuffle, fetchJson} from 'src/util/misc' -import database from 'src/agent/database' +import {relays, routes} from 'src/agent/state' import pool from 'src/agent/pool' import user from 'src/agent/user' @@ -25,25 +25,22 @@ import user from 'src/agent/user' export const initializeRelayList = async () => { // Throw some hardcoded defaults in there - await database.relays.bulkPatch( - createMap('url', [ - {url: 'wss://brb.io'}, - {url: 'wss://nostr.zebedee.cloud'}, - {url: 'wss://nostr-pub.wellorder.net'}, - {url: 'wss://relay.nostr.band'}, - {url: 'wss://nostr.pleb.network'}, - {url: 'wss://relay.nostrich.de'}, - {url: 'wss://relay.damus.io'}, - ]) - ) + await relays.bulkPatch([ + {url: 'wss://brb.io'}, + {url: 'wss://nostr.zebedee.cloud'}, + {url: 'wss://nostr-pub.wellorder.net'}, + {url: 'wss://relay.nostr.band'}, + {url: 'wss://nostr.pleb.network'}, + {url: 'wss://relay.nostrich.de'}, + {url: 'wss://relay.damus.io'}, + ]) // Load relays from nostr.watch via dufflepud try { const url = import.meta.env.VITE_DUFFLEPUD_URL + '/relay' const json = await fetchJson(url) - const relays = json.relays.filter(isRelay) - await database.relays.bulkPatch(createMap('url', map(objOf('url'), relays))) + await relays.bulkPatch(map(objOf('url'), json.relays.filter(isRelay))) } catch (e) { warn("Failed to fetch relays list", e) } @@ -53,13 +50,13 @@ export const initializeRelayList = async () => { const _getPubkeyRelaysCache = new LRUCache({max: 1000}) -export const getPubkeyRelays = (pubkey, mode = null, routes = null) => { +export const getPubkeyRelays = (pubkey, mode = null, routesOverride = null) => { const filter = mode ? {pubkey, mode} : {pubkey} const key = [mode, pubkey].join(':') - let result = routes || _getPubkeyRelaysCache.get(key) + let result = routesOverride || _getPubkeyRelaysCache.get(key) if (!result) { - result = database.routes.all(filter) + result = routes.all(filter) _getPubkeyRelaysCache.set(key, result) } @@ -75,7 +72,7 @@ export const getPubkeyWriteRelays = pubkey => getPubkeyRelays(pubkey, 'write') export const getAllPubkeyRelays = (pubkeys, mode = null) => { // As an optimization, filter the database once and group by pubkey const filter = mode ? {pubkey: pubkeys, mode} : {pubkey: pubkeys} - const routesByPubkey = groupBy(prop('pubkey'), database.routes.all(filter)) + const routesByPubkey = groupBy(prop('pubkey'), routes.all(filter)) return aggregateScores( pubkeys.map( diff --git a/src/agent/social.ts b/src/agent/social.ts index ddf408d8..11f14bd1 100644 --- a/src/agent/social.ts +++ b/src/agent/social.ts @@ -1,10 +1,10 @@ import {uniq, without} from 'ramda' import {Tags} from 'src/util/nostr' -import database from 'src/agent/database' +import {getPersonWithFallback} from 'src/agent/state' import user from 'src/agent/user' export const getFollows = pubkey => - Tags.wrap(database.getPersonWithFallback(pubkey).petnames).type("p").values().all() + Tags.wrap(getPersonWithFallback(pubkey).petnames).type("p").values().all() export const getNetwork = pubkey => { const follows = getFollows(pubkey) diff --git a/src/agent/state.ts b/src/agent/state.ts new file mode 100644 index 00000000..66f641cd --- /dev/null +++ b/src/agent/state.ts @@ -0,0 +1,25 @@ +import {pluck, all, identity} from "ramda" +import {derived} from "svelte/store" +import {Table, registry} from "src/agent/table" + +export const people = new Table("people", "pubkey") +export const contacts = new Table("contacts", "pubkey") +export const rooms = new Table("rooms", "id") +export const alerts = new Table("alerts", "id") +export const relays = new Table("relays", "url") +export const routes = new Table("routes", "id") + +export const getPersonWithFallback = pubkey => people.get(pubkey) || {pubkey} + +export const ready = derived(pluck("ready", Object.values(registry)), all(identity)) + +export const onReady = cb => { + const unsub = ready.subscribe($ready => { + if ($ready) { + cb() + setTimeout(() => unsub()) + } + }) +} + +window.state = {people, contacts, rooms, alerts, relays, routes} diff --git a/src/agent/sync.ts b/src/agent/sync.ts index d57ae790..e7daef7f 100644 --- a/src/agent/sync.ts +++ b/src/agent/sync.ts @@ -1,10 +1,10 @@ import {uniq, pick, identity, isEmpty} from 'ramda' import {nip05} from 'nostr-tools' -import {noop, createMap, ensurePlural, chunk, switcherFn} from 'hurdak/lib/hurdak' +import {noop, ensurePlural, chunk, switcherFn} from 'hurdak/lib/hurdak' import {log} from 'src/util/logger' import {lnurlEncode, tryFunc, lnurlDecode, tryFetch, now, sleep, tryJson, timedelta, shuffle, hash} from 'src/util/misc' import {Tags, roomAttrs, personKinds, isRelay, isShareableRelay, normalizeRelayUrl} from 'src/util/nostr' -import database from 'src/agent/database' +import {getPersonWithFallback, people, relays, rooms, routes} from 'src/agent/state' const processEvents = async events => { await Promise.all([ @@ -30,13 +30,12 @@ const batchProcess = async (processChunk, events) => { const processProfileEvents = async events => { const profileEvents = events.filter(e => personKinds.includes(e.kind)) - const updates = {} for (const e of profileEvents) { - const person = database.getPersonWithFallback(e.pubkey) + const person = getPersonWithFallback(e.pubkey) - updates[e.pubkey] = { + people.put({ ...person, - ...updates[e.pubkey], + pubkey: e.pubkey, ...switcherFn(e.kind, { 0: () => tryJson(() => { const kind0 = JSON.parse(e.content) @@ -53,18 +52,14 @@ const processProfileEvents = async events => { } return { - kind0: { - ...person?.kind0, - ...updates[e.pubkey]?.kind0, - ...kind0, - }, + kind0: {...person?.kind0, ...kind0}, kind0_updated_at: e.created_at, } } }), 2: () => { if (e.created_at > (person.relays_updated_at || 0)) { - const {relays = []} = database.getPersonWithFallback(e.pubkey) + const {relays = []} = getPersonWithFallback(e.pubkey) return { relays_updated_at: e.created_at, @@ -141,20 +136,15 @@ const processProfileEvents = async events => { }, }), updated_at: now(), - } - } - - if (!isEmpty(updates)) { - await database.people.bulkPatch(updates) + }) } } // Chat rooms -const processRoomEvents = async events => { +const processRoomEvents = events => { const roomEvents = events.filter(e => [40, 41].includes(e.kind)) - const updates = {} for (const e of roomEvents) { const content = tryJson(() => pick(roomAttrs, JSON.parse(e.content))) const roomId = e.kind === 40 ? e.id : Tags.from(e).type("e").values().first() @@ -164,25 +154,20 @@ const processRoomEvents = async events => { continue } - const room = database.rooms.get(roomId) + const room = rooms.get(roomId) // Don't let old edits override new ones if (room?.updated_at >= e.created_at) { continue } - updates[roomId] = { + rooms.put({ ...room, - ...updates, ...content, id: roomId, pubkey: e.pubkey, updated_at: e.created_at, - } - } - - if (!isEmpty(updates)) { - await database.rooms.bulkPatch(updates) + }) } } @@ -205,7 +190,7 @@ const calculateRoute = (pubkey, rawUrl, type, mode, created_at) => { const id = hash([pubkey, url, mode].join('')).toString() const score = getWeight(type) * (1 - (now() - created_at) / timedelta(30, 'days')) const defaults = {id, pubkey, url, mode, score: 0, count: 0, types: []} - const route = database.routes.get(id) || defaults + const route = routes.get(id) || defaults const newTotalScore = route.score * route.count + score const newCount = route.count + 1 @@ -293,8 +278,8 @@ const processRoutes = async events => { updates = updates.filter(identity) if (!isEmpty(updates)) { - await database.relays.bulkPatch(createMap('url', updates.map(pick(['url'])))) - await database.routes.bulkPut(createMap('id', updates)) + await relays.bulkPatch(updates.map(pick(['url']))) + await routes.bulkPut(updates) } } @@ -303,22 +288,20 @@ const processRoutes = async events => { const verifyNip05 = (pubkey, as) => nip05.queryProfile(as).then(result => { if (result?.pubkey === pubkey) { - const person = database.getPersonWithFallback(pubkey) + const person = getPersonWithFallback(pubkey) - database.people.patch({...person, verified_as: as}) + people.patch({...person, verified_as: as}) if (result.relays?.length > 0) { const urls = result.relays.filter(isRelay) - database.relays.bulkPatch( - createMap('url', urls.map(url => ({url: normalizeRelayUrl(url)}))) - ) + relays.bulkPatch(urls.map(url => ({url: normalizeRelayUrl(url)}))) - database.routes.bulkPut( - createMap('id', urls.flatMap(url => [ + routes.bulkPut( + urls.flatMap(url => [ calculateRoute(pubkey, url, 'nip05', 'write', now()), calculateRoute(pubkey, url, 'nip05', 'read', now()), - ]).filter(identity)) + ]).filter(identity) ) } } @@ -347,7 +330,7 @@ const verifyZapper = async (pubkey, address) => { const lnurl = lnurlEncode('lnurl', url) if (zapper?.allowsNostr && zapper?.nostrPubkey) { - database.people.patch({pubkey, zapper, lnurl}) + people.patch({pubkey, zapper, lnurl}) } } diff --git a/src/agent/table.ts b/src/agent/table.ts new file mode 100644 index 00000000..ed586982 --- /dev/null +++ b/src/agent/table.ts @@ -0,0 +1,208 @@ +import type {Writable} from 'svelte/store' +import LRUCache from 'lru-cache' +import {throttle} from 'throttle-debounce' +import {objOf, is, without} from 'ramda' +import {writable} from 'svelte/store' +import {isObject, mapValues, ensurePlural} from 'hurdak/lib/hurdak' +import {log} from 'src/util/logger' +import {where} from 'src/util/misc' +import {lf} from 'src/agent/database' + +// Local copy of data so we can provide a sync observable interface. The worker +// is just for storing data and processing expensive queries + +type CacheEntry = [string, {value: any}] + +type TableOpts = { + maxEntries?: number + initialize?: (table: Table) => Promise> +} + +export const registry = {} as Record + +export class Table { + name: string + pk: string + opts: TableOpts + cache: LRUCache + listeners: Array<(Table) => void> + ready: Writable + constructor(name, pk, opts: TableOpts = {}) { + this.name = name + this.pk = pk + this.opts = {maxEntries: 1000, initialize: t => this.dump(), ...opts} + this.cache = new LRUCache({max: this.opts.maxEntries}) + this.listeners = [] + this.ready = writable(false) + + registry[name] = this + + // Sync from storage initially + ;(async () => { + const t = Date.now() + + this.cache.load(await this.opts.initialize(this) || []) + this._notify() + + log(`Table ${name} ready in ${Date.now() - t}ms (${this.cache.size} records)`) + + this.ready.set(true) + })() + } + _persist = throttle(4_000, () => { + lf('setItem', this.name, this.cache.dump()) + }) + _notify() { + // Notify subscribers + for (const cb of this.listeners) { + cb(this) + } + + // Save to localstorage + this._persist() + } + subscribe(cb) { + cb = throttle(100, cb) + + this.listeners.push(cb) + + cb(this) + + return () => { + this.listeners = without([cb], this.listeners) + } + } + bulkPut(items) { + for (const item of items) { + const k = item[this.pk] + + if (!k) { + throw new Error(`Missing primary key on ${this.name}`) + } + + this.cache.set(k, item) + } + + this._persist() + } + put(item) { + this.bulkPut([item]) + } + bulkPatch(items) { + for (const item of items) { + const k = item[this.pk] + + if (!k) { + throw new Error(`Missing primary key on ${this.name}`) + } + + this.cache.set(k, {...this.cache.get(k), ...item}) + } + + this._persist() + } + patch(item) { + this.bulkPatch([item]) + } + bulkRemove(ks) { + for (const k of ks) { + this.cache.delete(k) + } + + this._persist() + } + remove(k) { + this.bulkRemove([k]) + } + async drop() { + this.cache.clear() + + return lf('removeItem', this.name) + } + async dump() { + let data = await lf('getItem', this.name) || [] + + // Backwards compat - we used to store objects rather than cache dump arrays + if (isObject(data)) { + data = Object.entries(mapValues(objOf('value'), data)) + } + + return data as Array + } + toArray() { + const result = [] + for (const item of this.cache.values()) { + result.push(item) + } + + return result + } + all(spec = {}) { + return this.toArray().filter(where(spec)) + } + find(spec = {}) { + return this.cache.find(where(spec)) + } + get(k) { + return this.cache.get(k) + } +} + +// Helper to allow us to listen to changes of any given table + +const listener = (() => { + let listeners = [] + + for (const table of Object.values(registry) as Array
) { + table.subscribe(() => listeners.forEach(f => f(table.name))) + } + + return { + subscribe: f => { + listeners.push(f) + + return () => { + listeners = without([f], listeners) + } + }, + } +})() + +// Helper to re-run a query every time a given table changes + +export const watch = (names, f) => { + names = ensurePlural(names) + + const store = writable(null) + const tables = names.map(name => registry[name]) + + // Initialize synchronously if possible + const initialValue = f(...tables) + if (is(Promise, initialValue)) { + initialValue.then(v => store.set(v)) + } else { + store.set(initialValue) + } + + // Debounce refresh so we don't get UI lag + const refresh = throttle(300, async () => store.set(await f(...tables))) + + // Listen for changes + listener.subscribe(name => { + if (names.includes(name)) { + refresh() + } + }) + + return store +} + +// Methods that work on all tables + +export const dropAll = async () => { + for (const table of Object.values(registry)) { + await table.drop() + + log(`Successfully dropped table ${table.name}`) + } +} diff --git a/src/agent/user.ts b/src/agent/user.ts index 61d62ed6..71f44497 100644 --- a/src/agent/user.ts +++ b/src/agent/user.ts @@ -4,7 +4,7 @@ import {slice, identity, prop, find, pipe, assoc, whereEq, when, concat, reject, import {findReplyId, findRootId} from 'src/util/nostr' import {synced} from 'src/util/misc' import {derived} from 'svelte/store' -import database from 'src/agent/database' +import {people} from 'src/agent/state' import keys from 'src/agent/keys' import cmd from 'src/agent/cmd' @@ -34,14 +34,8 @@ const settings = synced("agent/user/settings", { }) const profile = derived( - [keys.pubkey, database.people as Readable], - ([pubkey, $people]) => { - if (!pubkey) { - return null - } - - return $people[pubkey] || {pubkey} - } + [keys.pubkey, people as Readable], + ([pubkey, t]) => pubkey ? (t.get(pubkey) || {pubkey}) : null ) as Readable const profileKeyWithDefault = (key, stores) => derived( diff --git a/src/app/alerts.ts b/src/app/alerts.ts index 2ef7dfd7..b4465e7d 100644 --- a/src/app/alerts.ts +++ b/src/app/alerts.ts @@ -5,7 +5,8 @@ import {createMap} from 'hurdak/lib/hurdak' import {synced, tryJson, now, timedelta} from 'src/util/misc' import {Tags, personKinds, isAlert, asDisplayEvent, findReplyId} from 'src/util/nostr' import {getUserReadRelays} from 'src/agent/relays' -import database from 'src/agent/database' +import {alerts, contacts, rooms} from 'src/agent/state' +import {watch} from 'src/agent/table' import network from 'src/agent/network' let listener @@ -24,18 +25,18 @@ const seenAlertIds = synced('app/alerts/seenAlertIds', []) export const lastChecked = synced('app/alerts/lastChecked', {}) export const newAlerts = derived( - [database.watch('alerts', t => pluck('created_at', t.all()).reduce(max, 0)), lastChecked], + [watch('alerts', t => pluck('created_at', t.all()).reduce(max, 0)), lastChecked], ([$lastAlert, $lastChecked]) => $lastAlert > ($lastChecked.alerts || 0) ) export const newDirectMessages = derived( - [database.watch('contacts', t => t.all()), lastChecked], + [watch('contacts', t => t.all()), lastChecked], ([contacts, $lastChecked]) => Boolean(find(c => c.lastMessage > $lastChecked[c.pubkey], contacts)) ) export const newChatMessages = derived( - [database.watch('rooms', t => t.all()), lastChecked], + [watch('rooms', t => t.all()), lastChecked], ([rooms, $lastChecked]) => Boolean(find(c => c.lastMessage > $lastChecked[c.pubkey], rooms)) ) @@ -75,33 +76,33 @@ const processAlerts = async (pubkey, events) => { zaps.filter(isPubkeyChild).forEach(e => { const parent = parents[findReplyId(e)] - const note = asAlert(database.alerts.get(parent.id) || parent) + const note = asAlert(alerts.get(parent.id) || parent) const meta = Tags.from(e).asMeta() const request = tryJson(() => JSON.parse(meta.description)) if (request) { - database.alerts.put({...note, zappedBy: uniq(note.zappedBy.concat(request.pubkey))}) + alerts.put({...note, zappedBy: uniq(note.zappedBy.concat(request.pubkey))}) } }) likes.filter(isPubkeyChild).forEach(e => { const parent = parents[findReplyId(e)] - const note = asAlert(database.alerts.get(parent.id) || parent) + const note = asAlert(alerts.get(parent.id) || parent) - database.alerts.put({...note, likedBy: uniq(note.likedBy.concat(e.pubkey))}) + alerts.put({...note, likedBy: uniq(note.likedBy.concat(e.pubkey))}) }) replies.forEach(e => { const parent = parents[findReplyId(e)] - const note = asAlert(database.alerts.get(parent.id) || parent) + const note = asAlert(alerts.get(parent.id) || parent) - database.alerts.put({...note, repliesFrom: uniq(note.repliesFrom.concat(e.pubkey))}) + alerts.put({...note, repliesFrom: uniq(note.repliesFrom.concat(e.pubkey))}) }) mentions.forEach(e => { - const note = database.alerts.get(e.id) || asAlert(e) + const note = alerts.get(e.id) || asAlert(e) - database.alerts.put({...note, isMention: true}) + alerts.put({...note, isMention: true}) }) } @@ -118,12 +119,12 @@ const processMessages = async (pubkey, events) => { const recipient = Tags.from(message).type("p").values().first() $lastChecked[recipient] = Math.max($lastChecked[recipient] || 0, message.created_at) - database.contacts.patch({pubkey: recipient, accepted: true}) + contacts.patch({pubkey: recipient, accepted: true}) } else { - const contact = database.contacts.get(message.pubkey) + const contact = contacts.get(message.pubkey) const lastMessage = Math.max(contact?.lastMessage || 0, message.created_at) - database.contacts.patch({pubkey: message.pubkey, lastMessage}) + contacts.patch({pubkey: message.pubkey, lastMessage}) } } @@ -145,10 +146,10 @@ const processChats = async (pubkey, events) => { if (message.pubkey === pubkey) { $lastChecked[id] = Math.max($lastChecked[id] || 0, message.created_at) } else { - const room = database.rooms.get(id) + const room = rooms.get(id) const lastMessage = Math.max(room?.lastMessage || 0, message.created_at) - database.rooms.patch({id, lastMessage}) + rooms.patch({id, lastMessage}) } } @@ -159,7 +160,7 @@ const processChats = async (pubkey, events) => { const listen = async pubkey => { // Include an offset so we don't miss alerts on one relay but not another const since = now() - timedelta(7, 'days') - const roomIds = pluck('id', database.rooms.all({joined: true})) + const roomIds = pluck('id', rooms.all({joined: true})) if (listener) { listener.unsub() diff --git a/src/app/index.ts b/src/app/index.ts index 5de713ff..7aa4691e 100644 --- a/src/app/index.ts +++ b/src/app/index.ts @@ -5,7 +5,7 @@ import {renderContent} from 'src/util/html' import {displayPerson, findReplyId} from 'src/util/nostr' import {getUserFollows} from 'src/agent/social' import {getUserReadRelays} from 'src/agent/relays' -import database from 'src/agent/database' +import {getPersonWithFallback} from 'src/agent/state' import network from 'src/agent/network' import keys from 'src/agent/keys' import alerts from 'src/app/alerts' @@ -46,7 +46,7 @@ export const renderNote = (note, {showEntire = false}) => { } const pubkey = note.tags[parseInt(i)][1] - const person = database.getPersonWithFallback(pubkey) + const person = getPersonWithFallback(pubkey) const name = displayPerson(person) const path = routes.person(pubkey) diff --git a/src/partials/Channel.svelte b/src/partials/Channel.svelte index 368648df..93f4e226 100644 --- a/src/partials/Channel.svelte +++ b/src/partials/Channel.svelte @@ -6,7 +6,7 @@ import {sleep, createScroller, Cursor} from "src/util/misc" import Spinner from "src/partials/Spinner.svelte" import user from "src/agent/user" - import database from "src/agent/database" + import {getPersonWithFallback} from "src/agent/state" import network from "src/agent/network" export let loadMessages @@ -26,7 +26,7 @@ // Group messages so we're only showing the person once per chunk annotatedMessages = reverse( sortBy(prop("created_at"), uniqBy(prop("id"), messages)).reduce((mx, m) => { - const person = database.getPersonWithFallback(m.pubkey) + const person = getPersonWithFallback(m.pubkey) const showPerson = person.pubkey !== getPath(["person", "pubkey"], last(mx)) return mx.concat({...m, person, showPerson}) diff --git a/src/partials/Compose.svelte b/src/partials/Compose.svelte index 011bde70..d6c7a402 100644 --- a/src/partials/Compose.svelte +++ b/src/partials/Compose.svelte @@ -7,7 +7,7 @@ import {displayPerson} from "src/util/nostr" import {fromParentOffset} from "src/util/html" import Badge from "src/partials/Badge.svelte" - import database from "src/agent/database" + import {people} from "src/agent/state" export let onSubmit @@ -17,7 +17,7 @@ let input = null let prevContent = "" - const search = fuzzy(database.people.all({"kind0.name:!nil": null}), { + const search = fuzzy(people.all({"kind0.name:!nil": null}), { keys: ["kind0.name", "pubkey"], }) diff --git a/src/routes/Alerts.svelte b/src/routes/Alerts.svelte index 0f34654a..51033107 100644 --- a/src/routes/Alerts.svelte +++ b/src/routes/Alerts.svelte @@ -7,7 +7,7 @@ import Content from "src/partials/Content.svelte" import Alert from "src/views/alerts/Alert.svelte" import Mention from "src/views/alerts/Mention.svelte" - import database from "src/agent/database" + import {alerts} from "src/agent/state" import user from "src/agent/user" import {lastChecked} from "src/app/alerts" @@ -25,7 +25,7 @@ // Filter out mutes, and alerts for which we failed to find the required context. The bug // is really upstream of this, but it's an easy fix const events = user - .applyMutes(database.alerts.all()) + .applyMutes(alerts.all()) .filter(e => any(k => e[k]?.length > 0, ["replies", "likedBy", "zappedBy"]) || e.isMention) notes = sortBy(e => -e.created_at, events).slice(0, limit) diff --git a/src/routes/ChatDetail.svelte b/src/routes/ChatDetail.svelte index c4ec1ed7..a4b8ea42 100644 --- a/src/routes/ChatDetail.svelte +++ b/src/routes/ChatDetail.svelte @@ -9,7 +9,7 @@ import user from "src/agent/user" import {getRelaysForEventChildren, sampleRelays} from "src/agent/relays" import network from "src/agent/network" - import database from "src/agent/database" + import {watch} from "src/agent/table" import cmd from "src/agent/cmd" import {modal} from "src/app/ui" import {lastChecked} from "src/app/alerts" @@ -18,7 +18,7 @@ export let entity const id = toHex(entity) - const room = database.watch("rooms", t => t.get(id) || {id}) + const room = watch("rooms", t => t.get(id) || {id}) const getRelays = () => sampleRelays($room ? getRelaysForEventChildren($room) : []) const listenForMessages = onChunk => diff --git a/src/routes/ChatList.svelte b/src/routes/ChatList.svelte index aef1b0f2..c0c62567 100644 --- a/src/routes/ChatList.svelte +++ b/src/routes/ChatList.svelte @@ -5,7 +5,7 @@ import Content from "src/partials/Content.svelte" import Anchor from "src/partials/Anchor.svelte" import ChatListItem from "src/views/chat/ChatListItem.svelte" - import database from "src/agent/database" + import {watch} from "src/agent/table" import network from "src/agent/network" import {getUserReadRelays} from "src/agent/relays" import {modal} from "src/app/ui" @@ -14,8 +14,8 @@ let search let results = [] - const userRooms = database.watch("rooms", t => t.all({joined: true})) - const otherRooms = database.watch("rooms", t => t.all({"joined:!eq": true})) + const userRooms = watch("rooms", t => t.all({joined: true})) + const otherRooms = watch("rooms", t => t.all({"joined:!eq": true})) $: search = fuzzy($otherRooms, {keys: ["name", "about"]}) $: results = search(q).slice(0, 50) diff --git a/src/routes/Logout.svelte b/src/routes/Logout.svelte index 25b4c148..1740dc75 100644 --- a/src/routes/Logout.svelte +++ b/src/routes/Logout.svelte @@ -2,14 +2,14 @@ import {fly} from "svelte/transition" import Anchor from "src/partials/Anchor.svelte" import Content from "src/partials/Content.svelte" - import database from "src/agent/database" + import {dropAll} from "src/agent/table" let confirmed = false const confirm = async () => { confirmed = true - await database.dropAll() + await dropAll() localStorage.clear() diff --git a/src/routes/MessagesDetail.svelte b/src/routes/MessagesDetail.svelte index 4fb682bb..a8ddcaf4 100644 --- a/src/routes/MessagesDetail.svelte +++ b/src/routes/MessagesDetail.svelte @@ -8,7 +8,8 @@ import Channel from "src/partials/Channel.svelte" import Anchor from "src/partials/Anchor.svelte" import {getAllPubkeyRelays, sampleRelays} from "src/agent/relays" - import database from "src/agent/database" + import {getPersonWithFallback} from "src/agent/state" + import {watch} from "src/agent/table" import network from "src/agent/network" import keys from "src/agent/keys" import user from "src/agent/user" @@ -16,13 +17,13 @@ import {routes} from "src/app/ui" import {lastChecked} from "src/app/alerts" import {renderNote} from "src/app" - import PersonCircle from "src/partials/PersonCircle.svelte"; + import PersonCircle from "src/partials/PersonCircle.svelte" export let entity let crypt = keys.getCrypt() let pubkey = toHex(entity) - let person = database.watch("people", () => database.getPersonWithFallback(pubkey)) + let person = watch("people", () => getPersonWithFallback(pubkey)) lastChecked.update(assoc(pubkey, now())) diff --git a/src/routes/MessagesList.svelte b/src/routes/MessagesList.svelte index 591c4acb..63697286 100644 --- a/src/routes/MessagesList.svelte +++ b/src/routes/MessagesList.svelte @@ -4,7 +4,7 @@ import Tabs from "src/partials/Tabs.svelte" import Content from "src/partials/Content.svelte" import MessagesListItem from "src/views/messages/MessagesListItem.svelte" - import database from "src/agent/database" + import {watch} from "src/agent/table" let activeTab = "messages" @@ -12,8 +12,8 @@ activeTab = tab } - const accepted = database.watch("contacts", t => t.all({accepted: true})) - const requests = database.watch("contacts", t => t.all({"accepted:!eq": true})) + const accepted = watch("contacts", t => t.all({accepted: true})) + const requests = watch("contacts", t => t.all({"accepted:!eq": true})) const getContacts = tab => sortBy(c => -c.lastMessage, tab === "messages" ? $accepted : $requests) diff --git a/src/routes/PersonDetail.svelte b/src/routes/PersonDetail.svelte index 928c7106..9153c0d0 100644 --- a/src/routes/PersonDetail.svelte +++ b/src/routes/PersonDetail.svelte @@ -18,9 +18,9 @@ import user from "src/agent/user" import {sampleRelays, getPubkeyWriteRelays} from "src/agent/relays" import network from "src/agent/network" - import database from "src/agent/database" + import {getPersonWithFallback, people} from "src/agent/state" import {routes, modal} from "src/app/ui" - import PersonCircle from "src/partials/PersonCircle.svelte"; + import PersonCircle from "src/partials/PersonCircle.svelte" export let npub export let activeTab @@ -35,7 +35,7 @@ let muted = false let followers = new Set() let followersCount = tweened(0, {interpolate, duration: 1000}) - let person = database.getPersonWithFallback(pubkey) + let person = getPersonWithFallback(pubkey) let loading = true let showActions = false let actions = [] @@ -88,12 +88,12 @@ // Refresh our person network.loadPeople([pubkey], {force: true}).then(() => { - person = database.getPersonWithFallback(pubkey) + person = getPersonWithFallback(pubkey) loading = false }) // Prime our followers count - database.people.all().forEach(p => { + people.all().forEach(p => { if (Tags.wrap(p.petnames).type("p").values().all().includes(pubkey)) { followers.add(p.pubkey) followersCount.set(followers.size) @@ -173,7 +173,7 @@
- +
diff --git a/src/routes/RelayDetail.svelte b/src/routes/RelayDetail.svelte index 255bff40..13bdee72 100644 --- a/src/routes/RelayDetail.svelte +++ b/src/routes/RelayDetail.svelte @@ -7,22 +7,22 @@ import Content from "src/partials/Content.svelte" import Anchor from "src/partials/Anchor.svelte" import Feed from "src/views/feed/Feed.svelte" - import database from "src/agent/database" + import {relays} from "src/agent/state" import pool from "src/agent/pool" import user from "src/agent/user" export let url - const relay = database.relays.get(url) || {url} + const relay = relays.get(url) || {url} let quality = null let message = null let showStatus = false let joined = false - const {relays} = user + const {relays: userRelays} = user - $: joined = find(propEq("url", relay.url), $relays) + $: joined = find(propEq("url", relay.url), $userRelays) onMount(() => { return poll(10_000, async () => { @@ -73,7 +73,7 @@ {/if} {#if joined} - {#if $relays.length > 1} + {#if $userRelays.length > 1} Your Follows {/if} {#each $petnamePubkeys as pubkey (pubkey)} - + {:else}
diff --git a/src/views/alerts/Alert.svelte b/src/views/alerts/Alert.svelte index d62cc422..26d97d57 100644 --- a/src/views/alerts/Alert.svelte +++ b/src/views/alerts/Alert.svelte @@ -3,7 +3,7 @@ import Badge from "src/partials/Badge.svelte" import Popover from "src/partials/Popover.svelte" import {formatTimestamp} from "src/util/misc" - import database from "src/agent/database" + import {getPersonWithFallback} from "src/agent/state" import {modal} from "src/app/ui" export let note @@ -35,7 +35,7 @@
{#each pubkeys as pubkey} - + {/each}
diff --git a/src/views/alerts/Mention.svelte b/src/views/alerts/Mention.svelte index 5c2f775b..9a4f71c6 100644 --- a/src/views/alerts/Mention.svelte +++ b/src/views/alerts/Mention.svelte @@ -4,13 +4,13 @@ import {displayPerson} from "src/util/nostr" import Popover from "src/partials/Popover.svelte" import PersonSummary from "src/views/person/PersonSummary.svelte" - import database from "src/agent/database" + import {getPersonWithFallback} from "src/agent/state" import {modal} from "src/app/ui" - import PersonCircle from "src/partials/PersonCircle.svelte"; + import PersonCircle from "src/partials/PersonCircle.svelte" export let note - const person = database.getPersonWithFallback(note.pubkey) + const person = getPersonWithFallback(note.pubkey)
{:else}
No mentions
diff --git a/src/views/notes/NoteCreate.svelte b/src/views/notes/NoteCreate.svelte index 4fa47180..d9d144af 100644 --- a/src/views/notes/NoteCreate.svelte +++ b/src/views/notes/NoteCreate.svelte @@ -16,7 +16,8 @@ import Modal from "src/partials/Modal.svelte" import Heading from "src/partials/Heading.svelte" import {getUserWriteRelays} from "src/agent/relays" - import database from "src/agent/database" + import {getPersonWithFallback} from "src/agent/state" + import {watch} from "src/agent/table" import cmd from "src/agent/cmd" import {toast, modal} from "src/app/ui" import {publishWithToast} from "src/app" @@ -30,7 +31,7 @@ let q = "" let search - const knownRelays = database.watch("relays", t => t.all()) + const knownRelays = watch("relays", t => t.all()) $: { const joined = new Set(pluck("url", relays)) @@ -91,7 +92,7 @@ onMount(() => { if (pubkey) { - const person = database.getPersonWithFallback(pubkey) + const person = getPersonWithFallback(pubkey) input.type("@" + displayPerson(person)) input.trigger({key: "Enter"}) diff --git a/src/views/onboarding/Onboarding.svelte b/src/views/onboarding/Onboarding.svelte index a2058216..3a801d02 100644 --- a/src/views/onboarding/Onboarding.svelte +++ b/src/views/onboarding/Onboarding.svelte @@ -13,7 +13,7 @@ import OnboardingComplete from "src/views/onboarding/OnboardingComplete.svelte" import {getFollows} from "src/agent/social" import {getPubkeyWriteRelays, sampleRelays} from "src/agent/relays" - import database from "src/agent/database" + import {getPersonWithFallback} from "src/agent/state" import network from "src/agent/network" import user from "src/agent/user" import keys from "src/agent/keys" @@ -44,7 +44,7 @@ await user.updatePetnames(() => follows.map(pubkey => { const [{url}] = sampleRelays(getPubkeyWriteRelays(pubkey)) - const name = displayPerson(database.getPersonWithFallback(pubkey)) + const name = displayPerson(getPersonWithFallback(pubkey)) return ["p", pubkey, url, name] }) diff --git a/src/views/onboarding/OnboardingFollows.svelte b/src/views/onboarding/OnboardingFollows.svelte index 6e3bffb6..df9e8d59 100644 --- a/src/views/onboarding/OnboardingFollows.svelte +++ b/src/views/onboarding/OnboardingFollows.svelte @@ -6,7 +6,8 @@ import Heading from "src/partials/Heading.svelte" import Content from "src/partials/Content.svelte" import PersonInfo from "src/partials/PersonInfo.svelte" - import database from "src/agent/database" + import {getPersonWithFallback} from "src/agent/state" + import {watch} from "src/agent/table" import {modal} from "src/app/ui" export let follows @@ -14,7 +15,7 @@ let q = "" let search - const knownPeople = database.watch("people", t => t.all({"kind0.name:!nil": null})) + const knownPeople = watch("people", t => t.all({"kind0.name:!nil": null})) $: search = fuzzy( $knownPeople.filter(p => !follows.includes(p.pubkey)), @@ -54,7 +55,7 @@
{:else} {#each follows as pubkey} - + {/each} {/if}
diff --git a/src/views/onboarding/OnboardingRelays.svelte b/src/views/onboarding/OnboardingRelays.svelte index a755c267..f0d34eb0 100644 --- a/src/views/onboarding/OnboardingRelays.svelte +++ b/src/views/onboarding/OnboardingRelays.svelte @@ -6,7 +6,7 @@ import Heading from "src/partials/Heading.svelte" import Content from "src/partials/Content.svelte" import RelayCard from "src/partials/RelayCard.svelte" - import database from "src/agent/database" + import {watch} from "src/agent/table" import {modal} from "src/app/ui" export let relays @@ -14,7 +14,7 @@ let q = "" let search - const knownRelays = database.watch("relays", t => t.all()) + const knownRelays = watch("relays", t => t.all()) $: { const joined = new Set(pluck("url", relays)) diff --git a/src/views/person/PersonList.svelte b/src/views/person/PersonList.svelte index 4e091777..fcbfe396 100644 --- a/src/views/person/PersonList.svelte +++ b/src/views/person/PersonList.svelte @@ -1,12 +1,13 @@ diff --git a/src/views/person/PersonSearch.svelte b/src/views/person/PersonSearch.svelte index 5d693b35..37641298 100644 --- a/src/views/person/PersonSearch.svelte +++ b/src/views/person/PersonSearch.svelte @@ -5,7 +5,7 @@ import Spinner from "src/partials/Spinner.svelte" import PersonInfo from "src/views/person/PersonInfo.svelte" import {getUserReadRelays} from "src/agent/relays" - import database from "src/agent/database" + import {watch} from "src/agent/table" import network from "src/agent/network" import user from "src/agent/user" @@ -15,7 +15,7 @@ let results = [] const {petnamePubkeys} = user - const search = database.watch("people", t => + const search = watch("people", t => fuzzy(t.all({"kind0.name:!nil": null}), { keys: ["kind0.name", "kind0.about", "pubkey"], }) diff --git a/src/views/person/PersonSummary.svelte b/src/views/person/PersonSummary.svelte index 7f7d4bfc..b5eec075 100644 --- a/src/views/person/PersonSummary.svelte +++ b/src/views/person/PersonSummary.svelte @@ -6,15 +6,16 @@ import Anchor from "src/partials/Anchor.svelte" import user from "src/agent/user" import {sampleRelays, getPubkeyWriteRelays} from "src/agent/relays" - import database from "src/agent/database" + import {getPersonWithFallback} from "src/agent/state" + import {watch} from "src/agent/table" import {routes} from "src/app/ui" - import PersonCircle from "src/partials/PersonCircle.svelte"; + import PersonCircle from "src/partials/PersonCircle.svelte" export let pubkey const {petnamePubkeys, canPublish} = user const getRelays = () => sampleRelays(getPubkeyWriteRelays(pubkey)) - const person = database.watch("people", () => database.getPersonWithFallback(pubkey)) + const person = watch("people", () => getPersonWithFallback(pubkey)) let following = false diff --git a/src/views/relays/RelaySearch.svelte b/src/views/relays/RelaySearch.svelte index 81e126a2..487183fa 100644 --- a/src/views/relays/RelaySearch.svelte +++ b/src/views/relays/RelaySearch.svelte @@ -3,12 +3,12 @@ import {fuzzy} from "src/util/misc" import Input from "src/partials/Input.svelte" import RelayCard from "src/views/relays/RelayCard.svelte" - import database from "src/agent/database" + import {watch} from "src/agent/table" import user from "src/agent/user" let q = "" let search - let knownRelays = database.watch("relays", t => t.all()) + let knownRelays = watch("relays", t => t.all()) const {relays} = user