fix: load official skills from curated index (#3314)

This commit is contained in:
Patrick Erichsen
2026-07-30 10:26:32 -07:00
committed by GitHub
parent dc51281c87
commit 87b14acc61
2 changed files with 308 additions and 10 deletions
+234 -6
View File
@@ -333,18 +333,246 @@ describe("public skill list deterministic cursors", () => {
],
});
const result = await listPublicPageV4Handler({} as never, {
officialOnly: true,
createdAfter: 100,
sort: "newest",
numItems: 10,
});
const result = await listPublicPageV4Handler(
makeOfficialFirstCategoryCtx([officialNew, communityNew, officialOld]) as never,
{
officialOnly: true,
createdAfter: 100,
sort: "newest",
numItems: 10,
},
);
expect(
(result.page as Array<{ skill: { slug: string } }>).map((entry) => entry.skill.slug),
).toEqual(["official-new"]);
});
it("reads Official pages from the indexed curated projection", async () => {
const official = makeSearchDigest({
skillId: "skills:official",
slug: "official",
badges: { official: { byUserId: "users:admin", at: 2 } },
createdAt: 2,
updatedAt: 2,
});
const highlighted = makeSearchDigest({
skillId: "skills:highlighted",
slug: "highlighted",
badges: { highlighted: { byUserId: "users:admin", at: 1 } },
createdAt: 1,
updatedAt: 1,
});
const ctx = makeOfficialFirstCategoryCtx([official, highlighted]);
getPageMock.mockResolvedValueOnce({
page: [official, highlighted],
hasMore: false,
indexKeys: [
[undefined, official.createdAt, official.updatedAt, "curatedSkillSearchDigest:official"],
[
undefined,
highlighted.createdAt,
highlighted.updatedAt,
"curatedSkillSearchDigest:highlighted",
],
],
});
const result = await listPublicPageV4Handler(ctx as never, {
officialOnly: true,
sort: "newest",
dir: "desc",
numItems: 10,
});
expect(getPageMock).toHaveBeenCalledTimes(1);
expect(getPageMock.mock.calls[0]?.[1]).toMatchObject({
table: "curatedSkillSearchDigest",
index: "by_active_created",
});
expect(
(result.page as Array<{ skill: { slug: string } }>).map((entry) => entry.skill.slug),
).toEqual(["official"]);
});
it("continues through highlighted-only curated windows to reach Official rows", async () => {
const highlighted = makeSearchDigest({
skillId: "skills:highlighted",
slug: "highlighted",
badges: { highlighted: { byUserId: "users:admin", at: 2 } },
createdAt: 2,
updatedAt: 2,
});
const official = makeSearchDigest({
skillId: "skills:official",
slug: "official",
badges: { official: { byUserId: "users:admin", at: 1 } },
createdAt: 1,
updatedAt: 1,
});
const highlightedIndexKey = [
undefined,
highlighted.createdAt,
highlighted.updatedAt,
"curatedSkillSearchDigest:highlighted",
];
const ctx = makeOfficialFirstCategoryCtx([highlighted, official]);
getPageMock
.mockResolvedValueOnce({
page: [highlighted],
hasMore: true,
indexKeys: [highlightedIndexKey],
})
.mockResolvedValueOnce({
page: [official],
hasMore: false,
indexKeys: [
[undefined, official.createdAt, official.updatedAt, "curatedSkillSearchDigest:official"],
],
});
const result = await listPublicPageV4Handler(ctx as never, {
officialOnly: true,
sort: "newest",
numItems: 1,
});
expect(
(result.page as Array<{ skill: { slug: string } }>).map((entry) => entry.skill.slug),
).toEqual(["official"]);
expect(getPageMock).toHaveBeenCalledTimes(2);
expect(getPageMock.mock.calls[1]?.[1]).toMatchObject({
table: "curatedSkillSearchDigest",
startIndexKey: highlightedIndexKey,
startInclusive: false,
});
});
it("tags Official projection cursors before continuing on the curated index", async () => {
const official = makeSearchDigest({
skillId: "skills:official",
slug: "official",
badges: { official: { byUserId: "users:admin", at: 2 } },
createdAt: 2,
updatedAt: 2,
});
const indexKey = [
undefined,
official.createdAt,
official.updatedAt,
"curatedSkillSearchDigest:official",
];
const ctx = makeOfficialFirstCategoryCtx([official]);
getPageMock
.mockResolvedValueOnce({
page: [official],
hasMore: true,
indexKeys: [indexKey],
})
.mockResolvedValueOnce({ page: [], hasMore: false, indexKeys: [] });
const first = await listPublicPageV4Handler(ctx as never, {
officialOnly: true,
sort: "newest",
numItems: 1,
});
const second = await listPublicPageV4Handler(ctx as never, {
cursor: first.nextCursor!,
officialOnly: true,
sort: "newest",
numItems: 1,
});
expect(first.nextCursor).toMatch(/^skillofficial:/);
expect(second.nextCursor).toBeNull();
expect(getPageMock.mock.calls[1]?.[1]).toMatchObject({
table: "curatedSkillSearchDigest",
startIndexKey: indexKey,
startInclusive: false,
});
});
it("keeps pre-deploy Official cursors on the legacy projection", async () => {
const indexKey = [undefined, 2, 2, "skillSearchDigest:official"];
const legacyOfficialCursor = cursorForIndex("by_active_created", [
{ __undef: 1 },
2,
2,
"skillSearchDigest:official",
]);
await listPublicPageV4Handler({} as never, {
cursor: legacyOfficialCursor,
officialOnly: true,
sort: "newest",
numItems: 1,
});
expect(getPageMock).toHaveBeenCalledTimes(1);
expect(getPageMock.mock.calls[0]?.[1]).toMatchObject({
table: "skillSearchDigest",
startIndexKey: indexKey,
startInclusive: false,
});
});
it("preserves the Official creation cutoff for non-creation sorts", async () => {
const recent = makeSearchDigest({
skillId: "skills:recent-official",
slug: "recent-official",
badges: { official: { byUserId: "users:admin", at: 2 } },
createdAt: 200,
updatedAt: 1,
});
const old = makeSearchDigest({
skillId: "skills:old-official",
slug: "old-official",
badges: { official: { byUserId: "users:admin", at: 1 } },
createdAt: 50,
updatedAt: 2,
});
getPageMock.mockResolvedValueOnce({
page: [old, recent],
hasMore: false,
indexKeys: [
[undefined, old.updatedAt, old._id],
[undefined, recent.updatedAt, recent._id],
],
});
const result = await listPublicPageV4Handler(
makeOfficialFirstCategoryCtx([recent, old]) as never,
{
officialOnly: true,
createdAfter: 100,
sort: "updated",
numItems: 10,
},
);
expect(
(result.page as Array<{ skill: { slug: string } }>).map((entry) => entry.skill.slug),
).toEqual(["recent-official"]);
});
it("starts ascending Official creation scans at the requested cutoff", async () => {
await listPublicPageV4Handler({} as never, {
officialOnly: true,
createdAfter: 100,
sort: "newest",
dir: "asc",
numItems: 10,
});
expect(getPageMock).toHaveBeenCalledTimes(1);
expect(getPageMock.mock.calls[0]?.[1]).toMatchObject({
table: "curatedSkillSearchDigest",
index: "by_active_created",
startIndexKey: [undefined, 100],
startInclusive: true,
});
});
it("ends descending New pagination when the creation window boundary is reached", async () => {
const recent = makeSearchDigest({
skillId: "skills:recent",
+74 -4
View File
@@ -5252,6 +5252,7 @@ type PublicSkillListPage = {
nextCursor: string | null;
};
const OFFICIAL_FIRST_SKILL_CATEGORY_CURSOR_PREFIX = "skillofficialfirst:";
const OFFICIAL_SKILL_CURSOR_PREFIX = "skillofficial:";
const SORT_INDEX_FIELD_COUNTS: Record<PublicListSort, number> = {
recommended: 3,
@@ -5429,6 +5430,21 @@ function decodeOfficialFirstSkillCategoryCursor(
}
}
function encodeOfficialSkillCursor(cursor: string) {
return `${OFFICIAL_SKILL_CURSOR_PREFIX}${cursor}`;
}
function decodeOfficialSkillCursor(raw: string | undefined) {
if (!raw) return { projection: "curated" as const, cursor: null };
if (!raw.startsWith(OFFICIAL_SKILL_CURSOR_PREFIX)) {
// Untagged cursors were emitted by the legacy projection. Keep that session on
// the same table; fresh curated sessions tag every continuation cursor.
return { projection: "legacy" as const, cursor: raw };
}
const cursor = raw.slice(OFFICIAL_SKILL_CURSOR_PREFIX.length);
return { projection: "curated" as const, cursor: cursor || null };
}
/**
* V4 of listPublicPage using convex-helpers `getPage()` for deterministic,
* cacheable cursors. Two users requesting the same page produce identical
@@ -5476,6 +5492,7 @@ export const listPublicPageV4 = query({
? decodeOfficialFirstSkillCategoryCursor(args.cursor)
: null;
const publicListCursor = args.cursor;
const officialCursor = args.officialOnly ? decodeOfficialSkillCursor(args.cursor) : null;
const requestedSort = normalizePublicListSort(args.sort);
const dir = resolvePublicListDir(requestedSort, args.dir);
const numItems = clampInt(args.numItems ?? 25, 1, MAX_PUBLIC_LIST_LIMIT);
@@ -5538,6 +5555,26 @@ export const listPublicPageV4 = query({
});
}
if (args.officialOnly && officialCursor?.projection === "curated") {
const result = await listCuratedSkillPage(ctx, {
cursor: officialCursor.cursor,
sort: requestedSort,
dir,
numItems,
topic,
categorySlug,
categoryKeywords,
excludeCategoryKeywords,
nonSuspiciousOnly: args.nonSuspiciousOnly ?? false,
officialOnly: true,
createdAfter: args.createdAfter,
});
return {
...result,
nextCursor: result.nextCursor ? encodeOfficialSkillCursor(result.nextCursor) : null,
};
}
if (topic && !officialFirstCursor) {
return await listSkillTopicFilteredPage(ctx, {
cursor: publicListCursor,
@@ -7228,9 +7265,15 @@ type OfficialFirstSkillCategoryPageOptions = {
nonSuspiciousOnly: boolean;
};
async function listCuratedSkillCategoryPage(
type CuratedSkillPageOptions = Omit<OfficialFirstSkillCategoryPageOptions, "categorySlug"> & {
categorySlug: ServerSkillCategorySlug | null;
officialOnly?: boolean;
createdAfter?: number;
};
async function listCuratedSkillPage(
ctx: QueryCtx,
opts: OfficialFirstSkillCategoryPageOptions & { cursor: string | null },
opts: CuratedSkillPageOptions & { cursor: string | null },
): Promise<PublicSkillListPage> {
const indexName = opts.nonSuspiciousOnly
? NONSUSPICIOUS_CURATED_SORT_INDEXES[opts.sort]
@@ -7245,7 +7288,11 @@ async function listCuratedSkillCategoryPage(
allowLegacyArray: false,
});
const items: PublicSkillEntry[] = [];
let scanCursor = decodedCursor ?? eqPrefix;
const ascendingCreatedAfterCursor: IndexKey | null =
opts.sort === "newest" && opts.dir === "asc" && typeof opts.createdAfter === "number"
? [...eqPrefix, opts.createdAfter]
: null;
let scanCursor = decodedCursor ?? ascendingCreatedAfterCursor ?? eqPrefix;
let scanInclusive = !decodedCursor;
let hasMore = false;
let nextCursor: string | null = null;
@@ -7279,6 +7326,15 @@ async function listCuratedSkillCategoryPage(
const curatedDigest = result.page[index];
const cursor = result.indexKeys[index];
if (
opts.sort === "newest" &&
opts.dir === "desc" &&
typeof opts.createdAfter === "number" &&
curatedDigest.createdAt < opts.createdAfter
) {
return { page: items, hasMore: false, nextCursor: null };
}
if (
(typeof opts.createdAfter !== "number" || curatedDigest.createdAt >= opts.createdAfter) &&
digestPassesPublicListFilters(curatedDigest, {
topic: opts.topic,
categorySlug: opts.categorySlug,
@@ -7286,6 +7342,8 @@ async function listCuratedSkillCategoryPage(
excludeCategoryKeywords: opts.excludeCategoryKeywords,
})
) {
// The compact projection contains official and highlighted rows but omits badge kind,
// so hydrate before applying officialOnly and appending a result.
const digest = await ctx.db
.query("skillSearchDigest")
.withIndex("by_skill", (q) => q.eq("skillId", curatedDigest.skillId))
@@ -7293,6 +7351,8 @@ async function listCuratedSkillCategoryPage(
if (
digest &&
isCuratedSkillDigest(digest) &&
(!opts.officialOnly || isSkillCatalogOfficial(digest)) &&
(typeof opts.createdAfter !== "number" || digest.createdAt >= opts.createdAfter) &&
(!opts.nonSuspiciousOnly || !digest.isSuspicious) &&
digestPassesPublicListFilters(digest, {
topic: opts.topic,
@@ -7306,6 +7366,16 @@ async function listCuratedSkillCategoryPage(
}
}
if (items.length >= opts.numItems) {
const nextDigest = result.page[index + 1];
if (
opts.sort === "newest" &&
opts.dir === "desc" &&
typeof opts.createdAfter === "number" &&
nextDigest &&
nextDigest.createdAt < opts.createdAfter
) {
return { page: items, hasMore: false, nextCursor: null };
}
hasMore = result.hasMore || index < result.page.length - 1;
nextCursor = hasMore ? encodeIndexKey(indexName, cursor) : null;
return { page: items, hasMore, nextCursor };
@@ -7432,7 +7502,7 @@ async function listOfficialFirstSkillCategoryPage(
): Promise<PublicSkillListPage> {
const items: PublicSkillEntry[] = [];
if (opts.state.phase === "curated") {
const curatedPage = await listCuratedSkillCategoryPage(ctx, {
const curatedPage = await listCuratedSkillPage(ctx, {
...opts,
cursor: opts.state.cursor,
});