diff --git a/convex/packages.public.test.ts b/convex/packages.public.test.ts index 647bffc3..13d8ac36 100644 --- a/convex/packages.public.test.ts +++ b/convex/packages.public.test.ts @@ -683,10 +683,15 @@ function makeDigestCtx(options: { const withIndex = vi.fn((table: string, indexName: string) => { indexNames.push(indexName); + let ordered = false; return { - order: vi.fn(() => ({ - paginate: getPaginate(table), - })), + order: vi.fn(() => { + if (ordered) throw new Error("query builder reused after iteration"); + ordered = true; + return { + paginate: getPaginate(table), + }; + }), }; }); @@ -1485,6 +1490,32 @@ describe("packages public queries", () => { expect(result.map((entry) => entry.package.name)).toEqual(["secret-tools"]); }); + it("uses a fresh search digest query for each fallback scan page", async () => { + const { ctx, paginate } = makeDigestCtx({ + pages: [ + { + page: [makeDigest("first-page-miss")], + isDone: false, + continueCursor: "next", + }, + { + page: [makeDigest("needle-plugin")], + isDone: true, + continueCursor: "", + }, + ], + }); + + const result = await searchForViewerInternalHandler(ctx, { + query: "needle", + family: "code-plugin", + limit: 2, + }); + + expect(result.map((entry) => entry.package.name)).toEqual(["needle-plugin"]); + expect(paginate).toHaveBeenCalledTimes(2); + }); + it("allows org collaborators to search their private packages", async () => { const { ctx } = makeDigestCtx({ capabilityPages: [ diff --git a/convex/packages.ts b/convex/packages.ts index 1073c9dd..346c0248 100644 --- a/convex/packages.ts +++ b/convex/packages.ts @@ -1821,20 +1821,21 @@ async function searchPackagesImpl( })); } - const builder = args.capabilityTag - ? buildPackageCapabilityDigestQuery(ctx, { - capabilityTag: args.capabilityTag, - family: args.family, - channel: args.channel, - isOfficial: args.isOfficial, - executesCode: args.executesCode, - }) - : buildPackageDigestQuery(ctx, { - family: args.family, - channel: args.channel, - isOfficial: args.isOfficial, - executesCode: args.executesCode, - }); + const buildSearchDigestQuery = () => + args.capabilityTag + ? buildPackageCapabilityDigestQuery(ctx, { + capabilityTag: args.capabilityTag, + family: args.family, + channel: args.channel, + isOfficial: args.isOfficial, + executesCode: args.executesCode, + }) + : buildPackageDigestQuery(ctx, { + family: args.family, + channel: args.channel, + isOfficial: args.isOfficial, + executesCode: args.executesCode, + }); const matches: Array<{ score: number; package: PublicPackageListItem }> = []; const seen = new Set(); const directDigests = args.capabilityTag @@ -1873,7 +1874,9 @@ async function searchPackagesImpl( page: PackageDigestLike[]; isDone: boolean; continueCursor: string; - } = await builder.order("desc").paginate({ cursor, numItems: effectivePageSize }); + } = await buildSearchDigestQuery() + .order("desc") + .paginate({ cursor, numItems: effectivePageSize }); for (const digest of page.page) { if (!(await canViewPackage(digest))) continue; if (!digestMatchesSearchFilters(digest, args)) continue;