Notes & Threads
Short text notes are kind 1 events, and replies to them are notes too. Replies point at their parent and the thread root with marked e tags (NIP-10), so applesauce can rebuild a conversation from the notes in the store.
Reading a note
The Note cast exposes a note's thread references and the events around it. Invalid events are dropped by castTimelineStream, so only kind 1 notes come through.
const notes = use$(() => store.timeline({ kinds: [1] }).pipe(castTimelineStream(Note, store)), []);
note.isRoot; // true when the note replies to nothing
note.isReply; // true when the note has a reply reference
note.references; // { root?: { e, a }, reply?: { e, a } }
note.quotePointers; // EventPointer[] from "q" tagsnote.replies$, comments$, reactions$, zaps$, and shares$ subscribe to related events already in the store and cast them.
Walking up a thread
threadRoot$ and replyingTo$ resolve the root and parent events. They use store.event(), so a configured event loader fetches missing events.
const root = use$(note.threadRoot$);
const parent = use$(note.replyingTo$);Building a thread tree
store.thread(root) returns a Thread built from the root and every note tagging it. thread.root is the root item and thread.all maps each event's UID to its item, and each item links its parent and direct replies.
const thread = use$(() => store.thread(pointer), [pointer.id]);
function render(item: ThreadItem) {
return [item.event, ...[...item.replies].flatMap(render)];
}store.thread() is shorthand for store.model(ThreadModel, root). Use the model directly to include other kinds, such as { kinds: [1, 1111] }.
Listing direct replies
RepliesModel returns only the direct replies to an event. For a note it includes both kind 1 replies and kind 1111 comments, and for other kinds only comments.
const replies = use$(() => store.model(RepliesModel, event), [event.id]);Pass a kind list as the second argument to override this; note.replies$ passes [1].
Writing notes and replies
NoteFactory.create() builds a note, and NoteFactory.reply() adds the root and reply e tags and copies the parent's p tags so everyone in the thread is notified.
const note = await NoteFactory.create("hello #nostr").sign(signer);
const reply = await NoteFactory.reply(parent, "nice!").mention(pubkey).sign(signer);reply() throws when the parent is not a kind 1 note. Reply to other kinds with CommentFactory.
The builder also has .subject(), .addHashtag(), .hashtags(), and .zapSplit(). See common factories for signing and publishing.
Helpers
getNip10References(event)parses and caches therootandreplypointers, including legacy positionaletagsinterpretThreadTags(tags)returns the raw root and reply tagsgetEventPointerFromThreadTag(tag)builds anEventPointerfrom a threadetag and throws without an idsetThreadParent(parent)andincludePubkeyNotificationTags(parent)are the operations behindNoteFactory.reply()
Best Practices
- Load replies with a relay subscription or loader first, since
replies$,store.thread(), andRepliesModelonly read the store - Use
NoteFactory.reply()for kind 1 parents andCommentFactory.reply()for everything else - Use
RepliesModelfor a flat list of direct replies andstore.thread()for a nested conversation - Configure an event loader on the store so
threadRoot$andreplyingTo$can fetch parents that aren't loaded