Browse Source

split fetchEvents into multiple subfunctions

master
limina1 8 months ago
parent
commit
dc00474135
  1. 1
      src/lib/components/cards/BlogHeader.svelte
  2. 223
      src/routes/visualize/+page.svelte

1
src/lib/components/cards/BlogHeader.svelte

@ -6,6 +6,7 @@ @@ -6,6 +6,7 @@
import Interactions from "$components/util/Interactions.svelte";
import { quintOut } from "svelte/easing";
import CardActions from "$components/util/CardActions.svelte";
<<<<<<< HEAD
import { getMatchingTags } from "$lib/utils/nostrUtils";
import LazyImage from "$components/util/LazyImage.svelte";
import { generateDarkPastelColor } from "$lib/utils/image_utils";

223
src/routes/visualize/+page.svelte

@ -220,8 +220,14 @@ @@ -220,8 +220,14 @@
/**
* Fetches events from the Nostr network
*
* This function fetches index events and their referenced content events,
* filters them according to NIP-62, and combines them for visualization.
* This function orchestrates the fetching of events through multiple steps:
* 1. Setup configuration and loading state
* 2. Fetch non-publication events (kinds 1, 3, etc.)
* 3. Fetch publication index events
* 4. Extract and fetch content events
* 5. Deduplicate and combine all events
* 6. Fetch profiles for discovered pubkeys
* 7. Apply display limits and finalize
*/
async function fetchEvents() {
// Prevent concurrent fetches
@ -238,12 +244,47 @@ @@ -238,12 +244,47 @@
loading = true;
error = null;
// Get ALL event configurations (Phase 5: fetch all, display enabled)
// Step 1: Setup configuration and loading state
const { allConfigs, publicationConfigs, otherConfigs, kind0Config } = setupFetchConfiguration();
// Step 2: Fetch non-publication events
const nonPublicationEvents = await fetchNonPublicationEvents(otherConfigs);
// Step 3: Fetch publication index events
const validIndexEvents = await fetchPublicationIndexEvents(publicationConfigs);
// Step 4: Extract and fetch content events
const contentEvents = await fetchContentEvents(validIndexEvents, publicationConfigs);
// Step 5: Deduplicate and combine all events
const combinedEvents = deduplicateAndCombineEvents(nonPublicationEvents, validIndexEvents, contentEvents);
// Step 6: Fetch profiles for discovered pubkeys
const eventsWithProfiles = await fetchProfilesForEvents(combinedEvents, kind0Config);
// Step 7: Apply display limits and finalize
finalizeEventFetch(eventsWithProfiles);
} catch (e) {
console.error("Error fetching events:", e);
error = e instanceof Error ? e.message : String(e);
} finally {
loading = false;
isFetching = false;
debug("Loading set to false in fetchEvents");
debug("Final state check - loading:", loading, "events.length:", events.length, "allEvents.length:", allEvents.length);
}
}
/**
* Step 1: Setup configuration and loading state
*/
function setupFetchConfiguration() {
const config = get(visualizationConfig);
const allConfigs = config.eventConfigs;
debug("All event configs:", allConfigs);
debug("Disabled kinds:", config.disabledKinds);
debug("Enabled kinds:", allConfigs.filter(ec => ec.enabled !== false).map(ec => ec.kind));
// Set loading event kinds for display (show all being loaded)
loadingEventKinds = allConfigs.map(ec => ({
@ -256,12 +297,18 @@ @@ -256,12 +297,18 @@
const publicationConfigs = allConfigs.filter(ec => publicationKinds.includes(ec.kind));
const otherConfigs = allConfigs.filter(ec => !publicationKinds.includes(ec.kind));
let allFetchedEvents: NDKEvent[] = [];
// First, fetch non-publication events (like kind 1, 3, etc. but NOT kind 0)
// We'll fetch kind 0 profiles after we know which pubkeys we need
// Find kind 0 config for profile fetching
const kind0Config = otherConfigs.find(c => c.kind === 0);
return { allConfigs, publicationConfigs, otherConfigs, kind0Config };
}
/**
* Step 2: Fetch non-publication events (kinds 1, 3, etc. but NOT kind 0)
*/
async function fetchNonPublicationEvents(otherConfigs: any[]): Promise<NDKEvent[]> {
const nonProfileConfigs = otherConfigs.filter(c => c.kind !== 0);
let allFetchedEvents: NDKEvent[] = [];
if (nonProfileConfigs.length > 0) {
debug("Fetching non-publication events (excluding profiles):", nonProfileConfigs);
@ -293,8 +340,13 @@ @@ -293,8 +340,13 @@
}
}
// Then handle publication events as before
let validIndexEvents: Set<NDKEvent> = new Set();
return allFetchedEvents;
}
/**
* Step 3: Fetch publication index events
*/
async function fetchPublicationIndexEvents(publicationConfigs: any[]): Promise<Set<NDKEvent>> {
const shouldFetchIndex = publicationConfigs.some(ec => ec.kind === INDEX_EVENT_KIND);
if (data.eventId) {
@ -310,10 +362,10 @@ @@ -310,10 +362,10 @@
throw new Error(`Event ${data.eventId} is not a publication index (kind ${INDEX_EVENT_KIND})`);
}
validIndexEvents = new Set([event]);
return new Set([event]);
} else if (!shouldFetchIndex) {
debug("Index events (30040) are disabled, skipping fetch");
validIndexEvents = new Set();
return new Set();
} else {
// Original behavior: fetch all publications
debug(`Fetching index events (kind ${INDEX_EVENT_KIND})`);
@ -334,12 +386,57 @@ @@ -334,12 +386,57 @@
debug("Fetched index events:", indexEvents.size);
// Filter valid index events according to NIP-62
validIndexEvents = filterValidIndexEvents(indexEvents);
const validIndexEvents = filterValidIndexEvents(indexEvents);
debug("Valid index events after filtering:", validIndexEvents.size);
return validIndexEvents;
}
}
// Step 3: Extract content event references from index events
/**
* Step 4: Extract and fetch content events
*/
async function fetchContentEvents(validIndexEvents: Set<NDKEvent>, publicationConfigs: any[]): Promise<Set<NDKEvent>> {
// Extract content event references from index events
const contentReferences = extractContentReferences(validIndexEvents);
debug("Content references to fetch:", contentReferences.size);
// Fetch the referenced content events with author filter
const enabledPublicationKinds = publicationConfigs.map(ec => ec.kind);
const enabledContentKinds = CONTENT_EVENT_KINDS.filter(kind => enabledPublicationKinds.includes(kind));
debug(`Fetching content events (enabled kinds: ${enabledContentKinds.join(', ')})`);
// Group by author to make more efficient queries
const referencesByAuthor = groupContentReferencesByAuthor(contentReferences, enabledContentKinds);
// Fetch events for each author
const contentEventPromises = Array.from(referencesByAuthor.entries()).map(
async ([author, refs]) => {
const dTags = [...new Set(refs.map(r => r.dTag))]; // Dedupe d-tags
return $ndkInstance.fetchEvents({
kinds: enabledContentKinds, // Only fetch enabled kinds
authors: [author],
"#d": dTags,
});
}
);
const contentEventSets = await Promise.all(contentEventPromises);
// Deduplicate by keeping only the most recent version of each d-tag per author
const eventsByCoordinate = deduplicateContentEvents(contentEventSets);
const contentEvents = new Set(eventsByCoordinate.values());
debug("Fetched content events after deduplication:", contentEvents.size);
return contentEvents;
}
/**
* Extract content event references from index events
*/
function extractContentReferences(validIndexEvents: Set<NDKEvent>): Map<string, { kind: number; pubkey: string; dTag: string }> {
const contentReferences = new Map<string, { kind: number; pubkey: string; dTag: string }>();
validIndexEvents.forEach((event) => {
const aTags = event.getMatchingTags("a");
debug(`Event ${event.id} has ${aTags.length} a-tags`);
@ -362,16 +459,19 @@ @@ -362,16 +459,19 @@
}
});
});
debug("Content references to fetch:", contentReferences.size);
// Step 4: Fetch the referenced content events with author filter
// Only fetch content kinds that are enabled
const enabledPublicationKinds = publicationConfigs.map(ec => ec.kind);
const enabledContentKinds = CONTENT_EVENT_KINDS.filter(kind => enabledPublicationKinds.includes(kind));
debug(`Fetching content events (enabled kinds: ${enabledContentKinds.join(', ')})`);
return contentReferences;
}
// Group by author to make more efficient queries
/**
* Group content references by author for efficient fetching
*/
function groupContentReferencesByAuthor(
contentReferences: Map<string, { kind: number; pubkey: string; dTag: string }>,
enabledContentKinds: number[]
): Map<string, Array<{ kind: number; dTag: string }>> {
const referencesByAuthor = new Map<string, Array<{ kind: number; dTag: string }>>();
contentReferences.forEach(({ kind, pubkey, dTag }) => {
// Only include references for enabled kinds
if (enabledContentKinds.includes(kind)) {
@ -382,24 +482,16 @@ @@ -382,24 +482,16 @@
}
});
// Fetch events for each author
const contentEventPromises = Array.from(referencesByAuthor.entries()).map(
async ([author, refs]) => {
const dTags = [...new Set(refs.map(r => r.dTag))]; // Dedupe d-tags
return $ndkInstance.fetchEvents({
kinds: enabledContentKinds, // Only fetch enabled kinds
authors: [author],
"#d": dTags,
});
return referencesByAuthor;
}
);
const contentEventSets = await Promise.all(contentEventPromises);
// Deduplicate by keeping only the most recent version of each d-tag per author
/**
* Deduplicate content events by keeping only the most recent version
*/
function deduplicateContentEvents(contentEventSets: Set<NDKEvent>[]): Map<string, NDKEvent> {
const eventsByCoordinate = new Map<string, NDKEvent>();
contentEventSets.forEach((eventSet, idx) => {
contentEventSets.forEach((eventSet) => {
eventSet.forEach(event => {
const dTag = event.tagValue("d");
const author = event.pubkey;
@ -420,14 +512,21 @@ @@ -420,14 +512,21 @@
});
});
const contentEvents = new Set(eventsByCoordinate.values());
debug("Fetched content events after deduplication:", contentEvents.size);
return eventsByCoordinate;
}
// Step 5: Combine all events (non-publication + publication events)
/**
* Step 5: Deduplicate and combine all events
*/
function deduplicateAndCombineEvents(
nonPublicationEvents: NDKEvent[],
validIndexEvents: Set<NDKEvent>,
contentEvents: Set<NDKEvent>
): NDKEvent[] {
// First, build coordinate map for replaceable events
const coordinateMap = new Map<string, NDKEvent>();
const allEventsToProcess = [
...allFetchedEvents, // Non-publication events fetched earlier
...nonPublicationEvents, // Non-publication events fetched earlier
...Array.from(validIndexEvents),
...Array.from(contentEvents)
];
@ -490,13 +589,21 @@ @@ -490,13 +589,21 @@
baseEvents = [...allEvents]; // Store base events for tag expansion
// Step 6: Extract all pubkeys and fetch profiles
return allEvents;
}
/**
* Step 6: Fetch profiles for discovered pubkeys
*/
async function fetchProfilesForEvents(combinedEvents: NDKEvent[], kind0Config: any): Promise<NDKEvent[]> {
// Extract all pubkeys and fetch profiles
debug("Extracting pubkeys from all events");
// Use the utility function to extract ALL pubkeys (authors + p tags + content)
const allPubkeys = extractPubkeysFromEvents(allEvents);
const allPubkeys = extractPubkeysFromEvents(combinedEvents);
// Check if follow list is configured with limit > 0
const allConfigs = get(visualizationConfig).eventConfigs;
const followListConfig = allConfigs.find(c => c.kind === 3);
const shouldIncludeFollowPubkeys = followListConfig && followListConfig.limit > 0;
@ -517,7 +624,7 @@ @@ -517,7 +624,7 @@
debug("Profile extraction complete:", {
totalPubkeys: allPubkeys.size,
fromEvents: allEvents.length,
fromEvents: combinedEvents.length,
fromFollowLists: followListEvents.length
});
@ -540,7 +647,7 @@ @@ -540,7 +647,7 @@
debug("Profile fetch complete, fetched", profileEvents.length, "profiles");
// Add profile events to allEvents
allEvents = [...allEvents, ...profileEvents];
allEvents = [...combinedEvents, ...profileEvents];
// Update profile stats for display
// Use the total number of pubkeys, not just newly fetched profiles
@ -548,29 +655,29 @@ @@ -548,29 +655,29 @@
totalFetched: allPubkeys.size,
displayLimit: kind0Config.limit
};
} else {
allEvents = [...combinedEvents];
}
// Step 7: Apply display limits
events = filterByDisplayLimits(allEvents, $visualizationConfig);
return allEvents;
}
// Step 8: Detect missing events
const eventIds = new Set(allEvents.map(e => e.id));
/**
* Step 7: Apply display limits and finalize
*/
function finalizeEventFetch(eventsWithProfiles: NDKEvent[]) {
// Apply display limits
events = filterByDisplayLimits(eventsWithProfiles, $visualizationConfig);
// Detect missing events
const eventIds = new Set(eventsWithProfiles.map(e => e.id));
missingEventIds = detectMissingEvents(events, eventIds);
debug("Total events fetched:", allEvents.length);
debug("Total events fetched:", eventsWithProfiles.length);
debug("Events displayed:", events.length);
debug("Missing event IDs:", missingEventIds.size);
debug("About to set loading to false");
debug("Current loading state:", loading);
} catch (e) {
console.error("Error fetching events:", e);
error = e instanceof Error ? e.message : String(e);
} finally {
loading = false;
isFetching = false;
debug("Loading set to false in fetchEvents");
debug("Final state check - loading:", loading, "events.length:", events.length, "allEvents.length:", allEvents.length);
}
}
@ -793,7 +900,7 @@ @@ -793,7 +900,7 @@
// React to display limit and allowed kinds changes
$effect(() => {
debug("Effect triggered: allEvents.length =", allEvents.length, "allowedKinds =", $visualizationConfig.allowedKinds);
debug("Effect triggered: allEvents.length =", allEvents.length, "enabledKinds =", $visualizationConfig.eventConfigs.filter(ec => ec.enabled !== false).map(ec => ec.kind));
if (allEvents.length > 0) {
const newEvents = filterByDisplayLimits(allEvents, $visualizationConfig);

Loading…
Cancel
Save