Optimize subscription feed retrieval and rendering

This commit is contained in:
thePR0M3TH3AN committed 2025-10-20 21:04:52 -04:00
1 parent a8cea0b147
commit e49483d34d
4 files changed
+139 -11

No files matched your search

+16 -1
View File
@@ -92,12 +92,19 @@ export function createSubscriptionAuthorsSource({ service } = {}) {
return [];
}
const limitCandidate = Number(context?.runtime?.limit);
const limit =
Number.isFinite(limitCandidate) && limitCandidate > 0
? Math.floor(limitCandidate)
: null;
const options = {
blacklistedEventIds: toSet(context?.runtime?.blacklistedEventIds),
isAuthorBlocked:
typeof context?.runtime?.isAuthorBlocked === "function"
? context.runtime.isAuthorBlocked
: () => false,
limit,
};
const authorList = Array.from(authors);
@@ -129,7 +136,15 @@ export function createSubscriptionAuthorsSource({ service } = {}) {
return author && authors.has(author);
});
return filtered.map((video) => ({
const sorted = filtered
.slice()
.sort(
(a, b) => (Number(b?.created_at) || 0) - (Number(a?.created_at) || 0)
);
const limited = limit ? sorted.slice(0, limit) : sorted;
return limited.map((video) => ({
video,
metadata: {
source: "nostr:subscriptions",
+39 -3
View File
@@ -297,6 +297,24 @@ export class NostrService {
index.get(author).push(video);
}
for (const [author, videos] of index.entries()) {
if (!Array.isArray(videos) || videos.length <= 1) {
continue;
}
videos.sort((a, b) => {
const aCreatedCandidate = Number(a?.created_at);
const bCreatedCandidate = Number(b?.created_at);
const aCreated = Number.isFinite(aCreatedCandidate)
? aCreatedCandidate
: 0;
const bCreated = Number.isFinite(bCreatedCandidate)
? bCreatedCandidate
: 0;
return bCreated - aCreated;
});
index.set(author, videos);
}
this.videosByAuthorIndex = index;
this.authorIndexDirty = false;
return this.videosByAuthorIndex;
@@ -400,13 +418,25 @@ export class NostrService {
const index = this.ensureVideosByAuthorIndex();
const collected = [];
const seen = new Set();
const limitCandidate = Number(options?.limit);
const limit =
Number.isFinite(limitCandidate) && limitCandidate > 0
? Math.floor(limitCandidate)
: null;
const perAuthorLimit = limit
? Math.max(limit * 2, limit + 5)
: null;
for (const author of normalizedAuthors) {
const entries = index.get(author);
if (!Array.isArray(entries)) {
if (!Array.isArray(entries) || entries.length === 0) {
continue;
}
for (const video of entries) {
const sliceCount = perAuthorLimit
? Math.min(perAuthorLimit, entries.length)
: entries.length;
for (let idx = 0; idx < sliceCount; idx += 1) {
const video = entries[idx];
if (!video || typeof video !== "object") {
continue;
}
@@ -421,13 +451,19 @@ export class NostrService {
const filtered = this.filterVideos(collected, options);
return filtered.sort((a, b) => {
const sorted = filtered.sort((a, b) => {
const aCreatedCandidate = Number(a?.created_at);
const bCreatedCandidate = Number(b?.created_at);
const aCreated = Number.isFinite(aCreatedCandidate) ? aCreatedCandidate : 0;
const bCreated = Number.isFinite(bCreatedCandidate) ? bCreatedCandidate : 0;
return bCreated - aCreated;
});
if (limit) {
return sorted.slice(0, limit);
}
return sorted;
}
async loadVideos({
+27 -7
View File
@@ -373,7 +373,11 @@ class SubscriptionsManager {
}
const app = getApp();
const runtime = this.buildFeedRuntime({ app, authors: channelHexes });
const runtime = this.buildFeedRuntime({
app,
authors: channelHexes,
limit,
});
const runOptions = {
actorPubkey: userPubkey,
limit,
@@ -410,11 +414,20 @@ class SubscriptionsManager {
"[SubscriptionsManager] Failed to run subscriptions feed:",
error
);
container.innerHTML =
"<p class='text-muted-strong'>Unable to load subscriptions right now.</p>";
this.lastResult = null;
if (container && this.lastResult) {
const fallbackReason = reason
? `${reason}:cached`
: "cached-result";
this.renderSameGridStyle(this.lastResult, containerId, {
limit,
reason: fallbackReason,
});
} else if (container) {
container.innerHTML =
"<p class='text-muted-strong'>Unable to load subscriptions right now.</p>";
}
this.hasRenderedOnce = Boolean(container);
return null;
return this.lastResult;
} finally {
this.isRunningFeed = false;
this.processScheduledRefresh();
@@ -500,7 +513,7 @@ class SubscriptionsManager {
return app?.feedEngine || null;
}
buildFeedRuntime({ app, authors = [] } = {}) {
buildFeedRuntime({ app, authors = [], limit = null } = {}) {
const normalizedAuthors = Array.isArray(authors)
? authors.filter((author) => typeof author === "string" && author)
: [];
@@ -515,11 +528,18 @@ class SubscriptionsManager {
? (pubkey) => app.isAuthorBlocked(pubkey)
: () => false;
const limitCandidate = Number(limit);
const normalizedLimit =
Number.isFinite(limitCandidate) && limitCandidate > 0
? Math.floor(limitCandidate)
: null;
return {
subscriptionAuthors: normalizedAuthors,
authors: normalizedAuthors,
blacklistedEventIds: blacklist,
isAuthorBlocked
isAuthorBlocked,
limit: normalizedLimit
};
}
+57
View File
@@ -199,8 +199,65 @@ async function testNoAuthorsSkipsLookup() {
assert.deepEqual(result.videos, [], "no authors should yield an empty feed");
}
async function testLimitApplied() {
const engine = createFeedEngine();
const feedName = "subscriptions-limit";
const videos = [
{ id: "video-1", pubkey: "pub1", created_at: 100 },
{ id: "video-2", pubkey: "pub1", created_at: 200 },
{ id: "video-3", pubkey: "pub1", created_at: 300 },
{ id: "video-4", pubkey: "pub1", created_at: 400 },
{ id: "video-5", pubkey: "pub1", created_at: 500 },
];
const calls = [];
const service = {
async getActiveVideosByAuthors(authors, options) {
calls.push({ authors, options });
return videos;
},
async getFilteredActiveVideos() {
throw new Error("limit run should not use fallback lookup");
},
};
engine.registerFeed(feedName, {
source: createSubscriptionAuthorsSource({ service }),
stages: [
createBlacklistFilterStage({ shouldIncludeVideo: () => true }),
createDedupeByRootStage(),
],
sorter: createChronologicalSorter(),
});
const result = await engine.runFeed(feedName, {
runtime: {
subscriptionAuthors: ["pub1"],
authors: ["pub1"],
blacklistedEventIds: new Set(),
isAuthorBlocked: () => false,
limit: 3,
},
});
assert.equal(calls.length, 1, "limit run should request targeted lookup once");
assert.equal(
calls[0]?.options?.limit,
3,
"limit option should be forwarded to the service lookup",
);
assert.deepEqual(
result.videos.map((video) => video.id),
["video-5", "video-4", "video-3"],
"feed should only include the newest videos within the limit",
);
}
await testBlockedCreatorsFiltered();
await testDuplicateRootsFiltered();
await testNoAuthorsSkipsLookup();
await testLimitApplied();
console.log("subscriptions-feed tests passed");