Browse Source

Implement iterator `next()` method using cursor

master
buttercat1791 1 year ago
parent
commit
7da145fb5e
  1. 180
      src/lib/data_structures/publication_tree.ts

180
src/lib/data_structures/publication_tree.ts

@ -26,12 +26,6 @@ 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.
*/ */
@ -117,37 +111,11 @@ export class PublicationTree implements AsyncIterable<NDKEvent> {
); );
} }
const lazyNode = new Lazy<PublicationTreeNode>(async () => { parentNode.children!.push(
const [kind, pubkey, dTag] = address.split(':'); new Lazy<PublicationTreeNode>(() => this.resolveNode(address, parentNode))
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,
parent: parentNode,
children: [],
};
this.nodes.set(address, node);
this.events.set(address, event);
return node;
});
parentNode.children!.push(lazyNode);
this.lazyNodes.set(address, lazyNode);
}
/** /**
* Retrieves an event from the publication tree. * Retrieves an event from the publication tree.
* @param address The address of the event to retrieve. * @param address The address of the event to retrieve.
@ -168,30 +136,36 @@ export class PublicationTree implements AsyncIterable<NDKEvent> {
*/ */
setBookmark(address: string) { setBookmark(address: string) {
this.bookmark = address; this.bookmark = address;
this.#cursor.tryMoveTo(address);
} }
[Symbol.asyncIterator](): AsyncIterator<NDKEvent> { [Symbol.asyncIterator](): AsyncIterator<NDKEvent> {
this.#cursor.tryMoveTo(this.bookmark);
return this; return this;
} }
async next(): Promise<IteratorResult<NDKEvent>> { async next(): Promise<IteratorResult<NDKEvent>> {
// If no bookmark is set, start at the first leaf. Retrieve that first leaf if necessary. while (this.#cursor.target?.type !== PublicationTreeNodeType.Leaf) {
if (!this.bookmark) { if (await this.#cursor.tryMoveToFirstChild()) {
this.bookmark = this.leaves.at(0); continue;
if (this.bookmark) {
const bookmarkEvent = await this.getEvent(this.bookmark);
return { done: false, value: bookmarkEvent! };
} }
const firstLeafEvent = await this.depthFirstRetrieve(); if (await this.#cursor.tryMoveToNextSibling()) {
this.bookmark = firstLeafEvent!.tagAddress(); continue;
return { done: false, value: firstLeafEvent! };
} }
// TODO: Invoke a funciton to retrieve the next sibling of the bookmark. if (await this.#cursor.tryMoveToParent()) {
continue;
}
if (this.#cursor.target?.type === PublicationTreeNodeType.Root) {
return { done: true, value: null }; return { done: true, value: null };
} }
}
const event = await this.getEvent(this.#cursor.target!.address);
return { done: false, value: event! };
}
// #region Private Methods // #region Private Methods
@ -252,42 +226,37 @@ export class PublicationTree implements AsyncIterable<NDKEvent> {
return null; return null;
} }
private async getNextSibling(address: string): Promise<NDKEvent | null> { private async resolveNode(
if (!this.leaves.includes(address)) { address: string,
throw new Error( parentNode: PublicationTreeNode
`PublicationTree: Address ${address} is not a leaf. Cannot retrieve next sibling.` ): Promise<PublicationTreeNode> {
); const [kind, pubkey, dTag] = address.split(':');
} const event = await this.ndk.fetchEvent({
kinds: [parseInt(kind)],
let currentNode = this.nodes.get(address); authors: [pubkey],
if (!currentNode) { '#d': [dTag],
return null; });
}
let parent = currentNode.parent; if (!event) {
if (!parent) {
throw new Error( throw new Error(
`PublicationTree: Address ${address} has no parent. Cannot retrieve next sibling.` `PublicationTree: Event with address ${address} not found.`
); );
} }
// TODO: Handle the case where the current node is the last leaf. const childAddresses = event.tags.filter(tag => tag[0] === 'a').map(tag => tag[1]);
const node: PublicationTreeNode = {
let nextSibling: PublicationTreeNode | null = null; type: this.getNodeType(event),
do { address,
const siblings: Lazy<PublicationTreeNode>[] = parent!.children!; parent: parentNode,
const currentIndex = siblings.findIndex(async sibling => (await sibling.value()).address === currentNode!.address); children: childAddresses.map(
nextSibling = (await siblings.at(currentIndex + 1)?.value()) ?? null; address => new Lazy<PublicationTreeNode>(() => this.resolveNode(address, node))
),
};
// If the next sibling has children, it is not a leaf. this.nodes.set(address, node);
if ((nextSibling?.children?.length ?? 0) > 0) { this.events.set(address, event);
currentNode = (await nextSibling!.children!.at(0)!.value())!;
parent = currentNode.parent;
nextSibling = null;
}
} while (nextSibling == null);
return this.getEvent(nextSibling!.address); return node;
} }
private getNodeType(event: NDKEvent): PublicationTreeNodeType { private getNodeType(event: NDKEvent): PublicationTreeNodeType {
@ -314,49 +283,52 @@ export class PublicationTree implements AsyncIterable<NDKEvent> {
// #region Iteration Cursor // #region Iteration Cursor
Cursor = class { #cursor = new class {
private tree: PublicationTree; target: PublicationTreeNode | null | undefined;
private currentNode: PublicationTreeNode | null | undefined;
constructor(tree: PublicationTree, currentNode: PublicationTreeNode | null = null) { #tree: PublicationTree;
this.tree = tree;
if (!currentNode) { constructor(tree: PublicationTree) {
this.currentNode = this.tree.bookmark this.#tree = tree;
? this.tree.nodes.get(this.tree.bookmark)
: null;
} }
async tryMoveTo(address?: string) {
if (!address) {
const startEvent = await this.#tree.depthFirstRetrieve();
this.target = this.#tree.nodes.get(startEvent!.tagAddress());
} else {
this.target = this.#tree.nodes.get(address);
} }
async moveToFirstChild(): Promise<boolean> { if (!this.target) {
if (!this.currentNode) { return false;
throw new Error("Cursor: Current node is null or undefined.");
} }
const hasChildren = (this.currentNode.children?.length ?? 0) > 0; return true;
const isLeaf = this.tree.leaves.includes(this.currentNode.address); }
if (!hasChildren && isLeaf) { async tryMoveToFirstChild(): Promise<boolean> {
return false; if (!this.target) {
throw new Error("Cursor: Target node is null or undefined.");
} }
if (!hasChildren && !isLeaf) { if (this.target.type === PublicationTreeNodeType.Leaf) {
// TODO: Fetch any missing children, then return the first child. return false;
} }
this.currentNode = (await this.currentNode.children?.at(0)?.value())!; this.target = (await this.target.children?.at(0)?.value())!;
return true; return true;
} }
async moveToNextSibling(): Promise<boolean> { async tryMoveToNextSibling(): Promise<boolean> {
if (!this.currentNode) { if (!this.target) {
throw new Error("Cursor: Current node is null or undefined."); throw new Error("Cursor: Target node is null or undefined.");
} }
const parent = this.currentNode.parent; const parent = this.target.parent;
const siblings = parent?.children; const siblings = parent?.children;
const currentIndex = siblings?.findIndex(async sibling => const currentIndex = siblings?.findIndex(async sibling =>
(await sibling.value()).address === this.currentNode!.address (await sibling.value()).address === this.target!.address
); );
const nextSibling = (await siblings?.at(currentIndex! + 1)?.value()) ?? null; const nextSibling = (await siblings?.at(currentIndex! + 1)?.value()) ?? null;
@ -364,24 +336,24 @@ export class PublicationTree implements AsyncIterable<NDKEvent> {
return false; return false;
} }
this.currentNode = nextSibling; this.target = nextSibling;
return true; return true;
} }
moveToParent(): boolean { tryMoveToParent(): boolean {
if (!this.currentNode) { if (!this.target) {
throw new Error("Cursor: Current node is null or undefined."); throw new Error("Cursor: Target node is null or undefined.");
} }
const parent = this.currentNode.parent; const parent = this.target.parent;
if (!parent) { if (!parent) {
return false; return false;
} }
this.currentNode = parent; this.target = parent;
return true; return true;
} }
}; }(this);
// #endregion // #endregion
} }
Loading…
Cancel
Save