fix(web): satisfy vp check for marketplace catalog prefetch

PR style CI failed oxfmt on the catalog preview files. Split prefetch
out of the HydrateQueryClient module so the component file only exports
a component, and stop asserting extra list fields as Plugin.
This commit is contained in:
CodingOnStar 2026-09-03 14:18:23 +08:00
parent ebccd86338
commit 0584b26883
8 changed files with 84 additions and 69 deletions

View File

@ -24,6 +24,9 @@ vi.mock('../home/banners', async (importOriginal) => {
vi.mock('../hydration-server', () => ({
HydrateQueryClient: ({ children }: { children: ReactNode }) => children,
}))
vi.mock('../prefetch-marketplace-dehydrated-state', () => ({
prefetchMarketplaceDehydratedState: vi.fn().mockResolvedValue(undefined),
}))
@ -58,11 +61,7 @@ describe('Marketplace server entry', () => {
const { default: Marketplace } = await import('../index')
const element = await Marketplace({ variant: 'home' })
render(
<QueryClientProvider client={new QueryClient()}>
{element}
</QueryClientProvider>,
)
render(<QueryClientProvider client={new QueryClient()}>{element}</QueryClientProvider>)
expect(screen.getByText('Server banners: 1')).toBeInTheDocument()
expect(mockGetLocaleOnServer).toHaveBeenCalledOnce()
@ -70,7 +69,8 @@ describe('Marketplace server entry', () => {
})
it('starts catalog prefetch without waiting for banners to finish', async () => {
const { prefetchMarketplaceDehydratedState } = await import('../hydration-server')
const { prefetchMarketplaceDehydratedState } =
await import('../prefetch-marketplace-dehydrated-state')
let resolveBanners: (banners: PluginBanner[]) => void = () => {}
mockGetLocaleOnServer.mockResolvedValue('en-US')
mockFetchPluginBanners.mockImplementation(

View File

@ -345,7 +345,14 @@ describe('getMarketplaceCollectionsAndPlugins', () => {
mockCollections.mockResolvedValueOnce({
data: {
collections: [
{ name: 'featured', label: {}, description: {}, rule: '', created_at: '', updated_at: '' },
{
name: 'featured',
label: {},
description: {},
rule: '',
created_at: '',
updated_at: '',
},
],
},
})
@ -477,7 +484,7 @@ describe('toListPlugin', () => {
privacy_options: 'all',
readme_meta: { available_languages: ['en_US'] },
endpoint: { settings: [{ name: 'api_key' }] },
} as Plugin
} as unknown as Plugin
const listed = toListPlugin(plugin)

View File

@ -1,52 +1,7 @@
import type { SearchParams } from 'nuqs/server'
import type { MarketplaceSearchParams } from './search-params'
import type { DehydratedState } from '@tanstack/react-query'
import { dehydrate, HydrationBoundary } from '@tanstack/react-query'
import { createLoader } from 'nuqs/server'
import { getQueryClient } from '@/app/get-query-client'
import { marketplaceQuery } from '@/service/client'
import { PLUGIN_CATEGORY_WITH_COLLECTIONS } from './constants'
import { getMarketplacePluginsInfiniteQueryOptions } from './query-options'
import {
getMarketplacePluginsSearchParams,
marketplaceSearchParamsParsers,
shouldSearchMarketplacePlugins,
} from './search-params'
import { withinServerBudget } from './server-budget'
import { getCollectionsParams, getMarketplaceCollectionsAndPlugins } from './utils'
// The server side logic should move to marketplace's codebase so that we can get rid of Next.js
export async function prefetchMarketplaceDehydratedState(searchParams?: Promise<SearchParams>) {
if (!searchParams) {
return
}
const loadSearchParams = createLoader(marketplaceSearchParamsParsers)
const params: MarketplaceSearchParams = await loadSearchParams(searchParams)
const queryClient = getQueryClient()
if (shouldSearchMarketplacePlugins(params)) {
await withinServerBudget(
queryClient.prefetchInfiniteQuery(
getMarketplacePluginsInfiniteQueryOptions(getMarketplacePluginsSearchParams(params)),
),
)
return dehydrate(queryClient)
}
if (!PLUGIN_CATEGORY_WITH_COLLECTIONS.has(params.category)) return
await withinServerBudget(
queryClient.prefetchQuery({
queryKey: marketplaceQuery.collections.queryKey({
input: { query: getCollectionsParams(params.category) },
}),
queryFn: () => getMarketplaceCollectionsAndPlugins(getCollectionsParams(params.category)),
}),
)
return dehydrate(queryClient)
}
import type { SearchParams } from 'nuqs/server'
import { HydrationBoundary } from '@tanstack/react-query'
import { prefetchMarketplaceDehydratedState } from './prefetch-marketplace-dehydrated-state'
export async function HydrateQueryClient({
searchParams,

View File

@ -3,7 +3,8 @@ import type { SearchParams } from 'nuqs'
import type { MarketplaceViewProps } from './view'
import { getLocaleOnServer } from '@/i18n-config/server'
import { fetchPluginBanners } from './home/banners'
import { HydrateQueryClient, prefetchMarketplaceDehydratedState } from './hydration-server'
import { HydrateQueryClient } from './hydration-server'
import { prefetchMarketplaceDehydratedState } from './prefetch-marketplace-dehydrated-state'
import { withinServerBudget } from './server-budget'
import { MarketplaceView } from './view'

View File

@ -0,0 +1,46 @@
import type { SearchParams } from 'nuqs/server'
import type { MarketplaceSearchParams } from './search-params'
import { dehydrate } from '@tanstack/react-query'
import { createLoader } from 'nuqs/server'
import { getQueryClient } from '@/app/get-query-client'
import { marketplaceQuery } from '@/service/client'
import { PLUGIN_CATEGORY_WITH_COLLECTIONS } from './constants'
import { getMarketplacePluginsInfiniteQueryOptions } from './query-options'
import {
getMarketplacePluginsSearchParams,
marketplaceSearchParamsParsers,
shouldSearchMarketplacePlugins,
} from './search-params'
import { withinServerBudget } from './server-budget'
import { getCollectionsParams, getMarketplaceCollectionsAndPlugins } from './utils'
export async function prefetchMarketplaceDehydratedState(searchParams?: Promise<SearchParams>) {
if (!searchParams) {
return
}
const loadSearchParams = createLoader(marketplaceSearchParamsParsers)
const params: MarketplaceSearchParams = await loadSearchParams(searchParams)
const queryClient = getQueryClient()
if (shouldSearchMarketplacePlugins(params)) {
await withinServerBudget(
queryClient.prefetchInfiniteQuery(
getMarketplacePluginsInfiniteQueryOptions(getMarketplacePluginsSearchParams(params)),
),
)
return dehydrate(queryClient)
}
if (!PLUGIN_CATEGORY_WITH_COLLECTIONS.has(params.category)) return
await withinServerBudget(
queryClient.prefetchQuery({
queryKey: marketplaceQuery.collections.queryKey({
input: { query: getCollectionsParams(params.category) },
}),
queryFn: () => getMarketplaceCollectionsAndPlugins(getCollectionsParams(params.category)),
}),
)
return dehydrate(queryClient)
}

View File

@ -5,6 +5,9 @@ import { standaloneMarketplaceServer } from '../server'
vi.mock('../../index', () => ({ default: () => null }))
vi.mock('../../hydration-server', () => ({
HydrateQueryClient: () => null,
}))
vi.mock('../../prefetch-marketplace-dehydrated-state', () => ({
prefetchMarketplaceDehydratedState: vi.fn(),
}))

View File

@ -13,8 +13,9 @@ import {
sortCreatorCreations,
toPublisherSortQuery,
} from '../creator-profile/model'
import { HydrateQueryClient, prefetchMarketplaceDehydratedState } from '../hydration-server'
import { HydrateQueryClient } from '../hydration-server'
import Marketplace from '../index'
import { prefetchMarketplaceDehydratedState } from '../prefetch-marketplace-dehydrated-state'
import { SERVER_PREFETCH_BUDGET_MS, withinServerBudget } from '../server-budget'
export const standaloneMarketplaceServer = {

View File

@ -170,17 +170,19 @@ describe('marketplace template discovery', () => {
})
mocks.templateCollectionTemplates.mockResolvedValue({
data: {
templates: [{
id: 'template-1',
template_name: 'Inbox',
readme: '# long',
review_comment: 'ship it',
dsl_file_key: 'dsl.yml',
partner_link: 'https://example.com',
asset_files: [{ name: 'a' }],
asset_tree_nodes: [{ path: '/' }],
dsl_raw_file_key: 'raw.yml',
}],
templates: [
{
id: 'template-1',
template_name: 'Inbox',
readme: '# long',
review_comment: 'ship it',
dsl_file_key: 'dsl.yml',
partner_link: 'https://example.com',
asset_files: [{ name: 'a' }],
asset_tree_nodes: [{ path: '/' }],
dsl_raw_file_key: 'raw.yml',
},
],
},
})