Batch load context on feeds. This makes loading faster, and provides more context

This commit is contained in:
Jonathan Staab 2022-12-30 13:29:06 -08:00
parent 3d095e83ef
commit 3097b9e7e8
10 changed files with 74 additions and 68 deletions

View File

@ -22,20 +22,26 @@ If you like Coracle and want to support its development, you can donate sats via
- [ ] An actual readme - [ ] An actual readme
- [ ] Server discovery and relay publishing - https://github.com/nostr-protocol/nips/pull/32/files - [ ] Server discovery and relay publishing - https://github.com/nostr-protocol/nips/pull/32/files
- [ ] Support invoices https://twitter.com/jb55/status/1604131336247476224 - [ ] Support invoices https://twitter.com/jb55/status/1604131336247476224
- [ ] Expand/collapse large threads
- [ ] NIP 05 - [ ] NIP 05
- [ ] Lightning tips
- [ ] Direct messages
- [ ] Rooms/groups
# Bugs # Bugs
- [ ] Follow fiatjaf's vision of clients being smart and connecting to recommended relays to fetch content
- [ ] Add alerts for replies to posts the user liked - [ ] Add alerts for replies to posts the user liked
- [ ] Stack views so scroll position isn't lost on navigation - [ ] Stack views so scroll position isn't lost on navigation
- [ ] Add notification for slow relays - [ ] Add notification for slow relays, suggest relays based on network
- [ ] Separating events table into notes/reactions/etc would effectively give us a second index on kind. - [ ] Separating events table into notes/reactions/etc would effectively give us a second index on kind.
- [ ] Clicking on a badge in the popover falls through, and might also crash - [ ] Clicking on a badge in the popover falls through, and might also crash
- [ ] Add a slider in settings so users can decide whether to go with fast relays, or wait for everyone to complete their queries. Most relevant for NoteDetail
# Changelog # Changelog
## 0.2.5
- [x] Batch load context for feeds
## 0.2.4 ## 0.2.4
- [x] Fix reactions - livequery is required in order to listen for changes - [x] Fix reactions - livequery is required in order to listen for changes

View File

