Browse Source

Work with lazy-loading nodes

master
buttercat1791 1 year ago
parent
commit
b4906215c5
  1. 116
      src/lib/data_structures/publication_tree.ts

116
src/lib/data_structures/publication_tree.ts

@ -1,8 +1,15 @@
import type NDK from "@nostr-dev-kit/ndk"; import type NDK from "@nostr-dev-kit/ndk";
import type { NDKEvent, NDKFilter } from "@nostr-dev-kit/ndk"; import type { NDKEvent, NDKFilter } from "@nostr-dev-kit/ndk";
import type { Lazy } from "./lazy"; import { Lazy } from "./lazy";
enum PublicationTreeNodeType {
Root,
Branch,
Leaf,
}
interface PublicationTreeNode { interface PublicationTreeNode {
type: PublicationTreeNodeType;
address: string; address: string;
parent?: PublicationTreeNode; parent?: PublicationTreeNode;
children?: Array<Lazy<PublicationTreeNode>>; children?: Array<Lazy<PublicationTreeNode>>;
@ -19,6 +26,12 @@ export class PublicationTree implements AsyncIterable<NDKEvent> {
*/ */
private nodes: Map<string, PublicationTreeNode>; private nodes: Map<string, PublicationTreeNode>;
/**
* A map of addresses in the tree to their corresponding lazy-loaded nodes. When a lazy node is
* retrieved, it is added to the {@link PublicationTree.nodes} map.
*/
private lazyNodes: Map<string, Lazy<PublicationTreeNode>> = new Map();
/** /**
* A map of addresses in the tree to their corresponding events. * A map of addresses in the tree to their corresponding events.
*/ */
@ -40,8 +53,9 @@ export class PublicationTree implements AsyncIterable<NDKEvent> {
private ndk: NDK; private ndk: NDK;
constructor(rootEvent: NDKEvent, ndk: NDK) { constructor(rootEvent: NDKEvent, ndk: NDK) {
const rootAddress = this.getAddressFromEvent(rootEvent); const rootAddress = rootEvent.tagAddress();
this.root = { this.root = {
type: PublicationTreeNodeType.Root,
address: rootAddress, address: rootAddress,
children: [], children: [],
}; };
@ -64,8 +78,8 @@ export class PublicationTree implements AsyncIterable<NDKEvent> {
* {@link PublicationTree.getEvent} to retrieve an event already in the tree. * {@link PublicationTree.getEvent} to retrieve an event already in the tree.
*/ */
addEvent(event: NDKEvent, parentEvent: NDKEvent) { addEvent(event: NDKEvent, parentEvent: NDKEvent) {
const address = this.getAddressFromEvent(event); const address = event.tagAddress();
const parentAddress = this.getAddressFromEvent(parentEvent); const parentAddress = parentEvent.tagAddress();
const parentNode = this.nodes.get(parentAddress); const parentNode = this.nodes.get(parentAddress);
if (!parentNode) { if (!parentNode) {
@ -74,16 +88,64 @@ export class PublicationTree implements AsyncIterable<NDKEvent> {
); );
} }
// TODO: Determine node type.
const node = { const node = {
type: this.getNodeType(event),
address,
parent: parentNode,
children: [],
};
parentNode.children!.push(new Lazy<PublicationTreeNode>(() => Promise.resolve(node)));
this.nodes.set(address, node);
this.events.set(address, event);
}
/**
* Lazily adds an event to the publication tree by address if the full event is not already
* loaded into memory.
* @param address The address of the event to add.
* @param parentEvent The parent event of the event to add.
* @description The parent event must already be in the tree. Use
* {@link PublicationTree.getEvent} to retrieve an event already in the tree.
*/
addEventByAddress(address: string, parentEvent: NDKEvent) {
const parentAddress = parentEvent.tagAddress();
const parentNode = this.nodes.get(parentAddress);
if (!parentNode) {
throw new Error(
`PublicationTree: Parent node with address ${parentAddress} not found.`
);
}
const lazyNode = new Lazy<PublicationTreeNode>(async () => {
const [kind, pubkey, dTag] = address.split(':');
const event = await this.ndk.fetchEvent({
kinds: [parseInt(kind)],
authors: [pubkey],
'#d': [dTag],
});
if (!event) {
throw new Error(
`PublicationTree: Event with address ${address} not found.`
);
}
const node: PublicationTreeNode = {
type: this.getNodeType(event),
address, address,
parent: parentNode, parent: parentNode,
children: [], children: [],
}; };
// TODO: Define a resolver for the lazy node.
parentNode.children!.push(node);
this.nodes.set(address, node); this.nodes.set(address, node);
this.events.set(address, event); this.events.set(address, event);
return node;
});
parentNode.children!.push(lazyNode);
this.lazyNodes.set(address, lazyNode);
} }
/** /**
@ -122,7 +184,7 @@ export class PublicationTree implements AsyncIterable<NDKEvent> {
} }
const firstLeafEvent = await this.depthFirstRetrieve(); const firstLeafEvent = await this.depthFirstRetrieve();
this.bookmark = this.getAddressFromEvent(firstLeafEvent!); this.bookmark = firstLeafEvent!.tagAddress();
return { done: false, value: firstLeafEvent! }; return { done: false, value: firstLeafEvent! };
} }
@ -161,32 +223,16 @@ export class PublicationTree implements AsyncIterable<NDKEvent> {
.filter(tag => tag[0] === 'a') .filter(tag => tag[0] === 'a')
.map(tag => tag[1]); .map(tag => tag[1]);
const kinds = new Set<number>();
const pubkeys = new Set<string>();
const dTags = new Set<string>();
for (const childAddress of currentChildAddresses) { for (const childAddress of currentChildAddresses) {
if (this.nodes.has(childAddress)) { if (this.nodes.has(childAddress)) {
continue; continue;
} }
const [kind, pubkey, dTag] = childAddress.split(':'); this.addEventByAddress(childAddress, currentEvent!);
kinds.add(parseInt(kind));
pubkeys.add(pubkey);
dTags.add(dTag);
}
const childEvents = await this.ndk.fetchEvents({
kinds: Array.from(kinds),
authors: Array.from(pubkeys),
'#d': Array.from(dTags),
});
for (const childEvent of childEvents) {
this.addEvent(childEvent, currentEvent!);
} }
// If the current event has no children, it is a leaf. // If the current event has no children, it is a leaf.
if (childEvents.size === 0) { if (currentChildAddresses.length === 0) {
this.leaves.push(currentAddress!); this.leaves.push(currentAddress!);
// Return the first leaf if no address was provided. // Return the first leaf if no address was provided.
@ -244,14 +290,24 @@ export class PublicationTree implements AsyncIterable<NDKEvent> {
return this.getEvent(nextSibling!.address); return this.getEvent(nextSibling!.address);
} }
private getAddressFromEvent(event: NDKEvent): string { private getNodeType(event: NDKEvent): PublicationTreeNodeType {
if (event.kind! < 30000 || event.kind! >= 40000) { const address = event.tagAddress();
const node = this.nodes.get(address);
if (!node) {
throw new Error( throw new Error(
"PublicationTree: Invalid event kind. Event kind must be in the range 30000-39999" `PublicationTree: Event with address ${address} not found in the tree.`
); );
} }
return `${event.kind}:${event.pubkey}:${event.dTag}`; if (!node.parent) {
return PublicationTreeNodeType.Root;
}
if (event.tags.some(tag => tag[0] === 'a')) {
return PublicationTreeNodeType.Branch;
}
return PublicationTreeNodeType.Leaf;
} }
// #endregion // #endregion

Loading…
Cancel
Save