Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions apps/sim/app/workspace/[workspaceId]/files/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { Suspense } from 'react'
import { dehydrate, HydrationBoundary } from '@tanstack/react-query'
import type { Metadata } from 'next'
import { getQueryClient } from '@/app/_shell/providers/get-query-client'
import { prefetchFilesBrowser } from '@/app/workspace/[workspaceId]/files/prefetch'
import { Files } from './files'
import FilesLoading from './loading'

Expand All @@ -15,10 +18,17 @@ export const metadata: Metadata = {
* table headers) so a suspend never shows a blank frame; the route-level
* `loading.tsx` covers the navigation/chunk-load transition the same way.
*/
export default function FilesPage() {
export default async function FilesPage({ params }: { params: Promise<{ workspaceId: string }> }) {
const { workspaceId } = await params

const queryClient = getQueryClient()
await prefetchFilesBrowser(queryClient, workspaceId)

return (
<Suspense fallback={<FilesLoading />}>
<Files />
</Suspense>
<HydrationBoundary state={dehydrate(queryClient)}>
<Suspense fallback={<FilesLoading />}>
<Files />
</Suspense>
</HydrationBoundary>
)
}
43 changes: 43 additions & 0 deletions apps/sim/app/workspace/[workspaceId]/files/prefetch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import type { QueryClient } from '@tanstack/react-query'
import type { WorkspaceFileFolderApi } from '@/lib/api/contracts/workspace-file-folders'
import type { ListWorkspaceFilesResponse } from '@/lib/api/contracts/workspace-files'
import { prefetchInternalJson } from '@/app/workspace/[workspaceId]/lib/prefetch-internal-fetch'
import { workspaceFileFolderKeys } from '@/hooks/queries/workspace-file-folders'
import { workspaceFilesKeys } from '@/hooks/queries/workspace-files'

/**
* Prefetches the Files browser's two lists — workspace files and file folders —
* under the same query keys their client hooks (`useWorkspaceFiles`,
* `useWorkspaceFileFolders`) use (scope `active`), so the browser paints
* populated on first render.
*
* Both payloads carry `Date` fields, so they go through their routes and cache
* the serialized wire shape — see {@link prefetchInternalJson}.
*/
export async function prefetchFilesBrowser(
queryClient: QueryClient,
workspaceId: string
): Promise<void> {
await Promise.all([
queryClient.prefetchQuery({
queryKey: workspaceFilesKeys.list(workspaceId, 'active'),
queryFn: async () => {
const data = await prefetchInternalJson<ListWorkspaceFilesResponse>(
`/api/workspaces/${workspaceId}/files?scope=active`
)
return data.success ? data.files : []
},
staleTime: 30 * 1000,
}),
queryClient.prefetchQuery({
queryKey: workspaceFileFolderKeys.list(workspaceId, 'active'),
queryFn: async () => {
const data = await prefetchInternalJson<{ folders?: WorkspaceFileFolderApi[] }>(
`/api/workspaces/${workspaceId}/files/folders?scope=active`
)
return data.folders ?? []
},
Comment thread
waleedlatif1 marked this conversation as resolved.
staleTime: 30 * 1000,
}),
])
}
20 changes: 16 additions & 4 deletions apps/sim/app/workspace/[workspaceId]/home/page.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,30 @@
import { Suspense } from 'react'
import { dehydrate, HydrationBoundary } from '@tanstack/react-query'
import type { Metadata } from 'next'
import { getSession } from '@/lib/auth'
import { getQueryClient } from '@/app/_shell/providers/get-query-client'
import { prefetchHomeLists } from '@/app/workspace/[workspaceId]/home/prefetch'
import { Home } from './home'
import { HomeFallback } from './home-fallback'

export const metadata: Metadata = {
title: 'New chat',
}

export default async function HomePage() {
export default async function HomePage({ params }: { params: Promise<{ workspaceId: string }> }) {
const { workspaceId } = await params

const queryClient = getQueryClient()
const listsPrefetch = prefetchHomeLists(queryClient, workspaceId)

const session = await getSession()
await listsPrefetch

return (
<Suspense fallback={<HomeFallback />}>
<Home userName={session?.user?.name} userId={session?.user?.id} />
</Suspense>
<HydrationBoundary state={dehydrate(queryClient)}>
<Suspense fallback={<HomeFallback />}>
<Home userName={session?.user?.name} userId={session?.user?.id} />
</Suspense>
</HydrationBoundary>
)
}
48 changes: 48 additions & 0 deletions apps/sim/app/workspace/[workspaceId]/home/prefetch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import type { QueryClient } from '@tanstack/react-query'
import type { FolderApi } from '@/lib/api/contracts'
import type { ListWorkspaceFilesResponse } from '@/lib/api/contracts/workspace-files'
import { prefetchInternalJson } from '@/app/workspace/[workspaceId]/lib/prefetch-internal-fetch'
import { FOLDER_LIST_STALE_TIME, mapFolder } from '@/hooks/queries/folders'
import { folderKeys } from '@/hooks/queries/utils/folder-keys'
import { workspaceFilesKeys } from '@/hooks/queries/workspace-files'

/**
* Prefetches the home page's secondary lists — folders and workspace files —
* under the same query keys their client hooks (`useFolders`,
* `useWorkspaceFiles`) use, so the home view paints populated on first render.
*
* The workflow list (`workflowKeys.list(ws, 'active')`) is already hydrated by
* the workspace sidebar prefetch and is intentionally not repeated here.
*
* Folders are fetched through the route and mapped with the same `mapFolder`
* the hook applies, matching its cached shape (string dates → `Date`). Files
* carry `Date` fields, so they go through the route and cache the serialized
* wire shape — see {@link prefetchInternalJson}.
*/
export async function prefetchHomeLists(
queryClient: QueryClient,
workspaceId: string
): Promise<void> {
await Promise.all([
queryClient.prefetchQuery({
queryKey: folderKeys.list(workspaceId, 'active'),
queryFn: async () => {
const { folders } = await prefetchInternalJson<{ folders?: FolderApi[] }>(
`/api/folders?workspaceId=${workspaceId}&scope=active`
)
return (folders ?? []).map(mapFolder)
},
staleTime: FOLDER_LIST_STALE_TIME,
}),
queryClient.prefetchQuery({
queryKey: workspaceFilesKeys.list(workspaceId, 'active'),
queryFn: async () => {
const data = await prefetchInternalJson<ListWorkspaceFilesResponse>(
`/api/workspaces/${workspaceId}/files?scope=active`
)
return data.success ? data.files : []
},
staleTime: 30 * 1000,
}),
])
}
20 changes: 19 additions & 1 deletion apps/sim/app/workspace/[workspaceId]/knowledge/page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,26 @@
import { dehydrate, HydrationBoundary } from '@tanstack/react-query'
import type { Metadata } from 'next'
import { getQueryClient } from '@/app/_shell/providers/get-query-client'
import { prefetchKnowledgeBases } from '@/app/workspace/[workspaceId]/knowledge/prefetch'
import { Knowledge } from './knowledge'

export const metadata: Metadata = {
title: 'Knowledge Base',
}

export default Knowledge
export default async function KnowledgePage({
params,
}: {
params: Promise<{ workspaceId: string }>
}) {
const { workspaceId } = await params

const queryClient = getQueryClient()
await prefetchKnowledgeBases(queryClient, workspaceId)

return (
<HydrationBoundary state={dehydrate(queryClient)}>
<Knowledge />
</HydrationBoundary>
)
}
28 changes: 28 additions & 0 deletions apps/sim/app/workspace/[workspaceId]/knowledge/prefetch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { QueryClient } from '@tanstack/react-query'
import type { KnowledgeBaseData } from '@/lib/api/contracts/knowledge'
import { prefetchInternalJson } from '@/app/workspace/[workspaceId]/lib/prefetch-internal-fetch'
import { knowledgeKeys } from '@/hooks/queries/kb/knowledge'

/**
* Prefetches the workspace's knowledge-bases list under the same query key the
* client `useKnowledgeBasesQuery` hook uses (scope `active`), so the list paints
* populated on first render.
*
* The list carries `Date` fields, so it goes through the `/api/knowledge` route
* and caches the serialized wire shape — see {@link prefetchInternalJson}.
*/
export async function prefetchKnowledgeBases(
queryClient: QueryClient,
workspaceId: string
): Promise<void> {
await queryClient.prefetchQuery({
queryKey: knowledgeKeys.list(workspaceId, 'active'),
queryFn: async () => {
const result = await prefetchInternalJson<{ data: KnowledgeBaseData[] }>(
`/api/knowledge?workspaceId=${workspaceId}&scope=active`
)
return result.data
},
staleTime: 60 * 1000,
})
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { headers } from 'next/headers'
import { getInternalApiBaseUrl } from '@/lib/core/utils/urls'

/**
* Server-side GET against an internal `/api` route, forwarding the incoming
* request's cookie so the route authenticates as the current user.
*
* List prefetches go through the route (rather than the data layer) when the
* payload carries `Date` fields: `NextResponse.json` serializes them to the
* string wire shape the client caches via `requestJson`, so the
* server-hydrated entry byte-matches the client-fetched one through
* dehydration. Calling the data layer directly would cache raw `Date` objects
* and drift from that wire shape. Mirrors the settings/subscription prefetch.
*/
export async function prefetchInternalJson<T>(path: string): Promise<T> {
const cookie = (await headers()).get('cookie')
// boundary-raw-fetch: server-side RSC prefetch forwarding the session cookie to an internal API route; requestJson is client-only and cannot run here
const response = await fetch(`${getInternalApiBaseUrl()}${path}`, {
headers: cookie ? { cookie } : {},
})
Comment thread
waleedlatif1 marked this conversation as resolved.
if (!response.ok) {
throw new Error(`Prefetch failed for ${path}: ${response.status}`)
}
return response.json() as Promise<T>
}
Loading
Loading