@ -1,7 +1,7 @@
import {liveQuery} from 'dexie' import {liveQuery} from 'dexie'
import extractUrls from 'extract-urls' import extractUrls from 'extract-urls'
import {get} from 'svelte/store' import {get} from 'svelte/store'
import {intersection, find, sortBy, propEq, uniqBy, groupBy, concat, without, prop, isNil, identity} from 'ramda' import {uniq, pluck, intersection, sortBy, propEq, uniqBy, groupBy, concat, without, prop, isNil, identity} from 'ramda'
import {ensurePlural, first, createMap, ellipsize} from 'hurdak/lib/hurdak' import {ensurePlural, first, createMap, ellipsize} from 'hurdak/lib/hurdak'
import {escapeHtml} from 'src/util/html' import {escapeHtml} from 'src/util/html'
import {filterTags, getTagValues, findReply, findRoot} from 'src/util/nostr' import {filterTags, getTagValues, findReply, findRoot} from 'src/util/nostr'
@ -221,28 +221,36 @@ const unfollow = async pubkey => {
// Methods that wil attempt to load from the database and fall back to the network. // Methods that wil attempt to load from the database and fall back to the network.
// This is intended only for bootstrapping listeners // This is intended only for bootstrapping listeners
const loadNoteContext = async (note, {loadParent = false} = {}) => { const loadNotesContext = async (notes, {loadParents = false} = {}) => {
const $people = get(people) notes = ensurePlural(notes)
const filter = [{kinds: [1, 5, 7], '#e': [note.id]}]
// Load the author if needed if (notes.length === 0) {
if (!$people[note.pubkey]) { return
filter.push({kinds: [0], authors: [note.pubkey]})
} }
// Load the note's parent const $people = get(people)
const parentId = findReply(note) const authors = uniq(pluck('pubkey', notes)).filter(k => !$people[k])
if (loadParent && parentId) { const parentIds = loadParents ? uniq(notes.map(findReply).filter(identity)) : []
filter.push({kinds: [1], ids: [parentId]}) const filter = [{kinds: [1, 5, 7], '#e': pluck('id', notes)}]
// Load authors if needed
if (authors.length > 0) {
filter.push({kinds: [0], authors})
}
// Load the note parents
if (parentIds.length > 0) {
filter.push({kinds: [1], ids: parentIds})
} }
// Load the events // Load the events
const events = await pool.loadEvents(filter) const events = await pool.loadEvents(filter)
const eventsById = createMap('id', events)
const parents = parentIds.map(id => eventsById[id]).filter(identity)
// Load the note's context as well // Load the parents' context as well
const parent = find(propEq('id', parentId), events) if (parents.length > 0) {
if (loadParent && parent) { await loadNotesContext(parents)
await loadNoteContext(parent)
} }
} }
@ -254,7 +262,7 @@ const getOrLoadNote = async id => {
const note = await db.events.get(id) const note = await db.events.get(id)
if (note) { if (note) {
await loadNoteContext(note, {loadParent: true}) await loadNotesContext([note], {loadParent: true})
} }
return note return note
@ -296,5 +304,5 @@ export const connections = db.connections
export default { export default {
db, pool, cmd, lq, filterEvents, getOrLoadNote, filterReplies, findNote, db, pool, cmd, lq, filterEvents, getOrLoadNote, filterReplies, findNote,
annotateChunk, renderNote, login, addRelay, removeRelay, annotateChunk, renderNote, login, addRelay, removeRelay,
follow, unfollow, loadNoteContext, follow, unfollow, loadNotesContext,
} }

View File

@ -16,7 +16,7 @@
onMount(() => { onMount(() => {
const scroller = createScroller(async () => { const scroller = createScroller(async () => {
notes = notes.concat(await loadNotes()) notes = uniqBy(prop('id'), notes.concat(await loadNotes()))
}) })
return () => scroller.stop() return () => scroller.stop()
@ -24,25 +24,15 @@
const loadNotes = async () => { const loadNotes = async () => {
const [since, until] = cursor.step() const [since, until] = cursor.step()
const filter = {kinds: [1, 7], '#p': [$user.pubkey], since, until}
await relay.pool.loadEvents( // Load all our alerts and their context
[{kinds: [1, 7], '#p': [$user.pubkey], since, until}], await relay.loadNotesContext(
e => { await relay.pool.loadEvents(filter),
if (e.kind === 1) { {loadParents: true}
relay.loadNoteContext(e) )
}
if (e.kind === 7) {
const replyId = findReply(e)
if (replyId) {
relay.getOrLoadNote(replyId)
}
}
alerts.set({since: now()}) alerts.set({since: now()})
}
)
const events = await relay.filterEvents({ const events = await relay.filterEvents({
since, since,
@ -80,7 +70,6 @@
// Combine likes of a single note. Remove grandchild likes // Combine likes of a single note. Remove grandchild likes
const likesById = {} const likesById = {}
const alerts = notes.filter(e => e.pubkey !== $user.pubkey)
for (const reaction of reactions.filter(e => e.parent?.pubkey === $user.pubkey)) { for (const reaction of reactions.filter(e => e.parent?.pubkey === $user.pubkey)) {
if (!likesById[reaction.parent.id]) { if (!likesById[reaction.parent.id]) {
likesById[reaction.parent.id] = {...reaction.parent, people: []} likesById[reaction.parent.id] = {...reaction.parent, people: []}
@ -91,7 +80,9 @@
return sortBy( return sortBy(
e => -e.created_at, e => -e.created_at,
uniqBy(prop('id'), alerts.concat(Object.values(likesById))) notes
.filter(e => e.pubkey !== $user.pubkey)
.concat(Object.values(likesById))
) )
} }

View File

@ -22,7 +22,7 @@
sub = await relay.pool.listenForEvents( sub = await relay.pool.listenForEvents(
'routes/Person', 'routes/Person',
[{kinds: [0, 1, 5, 7], authors: [pubkey], since: now()}], [{kinds: [0, 1, 5, 7], authors: [pubkey], since: now()}],
when(propEq('kind', 1), relay.loadNoteContext) when(propEq('kind', 1), relay.loadNotesContext)
) )
}) })

View File

@ -21,7 +21,7 @@
sub = await relay.pool.listenForEvents( sub = await relay.pool.listenForEvents(
'routes/NoteDetail', 'routes/NoteDetail',
[{kinds: [1, 5, 7], '#e': [note.id], since: now()}], [{kinds: [1, 5, 7], '#e': [note.id], since: now()}],
when(propEq('kind', 1), relay.loadNoteContext) when(propEq('kind', 1), relay.loadNotesContext)
) )
} }
}) })

View File

@ -12,7 +12,7 @@
sub = await relay.pool.listenForEvents( sub = await relay.pool.listenForEvents(
'views/notes/Global', 'views/notes/Global',
[{kinds: [1, 5, 7], since: cursor.since}], [{kinds: [1, 5, 7], since: cursor.since}],
when(propEq('kind', 1), relay.loadNoteContext) when(propEq('kind', 1), relay.loadNotesContext)
) )
}) })
@ -24,13 +24,11 @@
const cursor = new Cursor(timedelta(1, 'minutes')) const cursor = new Cursor(timedelta(1, 'minutes'))
const loadNotes = () => { const loadNotes = async () => {
const [since, until] = cursor.step() const [since, until] = cursor.step()
const filter = {kinds: [1], since, until}
return relay.pool.loadEvents( const notes = await relay.pool.loadEvents(filter)
[{kinds: [1, 5, 7], since, until}], await relay.loadNotesContext(notes, {loadParents: true})
when(propEq('kind', 1), relay.loadNoteContext)
)
} }
const queryNotes = () => { const queryNotes = () => {

View File

@ -16,7 +16,7 @@
sub = await relay.pool.listenForEvents( sub = await relay.pool.listenForEvents(
'views/notes/Network', 'views/notes/Network',
[{kinds: [1, 5, 7], authors: $network, since: cursor.since}], [{kinds: [1, 5, 7], authors: $network, since: cursor.since}],
when(propEq('kind', 1), relay.loadNoteContext) when(propEq('kind', 1), relay.loadNotesContext)
) )
}) })
}) })
@ -31,13 +31,11 @@
const cursor = new Cursor(timedelta(10, 'minutes')) const cursor = new Cursor(timedelta(10, 'minutes'))
const loadNotes = () => { const loadNotes = async () => {
const [since, until] = cursor.step() const [since, until] = cursor.step()
const filter = {kinds: [1, 7], authors: $network, since, until}
return relay.pool.loadEvents( const notes = await relay.pool.loadEvents(filter)
[{kinds: [1, 5, 7], authors: $network, since, until}], await relay.loadNotesContext(notes, {loadParents: true})
when(propEq('kind', 1), relay.loadNoteContext)
)
} }
const queryNotes = () => { const queryNotes = () => {

View File

@ -8,10 +8,14 @@
const cursor = new Cursor(timedelta(1, 'days')) const cursor = new Cursor(timedelta(1, 'days'))
const loadNotes = () => { const loadNotes = async () => {
const [since, until] = cursor.step() const [since, until] = cursor.step()
const filter = {kinds: [7], authors: [pubkey], since, until}
return relay.pool.loadEvents({kinds: [7], authors: [pubkey], since, until}) await relay.loadEventsContext(
await relay.pool.loadEvents(filter),
{loadParents: true}
)
} }
const queryNotes = () => { const queryNotes = () => {

View File

@ -10,13 +10,13 @@
const loadNotes = async () => { const loadNotes = async () => {
const [since, until] = cursor.step() const [since, until] = cursor.step()
const authors = getTagValues(person.petnames)
const filter = {since, until, kinds: [1], authors}
return relay.pool.loadEvents({ await relay.loadEventsContext(
since, await relay.pool.loadEvents(filter),
until, {loadParents: true}
kinds: [1], )
authors: getTagValues(person.petnames),
})
} }
const queryNotes = () => { const queryNotes = () => {

View File

@ -7,12 +7,13 @@
const cursor = new Cursor(timedelta(1, 'days')) const cursor = new Cursor(timedelta(1, 'days'))
const loadNotes = () => { const loadNotes = async () => {
const [since, until] = cursor.step() const [since, until] = cursor.step()
const filter = {kinds: [1], authors: [pubkey], since, until}
return relay.pool.loadEvents( await relay.loadNotesContext(
[{kinds: [1], authors: [pubkey], since, until}], await relay.pool.loadEvents(filter),
relay.loadNoteContext {loadParents: true}
) )
} }