@vexcms/next 0.1.0-alpha.26 → 0.1.0-alpha.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/HydrationBoundaryClient.d.ts +17 -0
  2. package/dist/HydrationBoundaryClient.d.ts.map +1 -0
  3. package/dist/HydrationBoundaryClient.js +9 -0
  4. package/dist/HydrationBoundaryClient.js.map +1 -0
  5. package/dist/NextAdminLayout.d.ts +21 -13
  6. package/dist/NextAdminLayout.d.ts.map +1 -1
  7. package/dist/NextAdminLayout.js +1 -1
  8. package/dist/NextAdminLayoutClient.d.ts +10 -4
  9. package/dist/NextAdminLayoutClient.d.ts.map +1 -1
  10. package/dist/NextAdminLayoutClient.js +104 -7
  11. package/dist/NextAdminLayoutClient.js.map +1 -1
  12. package/dist/NextAdminPage.d.ts +52 -5
  13. package/dist/NextAdminPage.d.ts.map +1 -1
  14. package/dist/NextAdminPage.js +1 -1
  15. package/dist/auth/VexAdminAuthAdapterContext.d.ts +66 -0
  16. package/dist/auth/VexAdminAuthAdapterContext.d.ts.map +1 -0
  17. package/dist/auth/index.d.ts +2 -0
  18. package/dist/auth/index.d.ts.map +1 -0
  19. package/dist/auth/index.js +12 -0
  20. package/dist/auth/index.js.map +1 -0
  21. package/dist/chunk-JB3GMZPF.js +525 -0
  22. package/dist/chunk-JB3GMZPF.js.map +1 -0
  23. package/dist/chunk-NE2VUFS2.js +15 -0
  24. package/dist/chunk-NE2VUFS2.js.map +1 -0
  25. package/dist/chunk-T7DNJLSX.js +21 -0
  26. package/dist/chunk-T7DNJLSX.js.map +1 -0
  27. package/dist/index.js +2 -2
  28. package/dist/navigation/index.d.ts +3 -0
  29. package/dist/navigation/index.d.ts.map +1 -0
  30. package/dist/navigation/useNextNavigate.d.ts +19 -0
  31. package/dist/navigation/useNextNavigate.d.ts.map +1 -0
  32. package/dist/navigation/useNextNavigationBlocker.d.ts +32 -0
  33. package/dist/navigation/useNextNavigationBlocker.d.ts.map +1 -0
  34. package/dist/serverQueryClient.d.ts +48 -0
  35. package/dist/serverQueryClient.d.ts.map +1 -0
  36. package/package.json +11 -4
  37. package/dist/chunk-DDH2TJTP.js +0 -195
  38. package/dist/chunk-DDH2TJTP.js.map +0 -1
  39. package/dist/chunk-UMJTAD5A.js +0 -22
  40. package/dist/chunk-UMJTAD5A.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/NextAdminPage.tsx","../src/serverQueryClient.ts"],"sourcesContent":["import type {\n VexApiAuth,\n VexConfig,\n VexMediaDocument,\n VexStoredView,\n VexWhere,\n} from \"@vexcms/core\";\nimport type { ReactNode } from \"react\";\nimport type { QueryClient } from \"@tanstack/react-query\";\n\nimport {\n ADMIN_COOKIE_NAME,\n adminCollectionCookieName,\n appendLivePreviewParams,\n buildGetArgs,\n buildGlobalGetArgs,\n buildListViewFindQueryArgs,\n buildListViewSearchQueryArgs,\n collectListViewFilterRelationshipValues,\n CRUD_ACTIONS,\n parseListViewUrlState,\n parseListViewConfigCookies,\n parseListViewSkeletonRowCountCookie,\n parseLocalSavedListViewsIndexCookie,\n PERMISSION_SCOPES,\n readLivePreviewLayoutCookie,\n readLivePreviewPanelCookie,\n resolveFieldPermissions,\n resolveListViewQueryState,\n resolveLivePreviewSettings,\n resolveListViewSearchField,\n vexConvexApi,\n} from \"@vexcms/core\";\nimport {\n AdminNotFound,\n AdminTopNav,\n adminHref,\n CollectionEditView,\n CollectionListView,\n CollectionsIndexView,\n DashboardView,\n GlobalEditView,\n GlobalsListView,\n MediaCollectionEditView,\n MediaCollectionListView,\n VexAuthProvider,\n} from \"@vexcms/react\";\nimport { dehydrate } from \"@tanstack/react-query\";\nimport { fetchQuery } from \"convex/nextjs\";\nimport { cookies } from \"next/headers\";\nimport { notFound } from \"next/navigation\";\n\nimport { HydrationBoundaryClient } from \"./HydrationBoundaryClient\";\nimport { convexServerQueryKey, getServerQueryClient } from \"./serverQueryClient\";\n\n/**\n * Wraps one branch's resolved view content with the shell every `NextAdminPage` branch shares\n * (top-bar move, shell-streaming follow-up, developer-approved): the branch's own `auth`\n * context, its `HydrationBoundary` (so `AdminTopNav`'s own document-title crumb — a TRUE\n * descendant now, not a layout-level sibling — shares the exact same dehydrated cache the\n * view's own data component reads, required for it to resolve correctly on first paint), the\n * bar itself, and the padded content wrapper (`AdminLayout.tsx`'s `<main>` no longer owns this\n * padding, since the bar needs to render full-bleed, flush with `<main>`'s own edges, to stay\n * visually identical to before the move).\n *\n * Called on EVERY branch, including ones with nothing of their own to prefetch (dashboard,\n * globals list, not-found messages) — `dehydrate()` on an empty/otherwise-unrelated\n * `QueryClient` is harmless, and keeps every branch structurally uniform.\n *\n * @param props.auth - The resolved `{ user, organization }` for this request.\n * @param props.activeSlug - Forwarded to `AdminTopNav` — the active collection/media-collection\n * slug, or `\"globals\"` for any globals route, or `undefined` on the dashboard/not-found.\n * @param props.activeDocID - Forwarded to `AdminTopNav` — the active document id (edit routes)\n * or global slug (global edit), or `undefined` otherwise.\n * @param props.queryClient - This branch's own `getServerQueryClient()` instance (shared with\n * whatever it already prefetched into, if anything).\n * @param props.children - The branch's own view content.\n * @returns The branch's full return value — `VexAuthProvider` → `HydrationBoundaryClient` →\n * `AdminTopNav` + padded content wrapper.\n */\nfunction renderAdminShell(props: {\n auth: VexApiAuth;\n activeSlug?: string;\n activeDocID?: string;\n queryClient: QueryClient;\n children: ReactNode;\n}) {\n return (\n <VexAuthProvider value={props.auth}>\n <HydrationBoundaryClient state={dehydrate(props.queryClient)}>\n <AdminTopNav activeSlug={props.activeSlug} activeDocID={props.activeDocID} />\n {/* `AdminLayout.tsx`'s own `<main>` carries no padding anymore — this wrapper owns\n it instead, so the bar above renders full-bleed against `<main>`'s own edges.\n `flex-1 min-h-0` (developer-reported fix, SUPERSEDING an earlier `h-full` attempt\n that caused a double-scroll regression): `<main>` is now `flex flex-col`\n (`AdminLayout.tsx`) specifically so the bar (fixed ~48px) and this wrapper divide\n its height correctly via flexbox, the same way `<main>` itself divides\n `SidebarInset`'s height with the sidebar. `h-full` (100%) ignored the bar's own\n space entirely — it measured 100% of `<main>`'s FULL height while the bar ALSO\n took space above it, overflowing `<main>`'s own bottom edge by the bar's height\n and creating an unwanted second (page-level) scrollbar. `flex-1 min-h-0` fixes\n this the same way `<main>` itself avoids it: `min-h-0` overrides flexbox's\n default `min-height: auto` (which would otherwise let this item grow to fit its\n own content, i.e. `CollectionEditForm`'s tall `AppForm`, instead of being clamped\n to its allotted share) — required for `AppForm`'s own\n `h-[calc(100%+1.5rem)]` to resolve against a correctly-clamped, definite height.\n A list view's own taller content still scrolls via `<main>`'s own\n `overflow-y-auto` (`overflow: visible` is this div's own default, so it never\n clips taller content, same as before this wrapper existed). */}\n <div className=\"min-h-0 flex-1 p-6 pt-0\">{props.children}</div>\n </HydrationBoundaryClient>\n </VexAuthProvider>\n );\n}\n\n/**\n * Resolves a `{ server }` preview URL for first paint.\n *\n * Two things the caller must not have to remember. First, the query only\n * answers for the `{ server }` form — the string and client forms resolve\n * synchronously in the browser and return `null` here, so the iframe falls back\n * to its own resolution. Second, the resolved URL is a plain public URL: it\n * needs `vexLivePreview=1` appended or the preview loads an ordinary page that\n * listens to nothing, which looks like \"live preview is broken\" with no error\n * anywhere. Appending it here keeps the server path identical to the client one.\n *\n * @param props.args - Query arguments identifying the document or global.\n * @param props.slug - Collection or global slug, for the temp-id params.\n * @param props.token - Auth token forwarded to Convex.\n * @returns The preview-ready URL, or `undefined` when the config resolves in\n * the browser instead.\n */\nasync function resolveInitialPreviewUrl(props: {\n args: Parameters<typeof fetchQuery<typeof vexConvexApi.livePreviewUrl>>[1];\n slug: string;\n token?: string;\n}): Promise<string | undefined> {\n const url = await fetchQuery(\n vexConvexApi.livePreviewUrl,\n props.args,\n props.token ? { token: props.token } : undefined,\n );\n if (!url) return undefined;\n\n return appendLivePreviewParams({ url, collectionSlug: props.slug });\n}\n\nlet warnedMissingAuth = false;\n\n/**\n * Warns ONCE, dev-only, when `config.access` is enabled but `NextAdminPage` received no\n * `auth` prop (Decision 44, revised) — the caller is about to be treated as anonymous\n * (fail-closed, P-007's default-deny) rather than allow-all, which can look like \"nothing\n * shows up, for no obvious reason\" with no other signal. Silent when `access` is absent or\n * `{ enabled: false }` — the allow-all case genuinely needs no `auth` at all, nothing to\n * warn about — and silent in production (an operator's own logging/monitoring is the right\n * place for a real misconfiguration alert there, not a framework `console.warn`).\n *\n * @param props.access - The resolved config's own `access`, or `undefined`.\n * @param props.authPromise - The `authPromise` prop `NextAdminPage` received, or `undefined`.\n */\nfunction warnIfAuthMissing(props: {\n access: VexConfig[\"access\"];\n authPromise: Promise<VexApiAuth> | undefined;\n}): void {\n if (props.authPromise !== undefined) return;\n if (!props.access || !props.access.enabled) return;\n if (warnedMissingAuth) return;\n if (process.env.NODE_ENV === \"production\") return;\n warnedMissingAuth = true;\n console.warn(\n \"[vexcms] NextAdminPage: config.access is enabled but no `authPromise` prop was passed. \" +\n \"Every caller is being treated as anonymous (fail-closed) until you pass \" +\n \"`authPromise={getUserOrg()}` from your own session source — see Decision 44 \" +\n \"(.agent/docs/specs/2026-10-26-list-view-overhaul/spec.md).\",\n );\n}\n\n/**\n * VexCMS admin page server component for Next.js.\n *\n * An `async` server component that routes by the `[[...slug]]` catch-all\n * params, prefetches Convex data via `fetchQuery`, and renders the correct\n * view component. Does **not** include a layout wrapper — `NextAdminLayout`\n * in `app/admin/layout.tsx` owns the persistent shell.\n *\n * **Route mapping:**\n * | `path` array | View |\n * |---|---|\n * | `[]` or undefined | `DashboardView` |\n * | `[collectionSlug]` | `CollectionListView` with preloaded docs |\n * | `[collectionSlug, \"new\"]` | `CollectionEditView` (empty form) |\n * | `[collectionSlug, documentId]` | `CollectionEditView` with preloaded doc |\n *\n * Takes `tokenPromise`/`authPromise`, not resolved values (perf fix, developer-binding): the\n * caller (`page.tsx`) starts both without awaiting them immediately, so `NextAdminPage`'s own\n * data fetches can begin as soon as `tokenPromise` resolves (what every `fetchQuery` call\n * actually needs) rather than waiting on the slower `authPromise` (which itself depends on\n * the token, plus one more Convex round trip). The caller's own page-level access gate\n * (`canAccessAdminPanel`) should `await authPromise` and redirect BEFORE rendering\n * `NextAdminPage` at all (Polish Decision 8, developer-reported — a generic page-level\n * skeleton rendered while the gate was still in flight made the loading state worse than no\n * gate-level fallback at all: shifts, row-selection spinners showing where real checkboxes\n * belong, skeletons with no matching settled element, content popping in unannounced).\n * `NextAdminPage` streams its own real chrome (title/tabs/toolbar/controls) immediately once\n * rendered and wraps only its OWN data-dependent regions (e.g. table rows) in their own\n * `<Suspense>` boundaries — no page-level fallback needed or wanted.\n *\n * @param props - Component props\n * @param props.config - The resolved server `VexConfig`, typically the default export of\n * `vex.config.server.ts` — not the client-safe `vex.config.ts`.\n * @param props.params - Next.js 15 async params `{ path?: string[] }`\n * @param props.tokenPromise - The in-flight (or already-resolved) session token (e.g.\n * `getToken()`), forwarded to every `fetchQuery` call for authenticated reads. Optional —\n * a project with no auth at all omits it, every `fetchQuery` call runs unauthenticated.\n * @param props.authPromise - The in-flight (or already-resolved — e.g. `Promise.resolve(auth)`\n * after the caller's own gate already awaited it) server-resolved caller (Decision 44),\n * OPTIONAL. Wraps the entire return in `VexAuthProvider` and narrows the collection list\n * route's search field by read permission. Omit it when `config.access` is absent or\n * disabled — nothing is restricted either way. An `access`-enabled project that omits it is\n * treated as an anonymous caller (fail-closed) with a one-time dev console warning.\n * @returns The appropriate admin view for the current URL path.\n *\n * @example\n * ```tsx\n * // app/admin/[[...slug]]/page.tsx\n * import { canAccessAdminPanel } from \"@vexcms/core\";\n * import { NextAdminPage } from \"@vexcms/next/server\";\n * import { redirect } from \"next/navigation\";\n * import { getToken, getUserOrg } from \"~/auth/server\";\n * import config from \"../../../../vex.config.server\";\n *\n * export default async function AdminPage({\n * params,\n * }: {\n * params: Promise<{ path?: string[] }>;\n * }) {\n * const token = await getToken();\n * if (!token) redirect(\"/auth/sign-in?redirectTo=/admin\");\n * const auth = await getUserOrg();\n * if (\n * !canAccessAdminPanel({ access: config.access, user: auth.user, organization: auth.organization })\n * ) {\n * redirect(\"/unauthorized\");\n * }\n * return (\n * <NextAdminPage\n * config={config}\n * params={params}\n * tokenPromise={Promise.resolve(token)}\n * authPromise={Promise.resolve(auth)}\n * />\n * );\n * }\n * ```\n */\nexport async function NextAdminPage(props: {\n config: VexConfig;\n params: Promise<{ path?: string[] }>;\n /**\n * Next.js's own `searchParams` prop, forwarded by the app route (`AdminPage`). Only the\n * collection list route reads it (`parseListViewUrlState`, Design Decision 40) — every other\n * route ignores it.\n */\n searchParams?: Promise<Record<string, string | string[] | undefined>>;\n tokenPromise?: Promise<string | undefined>;\n authPromise?: Promise<VexApiAuth>;\n}) {\n const { path = [] } = await props.params;\n const [section, collectionSlug, documentId] = path;\n const cookieStore = await cookies();\n\n warnIfAuthMissing({ access: props.config.access, authPromise: props.authPromise });\n const token = props.tokenPromise ? await props.tokenPromise : undefined;\n // Decision 44's fail-closed default: an unresolved caller is `{ user: null }`, never a\n // wildcard-allow stand-in. `hasPermission`/`resolveFieldPermissions`/`resolveAccessRule`\n // already treat an ABSENT or disabled `access` config as allow-all on their own (each\n // short-circuits on `!access || !access.enabled` before ever reading `user`), so this\n // one fallback is correct for BOTH cases: \"no access config at all\" (the `user` value\n // below is never consulted) and \"access enabled, `auth` omitted\" (P-007's default-deny\n // denies an anonymous `{ user: null }`, exactly as it would any other unauthenticated\n // caller). Normalized to a promise that ALWAYS resolves (never `undefined`) so every\n // branch below can simply `await authPromise` without its own fallback.\n const authPromise: Promise<VexApiAuth> = props.authPromise ?? Promise.resolve({ user: null });\n\n if (path.length === 0) {\n const auth = await authPromise;\n return renderAdminShell({\n auth,\n queryClient: getServerQueryClient(),\n children: <DashboardView />,\n });\n }\n\n if (section !== \"collections\" && section !== \"globals\") {\n notFound();\n }\n\n if (section === \"globals\") {\n if (!collectionSlug) {\n const auth = await authPromise;\n return renderAdminShell({\n auth,\n activeSlug: \"globals\",\n queryClient: getServerQueryClient(),\n children: <GlobalsListView />,\n });\n }\n const globalConfig = props.config.globals.find((g) => g.slug === collectionSlug);\n if (!globalConfig) {\n const auth = await authPromise;\n return renderAdminShell({\n auth,\n activeSlug: \"globals\",\n queryClient: getServerQueryClient(),\n children: (\n <AdminNotFound\n variant=\"global\"\n detail={`Global \"${collectionSlug}\" doesn't exist.`}\n backHref={adminHref({ type: \"globals\" }, props.config.basePath)}\n backLabel=\"Back to globals\"\n />\n ),\n });\n }\n const globalQueryClient = getServerQueryClient();\n const globalArgs = buildGlobalGetArgs({ global: globalConfig });\n // Awaited (developer-reported regression fix — reverts the shell-streaming follow-up's\n // un-awaited prefetch for this query specifically): the edit view's own primary document\n // is essential and cheap, so `GlobalEditView` renders the whole form at once from\n // `initialData` below — no whole-form Suspense fallback. Parallelized with\n // `authPromise`/`previewUrlPromise` below (not sequenced after them, polish fixes,\n // Decision 5 — this used to `await` here directly, THEN create `previewUrlPromise`\n // afterward, serializing two independent round trips end to end) — same total wait as\n // the un-awaited version cost in practice, since this was always the single slowest\n // piece. Still dehydrated into the cache too (using the SAME already-resolved value as\n // its `queryFn`, so this costs no extra round trip) for parity with every other prefetch\n // this page does and for any live re-subscription `GlobalEditViewData`'s own `useQuery`\n // establishes after mount.\n const globalDocPromise = fetchQuery(\n vexConvexApi.globals.get,\n globalArgs,\n token ? { token } : undefined,\n ).catch(() => null);\n const globalLivePreview = resolveLivePreviewSettings({\n config: props.config.admin.livePreview,\n kind: \"global\",\n slug: globalConfig.slug,\n admin: globalConfig.admin.livePreview,\n });\n const previewUrlPromise =\n globalLivePreview === undefined\n ? Promise.resolve(undefined)\n : resolveInitialPreviewUrl({\n args: { collection: globalConfig.slug, kind: \"global\", values: {} },\n slug: globalConfig.slug,\n token,\n });\n const [auth, initialPreviewUrl, globalDoc] = await Promise.all([\n authPromise,\n previewUrlPromise,\n globalDocPromise,\n ]);\n if (!globalDoc) {\n return renderAdminShell({\n auth,\n activeSlug: \"globals\",\n queryClient: globalQueryClient,\n children: (\n <AdminNotFound\n variant=\"document\"\n detail=\"This document doesn't exist, or you don't have access to view it.\"\n backHref={`${props.config.basePath}/globals`}\n backLabel=\"Back to Globals\"\n />\n ),\n });\n }\n // Awaited (same regression class as the `listViews`/media-edit prefetch fixes, polish\n // fixes) — `globalDoc` is already resolved by the `Promise.all` above, so this\n // prefetch's own `queryFn` replays an already-settled value and resolves on the next\n // microtask, but `prefetchQuery`'s own cache-status bookkeeping (marking the entry\n // \"success\", not \"pending\") still needs that tick — an un-awaited prefetch here left\n // the raw SSR HTML's dehydrated entry at `status: \"pending\"` even though `globalDoc`\n // was already known, which `GlobalEditViewData`'s own `useQuery` (same key) would\n // otherwise re-fetch on mount instead of reading the already-resolved value.\n await globalQueryClient\n .prefetchQuery({\n queryKey: convexServerQueryKey(vexConvexApi.globals.get, globalArgs),\n queryFn: () => globalDoc,\n })\n .catch(() => undefined);\n return renderAdminShell({\n auth,\n activeSlug: \"globals\",\n activeDocID: globalConfig.slug,\n queryClient: globalQueryClient,\n children: (\n <GlobalEditView\n global={globalConfig.slug}\n initialData={globalDoc}\n initialPreviewPanelOpen={\n globalLivePreview !== undefined &&\n readLivePreviewPanelCookie({\n cookieValue: cookieStore.get(ADMIN_COOKIE_NAME)?.value,\n defaultOpen: globalLivePreview.defaultOpen,\n })\n }\n initialPreviewPanelSize={readLivePreviewLayoutCookie({\n cookieValue: cookieStore.get(ADMIN_COOKIE_NAME)?.value,\n })}\n initialPreviewUrl={initialPreviewUrl}\n />\n ),\n });\n }\n\n if (!collectionSlug) {\n const auth = await authPromise;\n return renderAdminShell({\n auth,\n activeSlug: \"collections\",\n queryClient: getServerQueryClient(),\n children: <CollectionsIndexView />,\n });\n }\n\n const collection = props.config.collections.find((c) => c.slug === collectionSlug);\n const mediaCollection = props.config.mediaCollections.find((mc) => mc.slug === collectionSlug);\n\n if (!collection && !mediaCollection) {\n const auth = await authPromise;\n return renderAdminShell({\n auth,\n activeSlug: collectionSlug,\n queryClient: getServerQueryClient(),\n children: (\n <AdminNotFound\n variant=\"collection\"\n detail={`Collection \"${collectionSlug}\" doesn't exist.`}\n backHref={adminHref({ type: \"collections\" }, props.config.basePath)}\n backLabel=\"Back to collections\"\n />\n ),\n });\n }\n\n if (mediaCollection && documentId) {\n const mediaEditQueryClient = getServerQueryClient();\n const mediaEditArgs = buildGetArgs({ id: documentId, collection: mediaCollection });\n // Awaited (developer-reported regression fix — reverts the shell-streaming follow-up's\n // un-awaited prefetch for this query specifically): the edit view's own primary document\n // is essential and cheap, so `MediaCollectionEditView` renders the whole form at once\n // from `initialData` below — no whole-form Suspense fallback. Parallelized with\n // `authPromise` below (not sequenced after it, polish fixes, Decision 5 — this used to\n // `await authPromise` THEN `await fetchQuery(...)` one after the other, serializing two\n // independent round trips end to end). Still dehydrated into the cache too (using the\n // SAME already-resolved value as its `queryFn`, no extra round trip) for parity with\n // every other prefetch this page does and for any live re-subscription\n // `MediaCollectionEditViewData`'s own `useVexDocQuery` establishes.\n const mediaDocPromise = fetchQuery(\n vexConvexApi.get,\n mediaEditArgs,\n token ? { token } : undefined,\n ).catch(() => null) as Promise<VexMediaDocument | null>;\n const [auth, mediaDoc] = await Promise.all([authPromise, mediaDocPromise]);\n if (!mediaDoc) {\n return renderAdminShell({\n auth,\n activeSlug: mediaCollection.slug,\n queryClient: mediaEditQueryClient,\n children: (\n <AdminNotFound\n variant=\"document\"\n detail=\"This document doesn't exist, or you don't have access to view it.\"\n backHref={`${props.config.basePath}/collections/${mediaCollection.slug}`}\n backLabel={`Back to ${mediaCollection.labels.plural}`}\n />\n ),\n });\n }\n // Awaited (same regression class as the `listViews` prefetch fix above, polish fixes —\n // developer-reported pop-in/missing-crumb bug): `mediaDoc` is already resolved by the\n // `Promise.all` above, so this prefetch's own `queryFn` replays an already-settled\n // value and resolves on the next microtask — but `prefetchQuery`'s own cache-status\n // bookkeeping (marking the entry \"success\", not \"pending\") still needs that tick.\n // `AdminTopNavDocCrumb`'s `useSuspenseQuery` reads this EXACT key (`buildGetArgs`,\n // polish fixes Decision 5) — un-awaited, the raw SSR HTML captured the entry as\n // `status: \"pending\"` even though `mediaDoc` was already known, so the crumb never\n // showed on first paint (confirmed live via `curl`'d SSR HTML before this fix).\n await mediaEditQueryClient\n .prefetchQuery({\n queryKey: convexServerQueryKey(vexConvexApi.get, mediaEditArgs),\n queryFn: () => mediaDoc,\n })\n .catch(() => undefined);\n return renderAdminShell({\n auth,\n activeSlug: mediaCollection.slug,\n activeDocID: documentId,\n queryClient: mediaEditQueryClient,\n children: (\n <MediaCollectionEditView collection={mediaCollection.slug} documentId={documentId} initialData={mediaDoc} />\n ),\n });\n }\n\n if (mediaCollection) {\n const numItems = Math.max(\n mediaCollection.admin.view.state.serverPageSize,\n mediaCollection.admin.view.state.pageSize,\n );\n const auth = await authPromise;\n const mediaQueryClient = getServerQueryClient();\n const mediaArgs = buildListViewFindQueryArgs({\n collection: mediaCollection,\n where: undefined,\n sort: undefined,\n drafts: undefined,\n depth: 1,\n numItems,\n });\n // Streamed, not awaited (same shell-streaming pattern as the collection-list branch\n // below): `prefetchQuery` is called SYNCHRONOUSLY, right before `dehydrate()`, so the\n // in-flight promise is captured in the dehydrated cache rather than its (not-yet-\n // resolved) result.\n void mediaQueryClient\n .prefetchQuery({\n queryKey: convexServerQueryKey(vexConvexApi.findPaginated, mediaArgs),\n queryFn: () => fetchQuery(vexConvexApi.findPaginated, mediaArgs, token ? { token } : undefined),\n })\n .catch(() => undefined);\n return renderAdminShell({\n auth,\n activeSlug: mediaCollection.slug,\n queryClient: mediaQueryClient,\n children: <MediaCollectionListView collection={mediaCollection.slug} />,\n });\n }\n\n if (collection && documentId) {\n const collectionEditQueryClient = getServerQueryClient();\n const collectionEditArgs = buildGetArgs({ id: documentId, collection });\n // Awaited (developer-reported regression fix — reverts the shell-streaming follow-up's\n // un-awaited prefetch for this query specifically): the edit view's own primary document\n // is essential and cheap, so `CollectionEditView` renders the whole form at once from\n // `initialData` below — no whole-form Suspense fallback. Parallelized with\n // `authPromise`/`previewUrlPromise` below (not sequenced after them) — same total wait\n // as the un-awaited version cost in practice, since this was always the single slowest\n // piece. Still dehydrated into the cache too (using the SAME already-resolved value as\n // its `queryFn`, no extra round trip) for parity with every other prefetch this page\n // does and for any live re-subscription `CollectionEditViewData`'s own `useVexDocQuery`\n // establishes. Same args as before (`get` falls back to published-only for a caller\n // without `readDrafts`).\n const collectionDocPromise = fetchQuery(\n vexConvexApi.get,\n collectionEditArgs,\n token ? { token } : undefined,\n ).catch(() => null);\n const collectionLivePreview = resolveLivePreviewSettings({\n config: props.config.admin.livePreview,\n kind: \"collection\",\n slug: collection.slug,\n admin: collection.admin.livePreview,\n });\n const previewUrlPromise =\n collectionLivePreview === undefined\n ? Promise.resolve(undefined)\n : resolveInitialPreviewUrl({\n args: { collection: collection.slug, kind: \"collection\", documentId, values: {} },\n slug: collection.slug,\n token,\n });\n const [auth, initialPreviewUrl, currentDocument] = await Promise.all([\n authPromise,\n previewUrlPromise,\n collectionDocPromise,\n ]);\n if (!currentDocument) {\n return renderAdminShell({\n auth,\n activeSlug: collection.slug,\n queryClient: collectionEditQueryClient,\n children: (\n <AdminNotFound\n variant=\"document\"\n detail=\"This document doesn't exist, or you don't have access to view it.\"\n backHref={`${props.config.basePath}/collections/${collection.slug}`}\n backLabel={`Back to ${collection.labels.plural}`}\n />\n ),\n });\n }\n // Awaited (same regression class as the `listViews`/media-edit/global-edit prefetch\n // fixes, polish fixes — developer-reported pop-in/missing-crumb bug):\n // `currentDocument` is already resolved above, so this prefetch's own `queryFn`\n // replays an already-settled value — but `prefetchQuery`'s own cache-status\n // bookkeeping (marking the entry \"success\", not \"pending\") still needs that tick.\n // `AdminTopNavDocCrumb`'s `useSuspenseQuery` reads this EXACT key (`buildGetArgs`,\n // polish fixes Decision 5) — un-awaited, the raw SSR HTML's dehydrated entry stayed\n // `status: \"pending\"` even though `currentDocument` was already known, so the crumb\n // never showed on first paint.\n await collectionEditQueryClient\n .prefetchQuery({\n queryKey: convexServerQueryKey(vexConvexApi.get, collectionEditArgs),\n queryFn: () => currentDocument,\n })\n .catch(() => undefined);\n return renderAdminShell({\n auth,\n activeSlug: collection.slug,\n activeDocID: documentId,\n queryClient: collectionEditQueryClient,\n children: (\n <CollectionEditView\n collection={collection.slug}\n documentId={documentId}\n initialData={currentDocument}\n initialPreviewPanelOpen={\n collectionLivePreview !== undefined &&\n readLivePreviewPanelCookie({\n cookieValue: cookieStore.get(adminCollectionCookieName({ collection: collection.slug }))?.value,\n defaultOpen: collectionLivePreview.defaultOpen,\n })\n }\n initialPreviewPanelSize={readLivePreviewLayoutCookie({\n cookieValue: cookieStore.get(adminCollectionCookieName({ collection: collection.slug }))?.value,\n })}\n initialPreviewUrl={initialPreviewUrl}\n />\n ),\n });\n }\n\n if (!collection) {\n throw new Error(\"invalid collection slug\");\n }\n\n const params = parseListViewUrlState({\n searchParams: toSearchParams((await props.searchParams) ?? {}),\n collection,\n });\n\n const [viewSource, viewId] = params.view?.split(\":\") ?? [];\n const configView =\n viewSource === \"config\" ? collection.admin.views.find((view) => view.viewId === viewId) : undefined;\n\n // `listViews` is always needed for the view-tabs UI (Design Decision 39), so it always\n // starts immediately, needing only `token` (already resolved above) — NOT `authPromise`.\n // `peek` is independent too. Both start here, in parallel with `find`/`search` below —\n // because only a `shared:` view genuinely needs `listViews`'s own RESULT before\n // `find`/`search` can be built (that view's own stored sort/where/pageSize lives in this\n // row); every other case (default, config/preset, local, no view) resolves entirely from\n // in-memory config/URL state. Before this, `find`/`search` waited on `listViews`/`peek`\n // unconditionally — the single biggest sequential hop in a warm reload (perf fix).\n const listViewsPromise = fetchQuery(\n vexConvexApi.listViews,\n { collection: collection.slug },\n token ? { token } : undefined,\n );\n const peekArgs = params.peek === undefined ? undefined : buildGetArgs({ id: params.peek, collection });\n const peekPromise =\n peekArgs === undefined\n ? Promise.resolve(undefined)\n : // Same args as `useVexDocQuery`'s client query inside `ListViewPeek` (Design Decision\n // 27/39), so the hydrated peek opens with no re-fetch.\n fetchQuery(vexConvexApi.get, peekArgs, token ? { token } : undefined);\n\n // Streamed, not awaited (developer-binding redesign — TanStack Query v5 + Next.js App\n // Router streaming: https://tanstack.com/query/v5/docs/framework/react/guides/advanced-ssr#streaming-with-server-components).\n // Same branch `useVexListQuery` takes (Design Decision 7/39): an active search means\n // `findPaginated` is the wrong endpoint entirely, not just the wrong args. A stale\n // `?sort=` link naming a field that no longer leads an index (`UNINDEXED_SORT`) is the\n // common failure here, but anything thrown is dehydrated as a REJECTED query — the\n // client's own `useSuspenseQuery` (same key) re-throws it into `CollectionListViewData`,\n // which the table's own `QueryErrorBoundary` catches and renders `ListViewQueryError` from,\n // with no special-casing needed for \"the server's own prefetch failed\" vs \"the client's\n // later refetch failed\" (polish fixes, Decision 1 — previously a dehydrated error crashed\n // the whole page instead, since `CollectionListViewData`'s OWN `useSuspenseQuery` for the\n // seed query threw before `useVexListQuery`'s `pagination.error` ever ran).\n const queryClient = getServerQueryClient();\n\n // Peek re-applied (shell-streaming follow-up, part 2): `ListViewPeek.tsx` now splits into a\n // `useSuspenseVexDocQuery`-driven, `<Suspense>`-wrapped piece — safe to stream un-awaited\n // the same way `findPaginated`/`search` already are, unlike `listViews` (still awaited\n // below until `SavedListViewTabs.tsx`'s own equivalent split lands, owned by\n // `Step12SavedViews` — reapplying that one too once it does). Only fires when there's an\n // actual peek to prefetch; `ListViewPeek` never mounts `useSuspenseVexDocQuery` at all when\n // `peekId` is undefined (its own outer component returns `null` first), so there's nothing\n // to seed in that case. Reuses the SAME `peekArgs` const the `fetchQuery` above does\n // (polish fixes, Decision 3) — two independently-built args objects for what must be the\n // identical TanStack Query key is exactly the bug class `buildGetArgs` exists to prevent\n // (see `AdminTopNavDocCrumb`'s own doc comment for the media-crumb incident this caused).\n if (peekArgs !== undefined) {\n void queryClient\n .prefetchQuery({\n queryKey: convexServerQueryKey(vexConvexApi.get, peekArgs),\n queryFn: () => peekPromise,\n })\n .catch(() => undefined);\n }\n\n // Narrows the readable searchField server-side (Decision 44 / Step 14's documented gap)\n // — this is the ONE piece of this branch that genuinely needs the resolved `auth` (not\n // just `token`) before it can fire, since it reads `auth.user`/`auth.organization`\n // directly; everything else below needs only `token`. `sharedView` only waits on\n // `listViewsPromise` for a `shared:` view (Design Decision 39, perf fix) — every other\n // case resolves from in-memory config/URL state, so `findPaginated`/`search` below can\n // start as soon as `auth` resolves, never gated on `listViews`'s own round trip.\n const [auth, sharedView] = await Promise.all([\n authPromise,\n viewSource === \"shared\"\n ? listViewsPromise\n .catch(() => [] as VexStoredView[])\n .then((views) => views.find((view) => view.viewId === viewId))\n : Promise.resolve(undefined),\n ]);\n\n // Resolved BEFORE `resolveListViewQueryState` (not just for `resolveListViewSearchField`\n // below any more) so it can also drop a user-added filter condition on an\n // unreadable field (access-system-fields.md Decision 11) before query args\n // are ever built — `find`/`search` reject such a condition server-side\n // (Decision 26) while `ListViewFilterChips` hides the chip that would let the\n // user remove it client-side, a dead end without this.\n const fieldPermissions = resolveFieldPermissions({\n access: props.config.access,\n user: auth.user,\n organization: auth.organization,\n resource: collection.slug,\n action: CRUD_ACTIONS.read,\n scope: PERMISSION_SCOPES.any,\n });\n // with the EXACT SAME functions `CollectionListView`'s own client-side query-building\n // already uses (`resolveFieldPermissions` + `resolveListViewSearchField`, Step 14's sort-parity\n // follow-up), fed `resolved.searchField` — the URL/view/collection-default-resolved\n // field `resolveListViewQueryState` already computed — not the raw URL `qf`, so an\n // unmodified view's own saved `searchField` gets the SAME readability narrowing the\n // client applies, not just an explicit `?qf=`. Without this, a `qf` (or a view's saved\n // `searchField`) naming a field this caller can't read would be sent to `search` as-is\n // server-side, while the client substitutes a readable field after hydration instead —\n // two different query keys, a refetch, exactly the flash class Decision 39 forbids.\n const resolved = resolveListViewQueryState({ collection, params, configView, sharedView, fieldPermissions });\n // Decision 50: every `relationship`/`upload` id the ACTIVE filter names,\n // resolved to its target document's title — server-prerendered so a\n // filter chip/row never flashes from the raw id. Kicked off here\n // (as soon as `resolved.where` is known) but not awaited until right\n // before `renderAdminShell` below, same \"start early, await late\"\n // shape as `listViewsPromise`/`peekPromise` — usually 0-3 distinct\n // ids, so this never meaningfully delays the page even though (unlike\n // `findPaginated`/`search`/`peek`/`listViews`) it must be a resolved\n // VALUE by render time: `initialFilterValueTitles` is a plain prop\n // `ListViewContext` seeds its OWN state from, not a dehydrated\n // query-cache entry a client `useQuery` re-reads.\n const relationshipFilterValues = collectListViewFilterRelationshipValues({ where: resolved.where, collection });\n const distinctFilterValues = [\n ...new Map(relationshipFilterValues.map((v) => [`${v.targetCollection}:${v.id}`, v])).values(),\n ];\n const filterValueTitlesPromise = Promise.allSettled(\n distinctFilterValues.map(async (v): Promise<[string, string | null]> => {\n const targetCollectionConfig =\n props.config.collections.find((c) => c.slug === v.targetCollection) ??\n props.config.mediaCollections.find((c) => c.slug === v.targetCollection);\n if (targetCollectionConfig === undefined) return [v.id, null];\n const doc = await fetchQuery(\n vexConvexApi.get,\n buildGetArgs({ id: v.id, collection: targetCollectionConfig }),\n token ? { token } : undefined,\n );\n if (doc === null) return [v.id, null];\n const titleField = targetCollectionConfig.admin.useAsTitle;\n const title = (doc as Record<string, unknown>)[titleField];\n return [v.id, typeof title === \"string\" && title !== \"\" ? title : String(doc._id)];\n }),\n );\n const numItems = Math.max(collection.admin.view.state.serverPageSize, resolved.pageSize);\n const drafts = collection.versions.drafts ? true : undefined;\n const resolvedSearchField = resolveListViewSearchField({\n collection,\n qf: resolved.searchField,\n fieldPermissions,\n });\n // Mirrors `CollectionListView.tsx`'s own `isSearchActive` exactly (whitespace-only\n // `q` is \"no search\"; no READABLE searchField is \"no search\" too — Decision 2's \"no\n // search bar\" case, now enforced server-side too) — the server and client must\n // agree on WHICH endpoint (`search` vs `findPaginated`) they're calling, not just on\n // that endpoint's args, or the client's post-hydration query key disagrees with the\n // server's and refetches (Step 14's sort-parity bug, same class).\n const isSearchActive = resolved.search.trim() !== \"\" && resolvedSearchField !== undefined;\n\n // `convexQuery(...).queryKey` is the SAME key `CollectionListViewData`'s own\n // `useSuspenseQuery` call builds from the identical `buildListViewFindQueryArgs`/\n // `buildListViewSearchQueryArgs` helpers — required for `<HydrationBoundary>` to find this\n // entry at all, not just for \"correctness\" (a mismatched key is a silent miss: the\n // client just re-fetches from scratch). `prefetchQuery` is called SYNCHRONOUSLY here (its\n // own promise is NOT awaited — the `.catch` only guards an unhandled-rejection warning),\n // right after `auth`/`sharedView` resolve and BEFORE awaiting `listViewsPromise`/\n // `peekPromise` below (for the OTHER props) — so `findPaginated`/`search` is called at\n // the earliest possible point, never gated on `listViews` (the perf fix this preserves).\n if (isSearchActive) {\n const args = buildListViewSearchQueryArgs({\n collection,\n where: resolved.where as VexWhere<string> | undefined,\n search: resolved.search,\n searchField: resolvedSearchField as string,\n drafts,\n numItems,\n });\n void queryClient\n .prefetchQuery({\n queryKey: convexServerQueryKey(vexConvexApi.search, args),\n queryFn: () => fetchQuery(vexConvexApi.search, args, token ? { token } : undefined),\n })\n .catch(() => undefined);\n } else {\n const args = buildListViewFindQueryArgs({\n collection,\n where: resolved.where as VexWhere<string> | undefined,\n sort: resolved.sort,\n drafts,\n depth: 1,\n numItems,\n });\n void queryClient\n .prefetchQuery({\n queryKey: convexServerQueryKey(vexConvexApi.findPaginated, args),\n queryFn: () => fetchQuery(vexConvexApi.findPaginated, args, token ? { token } : undefined),\n })\n .catch(() => undefined);\n }\n\n // Decision 9/32: a URL must never crash the page. `listViews` is already gated to\n // return `[]` for a denied caller (Step 7) and `get` to `null` for a not-found row;\n // either rejecting is caught by the `.catch(() => undefined)` on the `prefetchQuery`\n // call below, same as `findPaginated`/`search`/`peek` above.\n //\n // Awaited UNCONDITIONALLY for every view source (developer-reported regression fix —\n // previously only awaited `if (viewSource === \"shared\")`, \"streamed, not awaited\" for\n // every other case): the bug wasn't that the shared views never arrived, it's that the\n // raw SSR HTML's dehydrated cache entry captured `status: \"pending\"` for the DEFAULT/\n // config/local cases, so the client painted the tab strip's own no-shared-views-yet\n // state BEFORE the shared tabs resolved — a visible pop-in under any backend latency,\n // confirmed live via the raw SSR response (`curl`'d `/admin/collections/posts`, no\n // `view=`: the embedded dehydrated state showed `\"status\":\"pending\",\"fetchStatus\":\n // \"fetching\"` for the `listViews` query, with the shared view labels ABSENT from the\n // initial HTML). `listViewsPromise` itself already starts at the same moment as every\n // other fetch above (overlapping the auth/data work, not sequenced after it) — awaiting\n // `listViewsPrefetch` here costs only the REMAINING time (if any) for that same\n // in-flight request to settle, not a new round trip; for a warm reload this is\n // typically near-zero since `listViewsPromise` has had the whole rest of this\n // function's work (collection resolution, filter-title lookups, etc.) to settle in\n // already. `SavedListViewTabs.tsx`'s own tab strip and `CollectionListView.tsx`'s own\n // `useSavedListViews()` call (for `visibleColumns`/`columnOrder` state on a shared view)\n // both now see a `\"success\"` dehydrated entry on first paint, for every view source —\n // no skeleton-then-shared-tabs pop-in, no skeleton-then-narrower-table shift, no\n // hydration mismatch. (`SavedListViewTabs.tsx` used to split its tab strip into a\n // `<Suspense>`-wrapped piece specifically to stream shared views in once this prefetch\n // was un-awaited — removed once the await above landed, since that was the split's\n // only reason to exist, and a nested `<Suspense>` boundary turned out to have its own\n // cost even with already-resolved data: React's streaming SSR ships a nested\n // boundary's fallback in the first flush regardless of whether the underlying query\n // had to wait at all — confirmed live via a JS-disabled load of a shared-view URL.)\n const listViewsPrefetch = queryClient\n .prefetchQuery({\n queryKey: convexServerQueryKey(vexConvexApi.listViews, { collection: collection.slug }),\n queryFn: () => listViewsPromise,\n })\n .catch(() => undefined);\n await listViewsPrefetch;\n const skeletonRowCountEntry = parseListViewSkeletonRowCountCookie({\n cookieValue: cookieStore.get(adminCollectionCookieName({ collection: collection.slug }))?.value,\n })[viewId ?? \"default\"];\n const filterValueTitleResults = await filterValueTitlesPromise;\n const initialFilterValueTitles: Record<string, string | null> = Object.fromEntries(\n filterValueTitleResults.map((result, index) =>\n result.status === \"fulfilled\" ? result.value : [distinctFilterValues[index].id, null],\n ),\n );\n\n return renderAdminShell({\n auth,\n activeSlug: collection.slug,\n queryClient,\n children: (\n <CollectionListView\n collection={collection.slug}\n initialLocalIndex={parseLocalSavedListViewsIndexCookie({\n cookieValue: cookieStore.get(adminCollectionCookieName({ collection: collection.slug }))?.value,\n })}\n initialPreferences={parseListViewConfigCookies({ cookies: cookieStore, collectionSlug: collection.slug })}\n initialSkeletonRowCount={skeletonRowCountEntry?.n}\n initialSkeletonDone={skeletonRowCountEntry?.done}\n initialFilterValueTitles={initialFilterValueTitles}\n />\n ),\n });\n}\n\n/**\n * Converts Next.js's `searchParams` shape (`{ [key]: string | string[] | undefined }`) to\n * the `URLSearchParams` `parseListViewUrlState` (Design Decision 40) expects — the one place this\n * conversion happens, since every other reader of the list URL is client-side, where\n * `URLSearchParams` is already what `useSearchParams()`/`nuqs` hand back.\n *\n * @param raw - The awaited `searchParams` prop.\n * @returns An equivalent `URLSearchParams`, repeating a key for each array entry.\n */\nfunction toSearchParams(raw: Record<string, string | string[] | undefined>): URLSearchParams {\n const params = new URLSearchParams();\n for (const [key, value] of Object.entries(raw)) {\n if (value === undefined) continue;\n for (const entry of Array.isArray(value) ? value : [value]) params.append(key, entry);\n }\n return params;\n}\n","import { defaultShouldDehydrateQuery, hashKey, QueryClient } from \"@tanstack/react-query\";\nimport { getFunctionName } from \"convex/server\";\nimport { convexToJson } from \"convex/values\";\nimport type { FunctionReference } from \"convex/server\";\nimport { cache } from \"react\";\n\n/**\n * Builds the SAME query key shape `@convex-dev/react-query`'s `convexQuery(funcRef, args)`\n * produces (`[\"convexQuery\", functionName, args]`) — WITHOUT importing `convex/react` (its\n * own `ConvexQueryClient`/`ConvexReactClient` transitively call `React.createContext`, a\n * client-only API that crashes a Server Component import graph: \"createContext only works\n * in Client Components\", confirmed live). `getFunctionName`/`convexToJson` are the same\n * underlying helpers `convexQuery` itself uses, from `convex/server`/`convex/values` — both\n * server-safe, pure metadata/serialization functions with no live client involved.\n *\n * Required for `<HydrationBoundary>` to be found at all by the client's own\n * `useSuspenseQuery(convexQuery(sameFn, sameArgs))` call — the two sides must agree on this\n * key byte-for-byte (after hashing, below) or hydration silently misses and the client\n * re-fetches from scratch.\n *\n * @param funcRef - The Convex query function reference (e.g. `vexConvexApi.findPaginated`).\n * @param args - Its arguments.\n * @returns The `[\"convexQuery\", functionName, args]` key tuple.\n */\nexport function convexServerQueryKey(\n funcRef: FunctionReference<\"query\">,\n args: Record<string, unknown>,\n): readonly [string, string, Record<string, unknown>] {\n return [\"convexQuery\", getFunctionName(funcRef), args] as const;\n}\n\n/**\n * Hashes a query key EXACTLY the way `ConvexQueryClient.hashFn()` does for a Convex key\n * (`convexQuery|<functionName>|<JSON args>`), falling back to TanStack's own default\n * `hashKey` for any other (non-Convex) key — same two-branch shape as the original, just\n * without needing a live `ConvexQueryClient` instance to call `.hashFn()` on (see\n * `convexServerQueryKey`'s own doc comment for why that import is server-unsafe).\n *\n * @param queryKey - Any TanStack Query key.\n * @returns The hashed string TanStack Query uses as this key's cache map key.\n */\nfunction serverQueryKeyHashFn(queryKey: readonly unknown[]): string {\n if (queryKey.length >= 2 && queryKey[0] === \"convexQuery\") {\n return `convexQuery|${getFunctionName(queryKey[1] as FunctionReference<\"query\">)}|${JSON.stringify(convexToJson(queryKey[2] as Parameters<typeof convexToJson>[0]))}`;\n }\n return hashKey(queryKey);\n}\n\n/**\n * Per-request `QueryClient` for the TanStack Query v5 + Next.js App Router streaming-SSR\n * pattern (https://tanstack.com/query/v5/docs/framework/react/guides/advanced-ssr#streaming-with-server-components):\n * `NextAdminPage` calls `queryClient.prefetchQuery(...)` WITHOUT awaiting it, then wraps its\n * return value in `<HydrationBoundary state={dehydrate(queryClient)}>` — the still-pending\n * promise streams to the client as a LATER chunk instead of blocking the whole response, and\n * the client's own `useSuspenseQuery(convexQuery(...))` (same `queryKey`) picks it up via\n * `<HydrationBoundary>`'s hydration (ambient `QueryClientProvider`, mounted by the app's own\n * client providers — this server-only client is NEVER rendered to the page, it only exists to\n * prefetch into and dehydrate).\n *\n * `cache()`-scoped so every `NextAdminPage`/`page.tsx` call within ONE request shares the SAME\n * client (one `dehydrate()` at the end serializes every query prefetched anywhere in the\n * request), and a NEW one is built for the next request — no cross-request cache leak (the same\n * reasoning as `apps/*‍/src/components/providers/convex.tsx`'s own server branch).\n *\n * `shouldDehydrateQuery` additionally includes PENDING queries (TanStack's own default only\n * dehydrates settled ones) — required for the \"don't await the prefetch\" half of the pattern;\n * without it, an unawaited `prefetchQuery` call would dehydrate nothing and the client would\n * silently re-fetch from scratch, defeating the whole point.\n *\n * Every actual server-side fetch goes through `fetchQuery` (`convex/nextjs`) as the\n * `prefetchQuery` call's own `queryFn` — this module never touches `convex/react`/a live\n * `ConvexReactClient` at all (see `convexServerQueryKey`'s doc comment).\n */\nexport const getServerQueryClient = cache((): QueryClient => {\n return new QueryClient({\n defaultOptions: {\n queries: {\n // Matches `apps/*‍/src/components/providers/convex.tsx`'s client `QueryClient`, so a\n // Convex query's dehydrated key hashes identically on both sides — required for\n // hydration to find it rather than silently re-fetching.\n queryKeyHashFn: serverQueryKeyHashFn,\n retry: false,\n },\n dehydrate: {\n // `pending`: TanStack's own default only dehydrates settled queries — required for\n // the \"don't await the prefetch\" half of the pattern, see above. `error`: a Convex\n // query that genuinely fails (e.g. `UNINDEXED_SORT`) settles to \"error\" by the time\n // `dehydrate()` runs (several awaits/microtasks after the un-awaited `prefetchQuery`\n // call) — WITHOUT this, `defaultShouldDehydrateQuery` excludes it entirely (only\n // \"success\" dehydrates by default), so the client's `useSuspenseQuery` would silently\n // re-fetch from scratch and hit the identical error a second time — functionally fine\n // (the client already handles its own error the same way, `ListViewQueryError`), but a\n // wasted round trip; including \"error\" here lets the dehydrated failure reach the\n // client directly, no second Convex call needed.\n shouldDehydrateQuery: (query) =>\n defaultShouldDehydrateQuery(query) || query.state.status === \"pending\" || query.state.status === \"error\",\n // Next.js detects dynamic rendering via a thrown error during render; redacting it\n // here (TanStack's own default) would hide that signal from Next.js.\n shouldRedactErrors: () => false,\n },\n },\n });\n});\n"],"mappings":";AAUA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,iBAAiB;AAC1B,SAAS,kBAAkB;AAC3B,SAAS,eAAe;AACxB,SAAS,gBAAgB;AAEzB,SAAS,+BAA+B;;;ACpDxC,SAAS,6BAA6B,SAAS,mBAAmB;AAClE,SAAS,uBAAuB;AAChC,SAAS,oBAAoB;AAE7B,SAAS,aAAa;AAoBf,SAAS,qBACd,SACA,MACoD;AACpD,SAAO,CAAC,eAAe,gBAAgB,OAAO,GAAG,IAAI;AACvD;AAYA,SAAS,qBAAqB,UAAsC;AAClE,MAAI,SAAS,UAAU,KAAK,SAAS,CAAC,MAAM,eAAe;AACzD,WAAO,eAAe,gBAAgB,SAAS,CAAC,CAA+B,CAAC,IAAI,KAAK,UAAU,aAAa,SAAS,CAAC,CAAuC,CAAC,CAAC;AAAA,EACrK;AACA,SAAO,QAAQ,QAAQ;AACzB;AA2BO,IAAM,uBAAuB,MAAM,MAAmB;AAC3D,SAAO,IAAI,YAAY;AAAA,IACrB,gBAAgB;AAAA,MACd,SAAS;AAAA;AAAA;AAAA;AAAA,QAIP,gBAAgB;AAAA,QAChB,OAAO;AAAA,MACT;AAAA,MACA,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAWT,sBAAsB,CAAC,UACrB,4BAA4B,KAAK,KAAK,MAAM,MAAM,WAAW,aAAa,MAAM,MAAM,WAAW;AAAA;AAAA;AAAA,QAGnG,oBAAoB,MAAM;AAAA,MAC5B;AAAA,IACF;AAAA,EACF,CAAC;AACH,CAAC;;;ADbK,SACE,KADF;AATN,SAAS,iBAAiB,OAMvB;AACD,SACE,oBAAC,mBAAgB,OAAO,MAAM,MAC5B,+BAAC,2BAAwB,OAAO,UAAU,MAAM,WAAW,GACzD;AAAA,wBAAC,eAAY,YAAY,MAAM,YAAY,aAAa,MAAM,aAAa;AAAA,IAmB3E,oBAAC,SAAI,WAAU,2BAA2B,gBAAM,UAAS;AAAA,KAC3D,GACF;AAEJ;AAmBA,eAAe,yBAAyB,OAIR;AAC9B,QAAM,MAAM,MAAM;AAAA,IAChB,aAAa;AAAA,IACb,MAAM;AAAA,IACN,MAAM,QAAQ,EAAE,OAAO,MAAM,MAAM,IAAI;AAAA,EACzC;AACA,MAAI,CAAC,IAAK,QAAO;AAEjB,SAAO,wBAAwB,EAAE,KAAK,gBAAgB,MAAM,KAAK,CAAC;AACpE;AAEA,IAAI,oBAAoB;AAcxB,SAAS,kBAAkB,OAGlB;AACP,MAAI,MAAM,gBAAgB,OAAW;AACrC,MAAI,CAAC,MAAM,UAAU,CAAC,MAAM,OAAO,QAAS;AAC5C,MAAI,kBAAmB;AACvB,MAAI,QAAQ,IAAI,aAAa,aAAc;AAC3C,sBAAoB;AACpB,UAAQ;AAAA,IACN;AAAA,EAIF;AACF;AAgFA,eAAsB,cAAc,OAWjC;AACD,QAAM,EAAE,OAAO,CAAC,EAAE,IAAI,MAAM,MAAM;AAClC,QAAM,CAAC,SAAS,gBAAgB,UAAU,IAAI;AAC9C,QAAM,cAAc,MAAM,QAAQ;AAElC,oBAAkB,EAAE,QAAQ,MAAM,OAAO,QAAQ,aAAa,MAAM,YAAY,CAAC;AACjF,QAAM,QAAQ,MAAM,eAAe,MAAM,MAAM,eAAe;AAU9D,QAAM,cAAmC,MAAM,eAAe,QAAQ,QAAQ,EAAE,MAAM,KAAK,CAAC;AAE5F,MAAI,KAAK,WAAW,GAAG;AACrB,UAAMA,QAAO,MAAM;AACnB,WAAO,iBAAiB;AAAA,MACtB,MAAAA;AAAA,MACA,aAAa,qBAAqB;AAAA,MAClC,UAAU,oBAAC,iBAAc;AAAA,IAC3B,CAAC;AAAA,EACH;AAEA,MAAI,YAAY,iBAAiB,YAAY,WAAW;AACtD,aAAS;AAAA,EACX;AAEA,MAAI,YAAY,WAAW;AACzB,QAAI,CAAC,gBAAgB;AACnB,YAAMA,QAAO,MAAM;AACnB,aAAO,iBAAiB;AAAA,QACtB,MAAAA;AAAA,QACA,YAAY;AAAA,QACZ,aAAa,qBAAqB;AAAA,QAClC,UAAU,oBAAC,mBAAgB;AAAA,MAC7B,CAAC;AAAA,IACH;AACA,UAAM,eAAe,MAAM,OAAO,QAAQ,KAAK,CAAC,MAAM,EAAE,SAAS,cAAc;AAC/E,QAAI,CAAC,cAAc;AACjB,YAAMA,QAAO,MAAM;AACnB,aAAO,iBAAiB;AAAA,QACtB,MAAAA;AAAA,QACA,YAAY;AAAA,QACZ,aAAa,qBAAqB;AAAA,QAClC,UACE;AAAA,UAAC;AAAA;AAAA,YACC,SAAQ;AAAA,YACR,QAAQ,WAAW,cAAc;AAAA,YACjC,UAAU,UAAU,EAAE,MAAM,UAAU,GAAG,MAAM,OAAO,QAAQ;AAAA,YAC9D,WAAU;AAAA;AAAA,QACZ;AAAA,MAEJ,CAAC;AAAA,IACH;AACA,UAAM,oBAAoB,qBAAqB;AAC/C,UAAM,aAAa,mBAAmB,EAAE,QAAQ,aAAa,CAAC;AAa9D,UAAM,mBAAmB;AAAA,MACvB,aAAa,QAAQ;AAAA,MACrB;AAAA,MACA,QAAQ,EAAE,MAAM,IAAI;AAAA,IACtB,EAAE,MAAM,MAAM,IAAI;AAClB,UAAM,oBAAoB,2BAA2B;AAAA,MACnD,QAAQ,MAAM,OAAO,MAAM;AAAA,MAC3B,MAAM;AAAA,MACN,MAAM,aAAa;AAAA,MACnB,OAAO,aAAa,MAAM;AAAA,IAC5B,CAAC;AACD,UAAM,oBACJ,sBAAsB,SAClB,QAAQ,QAAQ,MAAS,IACzB,yBAAyB;AAAA,MACvB,MAAM,EAAE,YAAY,aAAa,MAAM,MAAM,UAAU,QAAQ,CAAC,EAAE;AAAA,MAClE,MAAM,aAAa;AAAA,MACnB;AAAA,IACF,CAAC;AACP,UAAM,CAACA,OAAM,mBAAmB,SAAS,IAAI,MAAM,QAAQ,IAAI;AAAA,MAC7D;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AACD,QAAI,CAAC,WAAW;AACd,aAAO,iBAAiB;AAAA,QACtB,MAAAA;AAAA,QACA,YAAY;AAAA,QACZ,aAAa;AAAA,QACb,UACE;AAAA,UAAC;AAAA;AAAA,YACC,SAAQ;AAAA,YACR,QAAO;AAAA,YACP,UAAU,GAAG,MAAM,OAAO,QAAQ;AAAA,YAClC,WAAU;AAAA;AAAA,QACZ;AAAA,MAEJ,CAAC;AAAA,IACH;AASA,UAAM,kBACH,cAAc;AAAA,MACb,UAAU,qBAAqB,aAAa,QAAQ,KAAK,UAAU;AAAA,MACnE,SAAS,MAAM;AAAA,IACjB,CAAC,EACA,MAAM,MAAM,MAAS;AACxB,WAAO,iBAAiB;AAAA,MACtB,MAAAA;AAAA,MACA,YAAY;AAAA,MACZ,aAAa,aAAa;AAAA,MAC1B,aAAa;AAAA,MACb,UACE;AAAA,QAAC;AAAA;AAAA,UACC,QAAQ,aAAa;AAAA,UACrB,aAAa;AAAA,UACb,yBACE,sBAAsB,UACtB,2BAA2B;AAAA,YACzB,aAAa,YAAY,IAAI,iBAAiB,GAAG;AAAA,YACjD,aAAa,kBAAkB;AAAA,UACjC,CAAC;AAAA,UAEH,yBAAyB,4BAA4B;AAAA,YACnD,aAAa,YAAY,IAAI,iBAAiB,GAAG;AAAA,UACnD,CAAC;AAAA,UACD;AAAA;AAAA,MACF;AAAA,IAEJ,CAAC;AAAA,EACH;AAEA,MAAI,CAAC,gBAAgB;AACnB,UAAMA,QAAO,MAAM;AACnB,WAAO,iBAAiB;AAAA,MACtB,MAAAA;AAAA,MACA,YAAY;AAAA,MACZ,aAAa,qBAAqB;AAAA,MAClC,UAAU,oBAAC,wBAAqB;AAAA,IAClC,CAAC;AAAA,EACH;AAEA,QAAM,aAAa,MAAM,OAAO,YAAY,KAAK,CAAC,MAAM,EAAE,SAAS,cAAc;AACjF,QAAM,kBAAkB,MAAM,OAAO,iBAAiB,KAAK,CAAC,OAAO,GAAG,SAAS,cAAc;AAE7F,MAAI,CAAC,cAAc,CAAC,iBAAiB;AACnC,UAAMA,QAAO,MAAM;AACnB,WAAO,iBAAiB;AAAA,MACtB,MAAAA;AAAA,MACA,YAAY;AAAA,MACZ,aAAa,qBAAqB;AAAA,MAClC,UACE;AAAA,QAAC;AAAA;AAAA,UACC,SAAQ;AAAA,UACR,QAAQ,eAAe,cAAc;AAAA,UACrC,UAAU,UAAU,EAAE,MAAM,cAAc,GAAG,MAAM,OAAO,QAAQ;AAAA,UAClE,WAAU;AAAA;AAAA,MACZ;AAAA,IAEJ,CAAC;AAAA,EACH;AAEA,MAAI,mBAAmB,YAAY;AACjC,UAAM,uBAAuB,qBAAqB;AAClD,UAAM,gBAAgB,aAAa,EAAE,IAAI,YAAY,YAAY,gBAAgB,CAAC;AAWlF,UAAM,kBAAkB;AAAA,MACtB,aAAa;AAAA,MACb;AAAA,MACA,QAAQ,EAAE,MAAM,IAAI;AAAA,IACtB,EAAE,MAAM,MAAM,IAAI;AAClB,UAAM,CAACA,OAAM,QAAQ,IAAI,MAAM,QAAQ,IAAI,CAAC,aAAa,eAAe,CAAC;AACzE,QAAI,CAAC,UAAU;AACb,aAAO,iBAAiB;AAAA,QACtB,MAAAA;AAAA,QACA,YAAY,gBAAgB;AAAA,QAC5B,aAAa;AAAA,QACb,UACE;AAAA,UAAC;AAAA;AAAA,YACC,SAAQ;AAAA,YACR,QAAO;AAAA,YACP,UAAU,GAAG,MAAM,OAAO,QAAQ,gBAAgB,gBAAgB,IAAI;AAAA,YACtE,WAAW,WAAW,gBAAgB,OAAO,MAAM;AAAA;AAAA,QACrD;AAAA,MAEJ,CAAC;AAAA,IACH;AAUA,UAAM,qBACH,cAAc;AAAA,MACb,UAAU,qBAAqB,aAAa,KAAK,aAAa;AAAA,MAC9D,SAAS,MAAM;AAAA,IACjB,CAAC,EACA,MAAM,MAAM,MAAS;AACxB,WAAO,iBAAiB;AAAA,MACtB,MAAAA;AAAA,MACA,YAAY,gBAAgB;AAAA,MAC5B,aAAa;AAAA,MACb,aAAa;AAAA,MACb,UACE,oBAAC,2BAAwB,YAAY,gBAAgB,MAAM,YAAwB,aAAa,UAAU;AAAA,IAE9G,CAAC;AAAA,EACH;AAEA,MAAI,iBAAiB;AACnB,UAAMC,YAAW,KAAK;AAAA,MACpB,gBAAgB,MAAM,KAAK,MAAM;AAAA,MACjC,gBAAgB,MAAM,KAAK,MAAM;AAAA,IACnC;AACA,UAAMD,QAAO,MAAM;AACnB,UAAM,mBAAmB,qBAAqB;AAC9C,UAAM,YAAY,2BAA2B;AAAA,MAC3C,YAAY;AAAA,MACZ,OAAO;AAAA,MACP,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,UAAAC;AAAA,IACF,CAAC;AAKD,SAAK,iBACF,cAAc;AAAA,MACb,UAAU,qBAAqB,aAAa,eAAe,SAAS;AAAA,MACpE,SAAS,MAAM,WAAW,aAAa,eAAe,WAAW,QAAQ,EAAE,MAAM,IAAI,MAAS;AAAA,IAChG,CAAC,EACA,MAAM,MAAM,MAAS;AACxB,WAAO,iBAAiB;AAAA,MACtB,MAAAD;AAAA,MACA,YAAY,gBAAgB;AAAA,MAC5B,aAAa;AAAA,MACb,UAAU,oBAAC,2BAAwB,YAAY,gBAAgB,MAAM;AAAA,IACvE,CAAC;AAAA,EACH;AAEA,MAAI,cAAc,YAAY;AAC5B,UAAM,4BAA4B,qBAAqB;AACvD,UAAM,qBAAqB,aAAa,EAAE,IAAI,YAAY,WAAW,CAAC;AAYtE,UAAM,uBAAuB;AAAA,MAC3B,aAAa;AAAA,MACb;AAAA,MACA,QAAQ,EAAE,MAAM,IAAI;AAAA,IACtB,EAAE,MAAM,MAAM,IAAI;AAClB,UAAM,wBAAwB,2BAA2B;AAAA,MACvD,QAAQ,MAAM,OAAO,MAAM;AAAA,MAC3B,MAAM;AAAA,MACN,MAAM,WAAW;AAAA,MACjB,OAAO,WAAW,MAAM;AAAA,IAC1B,CAAC;AACD,UAAM,oBACJ,0BAA0B,SACtB,QAAQ,QAAQ,MAAS,IACzB,yBAAyB;AAAA,MACvB,MAAM,EAAE,YAAY,WAAW,MAAM,MAAM,cAAc,YAAY,QAAQ,CAAC,EAAE;AAAA,MAChF,MAAM,WAAW;AAAA,MACjB;AAAA,IACF,CAAC;AACP,UAAM,CAACA,OAAM,mBAAmB,eAAe,IAAI,MAAM,QAAQ,IAAI;AAAA,MACnE;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AACD,QAAI,CAAC,iBAAiB;AACpB,aAAO,iBAAiB;AAAA,QACtB,MAAAA;AAAA,QACA,YAAY,WAAW;AAAA,QACvB,aAAa;AAAA,QACb,UACE;AAAA,UAAC;AAAA;AAAA,YACC,SAAQ;AAAA,YACR,QAAO;AAAA,YACP,UAAU,GAAG,MAAM,OAAO,QAAQ,gBAAgB,WAAW,IAAI;AAAA,YACjE,WAAW,WAAW,WAAW,OAAO,MAAM;AAAA;AAAA,QAChD;AAAA,MAEJ,CAAC;AAAA,IACH;AAUA,UAAM,0BACH,cAAc;AAAA,MACb,UAAU,qBAAqB,aAAa,KAAK,kBAAkB;AAAA,MACnE,SAAS,MAAM;AAAA,IACjB,CAAC,EACA,MAAM,MAAM,MAAS;AACxB,WAAO,iBAAiB;AAAA,MACtB,MAAAA;AAAA,MACA,YAAY,WAAW;AAAA,MACvB,aAAa;AAAA,MACb,aAAa;AAAA,MACb,UACE;AAAA,QAAC;AAAA;AAAA,UACC,YAAY,WAAW;AAAA,UACvB;AAAA,UACA,aAAa;AAAA,UACb,yBACE,0BAA0B,UAC1B,2BAA2B;AAAA,YACzB,aAAa,YAAY,IAAI,0BAA0B,EAAE,YAAY,WAAW,KAAK,CAAC,CAAC,GAAG;AAAA,YAC1F,aAAa,sBAAsB;AAAA,UACrC,CAAC;AAAA,UAEH,yBAAyB,4BAA4B;AAAA,YACnD,aAAa,YAAY,IAAI,0BAA0B,EAAE,YAAY,WAAW,KAAK,CAAC,CAAC,GAAG;AAAA,UAC5F,CAAC;AAAA,UACD;AAAA;AAAA,MACF;AAAA,IAEJ,CAAC;AAAA,EACH;AAEA,MAAI,CAAC,YAAY;AACf,UAAM,IAAI,MAAM,yBAAyB;AAAA,EAC3C;AAEA,QAAM,SAAS,sBAAsB;AAAA,IACnC,cAAc,eAAgB,MAAM,MAAM,gBAAiB,CAAC,CAAC;AAAA,IAC7D;AAAA,EACF,CAAC;AAED,QAAM,CAAC,YAAY,MAAM,IAAI,OAAO,MAAM,MAAM,GAAG,KAAK,CAAC;AACzD,QAAM,aACJ,eAAe,WAAW,WAAW,MAAM,MAAM,KAAK,CAAC,SAAS,KAAK,WAAW,MAAM,IAAI;AAU5F,QAAM,mBAAmB;AAAA,IACvB,aAAa;AAAA,IACb,EAAE,YAAY,WAAW,KAAK;AAAA,IAC9B,QAAQ,EAAE,MAAM,IAAI;AAAA,EACtB;AACA,QAAM,WAAW,OAAO,SAAS,SAAY,SAAY,aAAa,EAAE,IAAI,OAAO,MAAM,WAAW,CAAC;AACrG,QAAM,cACJ,aAAa,SACT,QAAQ,QAAQ,MAAS;AAAA;AAAA;AAAA,IAGzB,WAAW,aAAa,KAAK,UAAU,QAAQ,EAAE,MAAM,IAAI,MAAS;AAAA;AAc1E,QAAM,cAAc,qBAAqB;AAazC,MAAI,aAAa,QAAW;AAC1B,SAAK,YACF,cAAc;AAAA,MACb,UAAU,qBAAqB,aAAa,KAAK,QAAQ;AAAA,MACzD,SAAS,MAAM;AAAA,IACjB,CAAC,EACA,MAAM,MAAM,MAAS;AAAA,EAC1B;AASA,QAAM,CAAC,MAAM,UAAU,IAAI,MAAM,QAAQ,IAAI;AAAA,IAC3C;AAAA,IACA,eAAe,WACX,iBACG,MAAM,MAAM,CAAC,CAAoB,EACjC,KAAK,CAAC,UAAU,MAAM,KAAK,CAAC,SAAS,KAAK,WAAW,MAAM,CAAC,IAC/D,QAAQ,QAAQ,MAAS;AAAA,EAC/B,CAAC;AAQD,QAAM,mBAAmB,wBAAwB;AAAA,IAC/C,QAAQ,MAAM,OAAO;AAAA,IACrB,MAAM,KAAK;AAAA,IACX,cAAc,KAAK;AAAA,IACnB,UAAU,WAAW;AAAA,IACrB,QAAQ,aAAa;AAAA,IACrB,OAAO,kBAAkB;AAAA,EAC3B,CAAC;AAUD,QAAM,WAAW,0BAA0B,EAAE,YAAY,QAAQ,YAAY,YAAY,iBAAiB,CAAC;AAY3G,QAAM,2BAA2B,wCAAwC,EAAE,OAAO,SAAS,OAAO,WAAW,CAAC;AAC9G,QAAM,uBAAuB;AAAA,IAC3B,GAAG,IAAI,IAAI,yBAAyB,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,gBAAgB,IAAI,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,OAAO;AAAA,EAC/F;AACA,QAAM,2BAA2B,QAAQ;AAAA,IACvC,qBAAqB,IAAI,OAAO,MAAwC;AACtE,YAAM,yBACJ,MAAM,OAAO,YAAY,KAAK,CAAC,MAAM,EAAE,SAAS,EAAE,gBAAgB,KAClE,MAAM,OAAO,iBAAiB,KAAK,CAAC,MAAM,EAAE,SAAS,EAAE,gBAAgB;AACzE,UAAI,2BAA2B,OAAW,QAAO,CAAC,EAAE,IAAI,IAAI;AAC5D,YAAM,MAAM,MAAM;AAAA,QAChB,aAAa;AAAA,QACb,aAAa,EAAE,IAAI,EAAE,IAAI,YAAY,uBAAuB,CAAC;AAAA,QAC7D,QAAQ,EAAE,MAAM,IAAI;AAAA,MACtB;AACA,UAAI,QAAQ,KAAM,QAAO,CAAC,EAAE,IAAI,IAAI;AACpC,YAAM,aAAa,uBAAuB,MAAM;AAChD,YAAM,QAAS,IAAgC,UAAU;AACzD,aAAO,CAAC,EAAE,IAAI,OAAO,UAAU,YAAY,UAAU,KAAK,QAAQ,OAAO,IAAI,GAAG,CAAC;AAAA,IACnF,CAAC;AAAA,EACH;AACA,QAAM,WAAW,KAAK,IAAI,WAAW,MAAM,KAAK,MAAM,gBAAgB,SAAS,QAAQ;AACvF,QAAM,SAAS,WAAW,SAAS,SAAS,OAAO;AACnD,QAAM,sBAAsB,2BAA2B;AAAA,IACrD;AAAA,IACA,IAAI,SAAS;AAAA,IACb;AAAA,EACF,CAAC;AAOD,QAAM,iBAAiB,SAAS,OAAO,KAAK,MAAM,MAAM,wBAAwB;AAWhF,MAAI,gBAAgB;AAClB,UAAM,OAAO,6BAA6B;AAAA,MACxC;AAAA,MACA,OAAO,SAAS;AAAA,MAChB,QAAQ,SAAS;AAAA,MACjB,aAAa;AAAA,MACb;AAAA,MACA;AAAA,IACF,CAAC;AACD,SAAK,YACF,cAAc;AAAA,MACb,UAAU,qBAAqB,aAAa,QAAQ,IAAI;AAAA,MACxD,SAAS,MAAM,WAAW,aAAa,QAAQ,MAAM,QAAQ,EAAE,MAAM,IAAI,MAAS;AAAA,IACpF,CAAC,EACA,MAAM,MAAM,MAAS;AAAA,EAC1B,OAAO;AACL,UAAM,OAAO,2BAA2B;AAAA,MACtC;AAAA,MACA,OAAO,SAAS;AAAA,MAChB,MAAM,SAAS;AAAA,MACf;AAAA,MACA,OAAO;AAAA,MACP;AAAA,IACF,CAAC;AACD,SAAK,YACF,cAAc;AAAA,MACb,UAAU,qBAAqB,aAAa,eAAe,IAAI;AAAA,MAC/D,SAAS,MAAM,WAAW,aAAa,eAAe,MAAM,QAAQ,EAAE,MAAM,IAAI,MAAS;AAAA,IAC3F,CAAC,EACA,MAAM,MAAM,MAAS;AAAA,EAC1B;AAiCA,QAAM,oBAAoB,YACvB,cAAc;AAAA,IACb,UAAU,qBAAqB,aAAa,WAAW,EAAE,YAAY,WAAW,KAAK,CAAC;AAAA,IACtF,SAAS,MAAM;AAAA,EACjB,CAAC,EACA,MAAM,MAAM,MAAS;AACxB,QAAM;AACN,QAAM,wBAAwB,oCAAoC;AAAA,IAChE,aAAa,YAAY,IAAI,0BAA0B,EAAE,YAAY,WAAW,KAAK,CAAC,CAAC,GAAG;AAAA,EAC5F,CAAC,EAAE,UAAU,SAAS;AACtB,QAAM,0BAA0B,MAAM;AACtC,QAAM,2BAA0D,OAAO;AAAA,IACrE,wBAAwB;AAAA,MAAI,CAAC,QAAQ,UACnC,OAAO,WAAW,cAAc,OAAO,QAAQ,CAAC,qBAAqB,KAAK,EAAE,IAAI,IAAI;AAAA,IACtF;AAAA,EACF;AAEA,SAAO,iBAAiB;AAAA,IACtB;AAAA,IACA,YAAY,WAAW;AAAA,IACvB;AAAA,IACA,UACE;AAAA,MAAC;AAAA;AAAA,QACC,YAAY,WAAW;AAAA,QACvB,mBAAmB,oCAAoC;AAAA,UACrD,aAAa,YAAY,IAAI,0BAA0B,EAAE,YAAY,WAAW,KAAK,CAAC,CAAC,GAAG;AAAA,QAC5F,CAAC;AAAA,QACD,oBAAoB,2BAA2B,EAAE,SAAS,aAAa,gBAAgB,WAAW,KAAK,CAAC;AAAA,QACxG,yBAAyB,uBAAuB;AAAA,QAChD,qBAAqB,uBAAuB;AAAA,QAC5C;AAAA;AAAA,IACF;AAAA,EAEJ,CAAC;AACH;AAWA,SAAS,eAAe,KAAqE;AAC3F,QAAM,SAAS,IAAI,gBAAgB;AACnC,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,GAAG,GAAG;AAC9C,QAAI,UAAU,OAAW;AACzB,eAAW,SAAS,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,KAAK,EAAG,QAAO,OAAO,KAAK,KAAK;AAAA,EACtF;AACA,SAAO;AACT;","names":["auth","numItems"]}
@@ -0,0 +1,15 @@
1
+ // src/NextAdminLayout.tsx
2
+ import { ADMIN_COOKIE_NAME, parseAdminCookie } from "@vexcms/core";
3
+ import { NextAdminLayoutClient } from "./NextAdminLayoutClient";
4
+ import { cookies } from "next/headers";
5
+ import { jsx } from "react/jsx-runtime";
6
+ async function NextAdminLayout(props) {
7
+ const cookieStore = await cookies();
8
+ const sidebarOpen = parseAdminCookie({ cookieValue: cookieStore.get(ADMIN_COOKIE_NAME)?.value }).sidebarOpen ?? false;
9
+ return /* @__PURE__ */ jsx(NextAdminLayoutClient, { authPromise: props.authPromise, sidebarOpen, children: props.children });
10
+ }
11
+
12
+ export {
13
+ NextAdminLayout
14
+ };
15
+ //# sourceMappingURL=chunk-NE2VUFS2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/NextAdminLayout.tsx"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport { ADMIN_COOKIE_NAME, parseAdminCookie, type VexApiAuth } from \"@vexcms/core\";\nimport { NextAdminLayoutClient } from \"./NextAdminLayoutClient\";\nimport { cookies } from \"next/headers\";\n\n/**\n * Next.js admin layout shell for VexCMS.\n *\n * A **server component** that reads the consolidated `vex-admin` cookie's\n * `sidebarOpen` field server-side (Decision 51; previously a dedicated\n * `sidebar_state` cookie) so the sidebar's open/closed state is correct on\n * first paint, with no client flash, and passes it, plus `authPromise`/\n * `children`, to the\n * client leaf `NextAdminLayoutClient`. Config no longer crosses this\n * boundary at all — the admin panel's client tree gets its config from the\n * app's own root `VexConfigProvider` mount\n * (`components/providers/client.tsx`), not from a prop threaded through this\n * layout.\n *\n * Does **not** await `authPromise` itself (perf fix, Decision 44/streaming) — only\n * `cookies()` (fast, no network). The sidebar resolves `authPromise` inside its\n * OWN `<Suspense>` boundary (`AdminLayout`, `@vexcms/react`), so a slow\n * `getUserOrg()` round trip only delays the sidebar nav, never `children` or\n * this layout's own static chrome.\n *\n * Render it from your admin `layout.tsx`. Because this is a server component,\n * your `layout.tsx` may itself call server-only auth helpers to build\n * `authPromise` (e.g. `getUserOrg()` from `~/auth/server`) — started, not\n * awaited, so `layout.tsx` itself stays synchronous and streams immediately.\n *\n * @param props - Layout props.\n * @param props.children - The page content from `[[...slug]]/page.tsx`.\n * @param props.authPromise - The in-flight `{ user, organization }` fetch (Decision 44),\n * optional — omit it entirely when `config.access` is absent/disabled.\n * @returns The rendered admin shell, wrapping `children` in the client boundary\n * ({@link NextAdminLayoutClient}) with the server-read sidebar cookie state.\n * @example\n * ```tsx\n * // app/admin/layout.tsx\n * import { NextAdminLayout } from \"@vexcms/next/client\";\n * import { getUserOrg } from \"~/auth/server\";\n *\n * export default function AdminLayout({ children }: { children: ReactNode }) {\n * return (\n * <NextAdminLayout authPromise={getUserOrg()}>\n * {children}\n * </NextAdminLayout>\n * );\n * }\n * ```\n */\nexport async function NextAdminLayout(props: {\n children: ReactNode;\n authPromise?: Promise<VexApiAuth>;\n}) {\n const cookieStore = await cookies();\n const sidebarOpen = parseAdminCookie({ cookieValue: cookieStore.get(ADMIN_COOKIE_NAME)?.value }).sidebarOpen ?? false;\n\n return (\n <NextAdminLayoutClient authPromise={props.authPromise} sidebarOpen={sidebarOpen}>\n {props.children}\n </NextAdminLayoutClient>\n );\n}\n"],"mappings":";AACA,SAAS,mBAAmB,wBAAyC;AACrE,SAAS,6BAA6B;AACtC,SAAS,eAAe;AAwDpB;AARJ,eAAsB,gBAAgB,OAGnC;AACD,QAAM,cAAc,MAAM,QAAQ;AAClC,QAAM,cAAc,iBAAiB,EAAE,aAAa,YAAY,IAAI,iBAAiB,GAAG,MAAM,CAAC,EAAE,eAAe;AAEhH,SACE,oBAAC,yBAAsB,aAAa,MAAM,aAAa,aACpD,gBAAM,UACT;AAEJ;","names":[]}
@@ -0,0 +1,21 @@
1
+ "use client";
2
+
3
+ // src/auth/VexAdminAuthAdapterContext.tsx
4
+ import { createContext, useContext } from "react";
5
+ import { jsx } from "react/jsx-runtime";
6
+ var VexAdminAuthAdapterContext = createContext(
7
+ void 0
8
+ );
9
+ function VexAdminAuthAdapterProvider(props) {
10
+ return /* @__PURE__ */ jsx(VexAdminAuthAdapterContext.Provider, { value: props.value, children: props.children });
11
+ }
12
+ function useVexAdminAuthAdapter() {
13
+ return useContext(VexAdminAuthAdapterContext);
14
+ }
15
+
16
+ export {
17
+ VexAdminAuthAdapterContext,
18
+ VexAdminAuthAdapterProvider,
19
+ useVexAdminAuthAdapter
20
+ };
21
+ //# sourceMappingURL=chunk-T7DNJLSX.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/auth/VexAdminAuthAdapterContext.tsx"],"sourcesContent":["\"use client\";\n// \"use client\" is also injected by the tsup build banner for this entry (see\n// tsup.config.ts) so the emitted module carries the client boundary — written\n// explicitly here too so `tsc`/vitest (which never run tsup) see it.\n\nimport { createContext, useContext, type ReactNode } from \"react\";\nimport type { FrameworkAuthAdapter } from \"@vexcms/react\";\n\n/**\n * Carries the app's sign-in/out actions (`@vexcms/react`'s `FrameworkComponents.auth`,\n * Next.js adapter) from wherever the app mounts {@link VexAdminAuthAdapterProvider} down\n * to `NextAdminLayoutClient`, which forwards it into `AdminLayout`'s `components` prop.\n *\n * A context, not a prop threaded through `NextAdminLayout`/your admin `layout.tsx`,\n * because `layout.tsx` and `NextAdminLayout` are BOTH server components — the sign-in\n * href builder and `signOut` function are app-specific client values (closures over your\n * own `~/auth/client` module) that cannot cross a Server→Client Component boundary as\n * props. Mounting this provider anywhere above `NextAdminLayout` in your own CLIENT tree\n * (e.g. alongside `VexConfigProvider`/your Better Auth client provider) sidesteps that\n * boundary entirely — both ends are client components.\n *\n * `undefined` default (not an empty object) distinguishes \"no provider mounted\" from \"a\n * provider mounted with no adapter\" — `AdminTopNav`'s own control renders nothing either\n * way, but the distinction matters for anyone debugging why it's missing.\n */\nexport const VexAdminAuthAdapterContext = createContext<FrameworkAuthAdapter | undefined>(\n undefined,\n);\n\n/**\n * Mounts the sign-in/out adapter for the admin panel's top-bar control.\n *\n * @example\n * ```tsx\n * // ~/components/providers/client.tsx (\"use client\")\n * import { VexAdminAuthAdapterProvider } from \"@vexcms/next/auth\";\n * import { useRouter } from \"next/navigation\";\n * import { signOut } from \"~/auth/client\";\n *\n * function AuthAdapterProvider({ children }: { children: React.ReactNode }) {\n * const router = useRouter();\n * return (\n * <VexAdminAuthAdapterProvider\n * value={{\n * signInHref: (redirectTo) => `/auth/sign-in?redirectTo=${encodeURIComponent(redirectTo)}`,\n * signOut: () =>\n * signOut()\n * .then(() => router.refresh())\n * .catch(() => router.refresh()), // a stale client cache after a failed\n * // sign-out is worse than a redundant refresh — refresh either way.\n * }}\n * >\n * {children}\n * </VexAdminAuthAdapterProvider>\n * );\n * }\n * ```\n *\n * @param props.value - The sign-in href builder and `signOut` function.\n * @param props.children - The subtree that should see this adapter — must contain\n * `NextAdminLayout` for `AdminTopNav`'s control to pick it up.\n * @returns The context provider wrapping `props.children`.\n */\nexport function VexAdminAuthAdapterProvider(props: {\n value: FrameworkAuthAdapter;\n children: ReactNode;\n}) {\n return (\n <VexAdminAuthAdapterContext.Provider value={props.value}>\n {props.children}\n </VexAdminAuthAdapterContext.Provider>\n );\n}\n\n/**\n * Reads the sign-in/out adapter mounted by {@link VexAdminAuthAdapterProvider}, if any.\n * Consumed internally by `NextAdminLayoutClient` — not typically called from app code.\n *\n * @returns The adapter, or `undefined` when no provider is mounted above this point.\n */\nexport function useVexAdminAuthAdapter(): FrameworkAuthAdapter | undefined {\n return useContext(VexAdminAuthAdapterContext);\n}\n"],"mappings":";;;AAKA,SAAS,eAAe,kBAAkC;AA+DtD;AA3CG,IAAM,6BAA6B;AAAA,EACxC;AACF;AAoCO,SAAS,4BAA4B,OAGzC;AACD,SACE,oBAAC,2BAA2B,UAA3B,EAAoC,OAAO,MAAM,OAC/C,gBAAM,UACT;AAEJ;AAQO,SAAS,yBAA2D;AACzE,SAAO,WAAW,0BAA0B;AAC9C;","names":[]}
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  NextAdminPage
3
- } from "./chunk-DDH2TJTP.js";
3
+ } from "./chunk-JB3GMZPF.js";
4
4
  import {
5
5
  NextAdminLayout
6
- } from "./chunk-UMJTAD5A.js";
6
+ } from "./chunk-NE2VUFS2.js";
7
7
  import {
8
8
  createVexRevalidateRoute,
9
9
  createVexServerClient
@@ -0,0 +1,3 @@
1
+ export { useNextNavigationBlocker } from "./useNextNavigationBlocker";
2
+ export { useNextNavigate } from "./useNextNavigate";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/navigation/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AACtE,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Next.js App Router implementation of `FrameworkComponents["useNavigate"]`
3
+ * (`packages/react/src/config/useFrameworkComponents.ts`, Step 7).
4
+ *
5
+ * Returns the App Router's own `router.push`, unwrapped — guarding is not
6
+ * this hook's job. `@vexcms/react`'s `useVexNavigate`
7
+ * (`packages/react/src/config/useVexNavigate.ts`) is the only caller: it
8
+ * consults `useUnsavedChangesGuard`'s blocking state itself before invoking
9
+ * the function this hook returns, so every built-in post-mutation
10
+ * navigation (after delete, after a duplicate's create resolves) is guarded
11
+ * without this hook knowing anything about dirty state.
12
+ *
13
+ * Provide it from `NextAdminLayoutClient`:
14
+ * `components={{ …, useNavigate: useNextNavigate }}`.
15
+ *
16
+ * @returns A function that navigates to `href` via the App Router.
17
+ */
18
+ export declare function useNextNavigate(): (href: string) => void;
19
+ //# sourceMappingURL=useNextNavigate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useNextNavigate.d.ts","sourceRoot":"","sources":["../../src/navigation/useNextNavigate.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,eAAe,IAAI,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAGxD"}
@@ -0,0 +1,32 @@
1
+ import type { NavigationBlockerAdapter } from "@vexcms/react";
2
+ /**
3
+ * Next.js App Router implementation of `NavigationBlockerAdapter["useBlocker"]`.
4
+ *
5
+ * The App Router has no `routeChangeStart`-equivalent event and (spec decision 3)
6
+ * programmatic navigation cannot be intercepted by patching
7
+ * `history.pushState`/`replaceState` — Next's own `HistoryUpdater` calls
8
+ * `pushState` from a `useInsertionEffect` AFTER the route has already
9
+ * committed, and patches those methods itself. This hook therefore covers
10
+ * only the two surfaces that fire BEFORE a route change commits: same-origin
11
+ * `<a>` clicks (capture-phase `click` listener) and browser back/forward (a
12
+ * `popstate` listener plus a duplicate-history-entry trap). Programmatic
13
+ * navigation is guarded separately by `@vexcms/react`'s `useVexNavigate`,
14
+ * which consults the guard itself before calling this adapter's
15
+ * `useNavigate` (`useNextNavigate`, this package). `beforeunload` (tab close
16
+ * / hard reload) is also NOT handled here — `@vexcms/react`'s
17
+ * `useUnsavedChangesGuard` registers it directly, framework-independent.
18
+ *
19
+ * Provide the resulting adapter from `NextAdminLayoutClient`:
20
+ * `components={{ …, navigationBlocker: { useBlocker: useNextNavigationBlocker } }}`.
21
+ * `useUnsavedChangesGuard` then calls it once per mounted edit view with the
22
+ * live dirty/autosave state.
23
+ *
24
+ * @param props - See `NavigationBlockerAdapter["useBlocker"]` in
25
+ * `@vexcms/react` (`packages/react/src/config/useFrameworkComponents.ts`).
26
+ * @returns Nothing — every intercepted navigation surfaces through
27
+ * `props.onBlock`, never a return value.
28
+ * @throws Never under normal operation; a blocked navigation reports through
29
+ * `onBlock`, not a throw.
30
+ */
31
+ export declare function useNextNavigationBlocker(props: Parameters<NavigationBlockerAdapter["useBlocker"]>[0]): void;
32
+ //# sourceMappingURL=useNextNavigationBlocker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useNextNavigationBlocker.d.ts","sourceRoot":"","sources":["../../src/navigation/useNextNavigationBlocker.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAC;AAE9D;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,UAAU,CAAC,wBAAwB,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,GAC3D,IAAI,CAgIN"}
@@ -0,0 +1,48 @@
1
+ import { QueryClient } from "@tanstack/react-query";
2
+ import type { FunctionReference } from "convex/server";
3
+ /**
4
+ * Builds the SAME query key shape `@convex-dev/react-query`'s `convexQuery(funcRef, args)`
5
+ * produces (`["convexQuery", functionName, args]`) — WITHOUT importing `convex/react` (its
6
+ * own `ConvexQueryClient`/`ConvexReactClient` transitively call `React.createContext`, a
7
+ * client-only API that crashes a Server Component import graph: "createContext only works
8
+ * in Client Components", confirmed live). `getFunctionName`/`convexToJson` are the same
9
+ * underlying helpers `convexQuery` itself uses, from `convex/server`/`convex/values` — both
10
+ * server-safe, pure metadata/serialization functions with no live client involved.
11
+ *
12
+ * Required for `<HydrationBoundary>` to be found at all by the client's own
13
+ * `useSuspenseQuery(convexQuery(sameFn, sameArgs))` call — the two sides must agree on this
14
+ * key byte-for-byte (after hashing, below) or hydration silently misses and the client
15
+ * re-fetches from scratch.
16
+ *
17
+ * @param funcRef - The Convex query function reference (e.g. `vexConvexApi.findPaginated`).
18
+ * @param args - Its arguments.
19
+ * @returns The `["convexQuery", functionName, args]` key tuple.
20
+ */
21
+ export declare function convexServerQueryKey(funcRef: FunctionReference<"query">, args: Record<string, unknown>): readonly [string, string, Record<string, unknown>];
22
+ /**
23
+ * Per-request `QueryClient` for the TanStack Query v5 + Next.js App Router streaming-SSR
24
+ * pattern (https://tanstack.com/query/v5/docs/framework/react/guides/advanced-ssr#streaming-with-server-components):
25
+ * `NextAdminPage` calls `queryClient.prefetchQuery(...)` WITHOUT awaiting it, then wraps its
26
+ * return value in `<HydrationBoundary state={dehydrate(queryClient)}>` — the still-pending
27
+ * promise streams to the client as a LATER chunk instead of blocking the whole response, and
28
+ * the client's own `useSuspenseQuery(convexQuery(...))` (same `queryKey`) picks it up via
29
+ * `<HydrationBoundary>`'s hydration (ambient `QueryClientProvider`, mounted by the app's own
30
+ * client providers — this server-only client is NEVER rendered to the page, it only exists to
31
+ * prefetch into and dehydrate).
32
+ *
33
+ * `cache()`-scoped so every `NextAdminPage`/`page.tsx` call within ONE request shares the SAME
34
+ * client (one `dehydrate()` at the end serializes every query prefetched anywhere in the
35
+ * request), and a NEW one is built for the next request — no cross-request cache leak (the same
36
+ * reasoning as `apps/*‍/src/components/providers/convex.tsx`'s own server branch).
37
+ *
38
+ * `shouldDehydrateQuery` additionally includes PENDING queries (TanStack's own default only
39
+ * dehydrates settled ones) — required for the "don't await the prefetch" half of the pattern;
40
+ * without it, an unawaited `prefetchQuery` call would dehydrate nothing and the client would
41
+ * silently re-fetch from scratch, defeating the whole point.
42
+ *
43
+ * Every actual server-side fetch goes through `fetchQuery` (`convex/nextjs`) as the
44
+ * `prefetchQuery` call's own `queryFn` — this module never touches `convex/react`/a live
45
+ * `ConvexReactClient` at all (see `convexServerQueryKey`'s doc comment).
46
+ */
47
+ export declare const getServerQueryClient: () => QueryClient;
48
+ //# sourceMappingURL=serverQueryClient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serverQueryClient.d.ts","sourceRoot":"","sources":["../src/serverQueryClient.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwC,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAG1F,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAGvD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,iBAAiB,CAAC,OAAO,CAAC,EACnC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAEpD;AAmBD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,oBAAoB,QAAa,WA6B5C,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vexcms/next",
3
- "version": "0.1.0-alpha.26",
3
+ "version": "0.1.0-alpha.28",
4
4
  "description": "Next.js adapter and admin routes for VexCMS",
5
5
  "keywords": [
6
6
  "cms",
@@ -43,6 +43,11 @@
43
43
  "import": "./dist/NextAdminLayout.js",
44
44
  "types": "./dist/NextAdminLayout.d.ts"
45
45
  },
46
+ "./auth": {
47
+ "source": "./src/auth/index.ts",
48
+ "import": "./dist/auth/index.js",
49
+ "types": "./dist/auth/index.d.ts"
50
+ },
46
51
  "./cache": {
47
52
  "source": "./src/cache/index.ts",
48
53
  "import": "./dist/cache/index.js",
@@ -66,20 +71,22 @@
66
71
  },
67
72
  "dependencies": {
68
73
  "lucide-react": "0.577.0",
69
- "@vexcms/core": "0.1.0-alpha.26",
70
- "@vexcms/react": "0.1.0-alpha.26"
74
+ "@vexcms/core": "0.1.0-alpha.28",
75
+ "@vexcms/react": "0.1.0-alpha.28"
71
76
  },
72
77
  "devDependencies": {
73
78
  "@convex-dev/react-query": "0.1.0",
74
79
  "@tanstack/react-form": "1.33.1",
75
80
  "@tanstack/react-query": "5.101.2",
76
81
  "@tanstack/react-table": "8.21.3",
82
+ "@testing-library/react": "16.3.2",
77
83
  "@types/node": "20.19.43",
78
84
  "@types/react": "19.2.17",
79
85
  "@types/react-dom": "19.2.3",
80
86
  "@vitest/coverage-v8": "4.1.10",
81
87
  "convex": "1.44.0",
82
- "next": "16.3.3",
88
+ "jsdom": "26.1.0",
89
+ "next": "16.4.3",
83
90
  "nuqs": "2.9.0",
84
91
  "react": "19.2.7",
85
92
  "react-dom": "19.2.7",
@@ -1,195 +0,0 @@
1
- // src/NextAdminPage.tsx
2
- import {
3
- appendLivePreviewParams,
4
- livePreviewLayoutCookieName,
5
- livePreviewPanelCookieName,
6
- readLivePreviewLayoutCookie,
7
- readLivePreviewPanelCookie,
8
- resolveLivePreviewSettings,
9
- vexConvexApi
10
- } from "@vexcms/core";
11
- import {
12
- CollectionEditView,
13
- CollectionListView,
14
- DashboardView,
15
- GlobalEditView,
16
- GlobalsListView,
17
- MediaCollectionEditView,
18
- MediaCollectionListView
19
- } from "@vexcms/react";
20
- import { fetchQuery } from "convex/nextjs";
21
- import { cookies } from "next/headers";
22
- import { jsx, jsxs } from "react/jsx-runtime";
23
- async function resolveInitialPreviewUrl(props) {
24
- const url = await fetchQuery(
25
- vexConvexApi.livePreviewUrl,
26
- props.args,
27
- props.token ? { token: props.token } : void 0
28
- );
29
- if (!url) return void 0;
30
- return appendLivePreviewParams({ url, collectionSlug: props.slug });
31
- }
32
- async function NextAdminPage(props) {
33
- const { path = [] } = await props.params;
34
- const [collectionSlug, documentId] = path;
35
- const cookieStore = await cookies();
36
- if (!collectionSlug) {
37
- return /* @__PURE__ */ jsx(DashboardView, {});
38
- }
39
- if (collectionSlug === "globals") {
40
- if (!documentId) {
41
- return /* @__PURE__ */ jsx(GlobalsListView, {});
42
- }
43
- const globalConfig = props.config.globals.find((g) => g.slug === documentId);
44
- if (!globalConfig) {
45
- return /* @__PURE__ */ jsxs("div", { children: [
46
- /* @__PURE__ */ jsxs("p", { className: "text-muted-foreground p-6", children: [
47
- 'Global "',
48
- documentId,
49
- '" not found.'
50
- ] }),
51
- /* @__PURE__ */ jsx("p", { children: "TODO: add not found view" })
52
- ] });
53
- }
54
- const global = await fetchQuery(
55
- vexConvexApi.globals.get,
56
- // Same args as `GlobalEditView`'s client query (`getGlobal` falls back to
57
- // the published row for a caller without `readDrafts`).
58
- { slug: globalConfig.slug, drafts: globalConfig.versions.drafts },
59
- props.token ? { token: props.token } : void 0
60
- );
61
- const globalLivePreview = resolveLivePreviewSettings({
62
- config: props.config.admin.livePreview,
63
- kind: "global",
64
- slug: globalConfig.slug,
65
- admin: globalConfig.admin.livePreview
66
- });
67
- return /* @__PURE__ */ jsx(
68
- GlobalEditView,
69
- {
70
- global: globalConfig.slug,
71
- initialData: global,
72
- initialPreviewPanelOpen: globalLivePreview !== void 0 && readLivePreviewPanelCookie({
73
- cookieValue: cookieStore.get(livePreviewPanelCookieName({ slug: globalConfig.slug }))?.value,
74
- defaultOpen: globalLivePreview.defaultOpen
75
- }),
76
- initialPreviewPanelSize: readLivePreviewLayoutCookie({
77
- cookieValue: cookieStore.get(livePreviewLayoutCookieName({ slug: globalConfig.slug }))?.value
78
- }),
79
- initialPreviewUrl: globalLivePreview === void 0 ? void 0 : await resolveInitialPreviewUrl({
80
- args: { collection: globalConfig.slug, kind: "global", values: {} },
81
- slug: globalConfig.slug,
82
- token: props.token
83
- })
84
- }
85
- );
86
- }
87
- const collection = props.config.collections.find((c) => c.slug === collectionSlug);
88
- const mediaCollection = props.config.mediaCollections.find((mc) => mc.slug === collectionSlug);
89
- if (!collection && !mediaCollection) {
90
- return /* @__PURE__ */ jsxs("div", { children: [
91
- /* @__PURE__ */ jsxs("p", { className: "text-muted-foreground p-6", children: [
92
- 'Collection "',
93
- collectionSlug,
94
- '" not found.'
95
- ] }),
96
- /* @__PURE__ */ jsx("p", { children: "TODO: add not found view" })
97
- ] });
98
- }
99
- if (mediaCollection && documentId) {
100
- const initialData2 = await fetchQuery(
101
- vexConvexApi.get,
102
- { id: documentId, collection: mediaCollection.slug },
103
- props.token ? { token: props.token } : void 0
104
- );
105
- return /* @__PURE__ */ jsx(
106
- MediaCollectionEditView,
107
- {
108
- collection: mediaCollection.slug,
109
- documentId,
110
- initialData: initialData2
111
- }
112
- );
113
- }
114
- if (mediaCollection) {
115
- const numItems2 = Math.max(
116
- mediaCollection.admin.table.serverPageSize,
117
- mediaCollection.admin.table.defaultPageSize
118
- );
119
- const initialData2 = await fetchQuery(
120
- vexConvexApi.find,
121
- {
122
- collection: collectionSlug,
123
- paginationOpts: { numItems: numItems2, totalDocs: true, cursor: null }
124
- },
125
- props.token ? { token: props.token } : void 0
126
- );
127
- return /* @__PURE__ */ jsx(
128
- MediaCollectionListView,
129
- {
130
- collection: mediaCollection.slug,
131
- initialData: initialData2
132
- }
133
- );
134
- }
135
- if (collection && documentId) {
136
- const initialData2 = await fetchQuery(
137
- vexConvexApi.get,
138
- // Same args as `CollectionEditView`'s client query (`get` falls back to
139
- // published-only for a caller without `readDrafts`).
140
- { id: documentId, collection: collection.slug, drafts: collection.versions.drafts },
141
- props.token ? { token: props.token } : void 0
142
- );
143
- const collectionLivePreview = resolveLivePreviewSettings({
144
- config: props.config.admin.livePreview,
145
- kind: "collection",
146
- slug: collection.slug,
147
- admin: collection.admin.livePreview
148
- });
149
- return /* @__PURE__ */ jsx(
150
- CollectionEditView,
151
- {
152
- collection: collection.slug,
153
- documentId,
154
- initialData: initialData2,
155
- initialPreviewPanelOpen: collectionLivePreview !== void 0 && readLivePreviewPanelCookie({
156
- cookieValue: cookieStore.get(livePreviewPanelCookieName({ slug: collection.slug }))?.value,
157
- defaultOpen: collectionLivePreview.defaultOpen
158
- }),
159
- initialPreviewPanelSize: readLivePreviewLayoutCookie({
160
- cookieValue: cookieStore.get(livePreviewLayoutCookieName({ slug: collection.slug }))?.value
161
- }),
162
- initialPreviewUrl: collectionLivePreview === void 0 ? void 0 : await resolveInitialPreviewUrl({
163
- args: { collection: collection.slug, kind: "collection", documentId, values: {} },
164
- slug: collection.slug,
165
- token: props.token
166
- })
167
- }
168
- );
169
- }
170
- if (!collection) {
171
- throw new Error("invalid collection slug");
172
- }
173
- const numItems = Math.max(
174
- collection.admin.table.serverPageSize,
175
- collection.admin.table.defaultPageSize
176
- );
177
- const initialData = await fetchQuery(
178
- vexConvexApi.findPaginated,
179
- {
180
- collection: collectionSlug,
181
- // Same args as `CollectionListView`'s client query, so the hydrated
182
- // initial data belongs to the query the client subscribes to.
183
- drafts: collection.versions.drafts ? true : void 0,
184
- depth: 1,
185
- paginationOpts: { cursor: null, numItems, totalDocs: true }
186
- },
187
- props.token ? { token: props.token } : void 0
188
- );
189
- return /* @__PURE__ */ jsx(CollectionListView, { collection: collection.slug, initialData });
190
- }
191
-
192
- export {
193
- NextAdminPage
194
- };
195
- //# sourceMappingURL=chunk-DDH2TJTP.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/NextAdminPage.tsx"],"sourcesContent":["import type { CollectionSlug, PaginationResult, VexConfig, VexMediaDocument } from \"@vexcms/core\";\n\nimport {\n appendLivePreviewParams,\n livePreviewLayoutCookieName,\n livePreviewPanelCookieName,\n readLivePreviewLayoutCookie,\n readLivePreviewPanelCookie,\n resolveLivePreviewSettings,\n vexConvexApi,\n} from \"@vexcms/core\";\nimport {\n CollectionEditView,\n CollectionListView,\n DashboardView,\n GlobalEditView,\n GlobalsListView,\n MediaCollectionEditView,\n MediaCollectionListView,\n} from \"@vexcms/react\";\nimport { fetchQuery } from \"convex/nextjs\";\nimport { cookies } from \"next/headers\";\n\n/**\n * Resolves a `{ server }` preview URL for first paint.\n *\n * Two things the caller must not have to remember. First, the query only\n * answers for the `{ server }` form — the string and client forms resolve\n * synchronously in the browser and return `null` here, so the iframe falls back\n * to its own resolution. Second, the resolved URL is a plain public URL: it\n * needs `vexLivePreview=1` appended or the preview loads an ordinary page that\n * listens to nothing, which looks like \"live preview is broken\" with no error\n * anywhere. Appending it here keeps the server path identical to the client one.\n *\n * @param props.args - Query arguments identifying the document or global.\n * @param props.slug - Collection or global slug, for the temp-id params.\n * @param props.token - Auth token forwarded to Convex.\n * @returns The preview-ready URL, or `undefined` when the config resolves in\n * the browser instead.\n */\nasync function resolveInitialPreviewUrl(props: {\n args: Parameters<typeof fetchQuery<typeof vexConvexApi.livePreviewUrl>>[1];\n slug: string;\n token?: string;\n}): Promise<string | undefined> {\n const url = await fetchQuery(\n vexConvexApi.livePreviewUrl,\n props.args,\n props.token ? { token: props.token } : undefined,\n );\n if (!url) return undefined;\n\n return appendLivePreviewParams({ url, collectionSlug: props.slug });\n}\n\n/**\n * VexCMS admin page server component for Next.js.\n *\n * An `async` server component that routes by the `[[...slug]]` catch-all\n * params, prefetches Convex data via `fetchQuery`, and renders the correct\n * view component. Does **not** include a layout wrapper — `NextAdminLayout`\n * in `app/admin/layout.tsx` owns the persistent shell.\n *\n * **Route mapping:**\n * | `path` array | View |\n * |---|---|\n * | `[]` or undefined | `DashboardView` |\n * | `[collectionSlug]` | `CollectionListView` with preloaded docs |\n * | `[collectionSlug, \"new\"]` | `CollectionEditView` (empty form) |\n * | `[collectionSlug, documentId]` | `CollectionEditView` with preloaded doc |\n *\n * @param props - Component props\n * @param props.config - The resolved server `VexConfig`, typically the default export of\n * `vex.config.server.ts` — not the client-safe `vex.config.ts`.\n * @param props.params - Next.js 15 async params `{ path?: string[] }`\n * @param props.token - Optional session token forwarded to `fetchQuery` for authenticated reads.\n * @returns The appropriate admin view for the current URL path.\n *\n * @example\n * ```tsx\n * // app/admin/[[...slug]]/page.tsx\n * import { NextAdminPage } from \"@vexcms/next/server\";\n * import config from \"../../../../vex.config.server\";\n *\n * export default function AdminPage({\n * params,\n * }: {\n * params: Promise<{ path?: string[] }>;\n * }) {\n * return <NextAdminPage config={config} params={params} />;\n * }\n * ```\n */\nexport async function NextAdminPage(props: {\n config: VexConfig;\n params: Promise<{ path?: string[] }>;\n token?: string;\n}) {\n const { path = [] } = await props.params;\n const [collectionSlug, documentId] = path;\n const cookieStore = await cookies();\n\n if (!collectionSlug) {\n return <DashboardView />;\n }\n\n if (collectionSlug === \"globals\") {\n if (!documentId) {\n return <GlobalsListView />;\n }\n const globalConfig = props.config.globals.find((g) => g.slug === documentId);\n if (!globalConfig) {\n return (\n <div>\n <p className=\"text-muted-foreground p-6\">Global &quot;{documentId}&quot; not found.</p>\n <p>TODO: add not found view</p>\n </div>\n );\n }\n const global = await fetchQuery(\n vexConvexApi.globals.get,\n // Same args as `GlobalEditView`'s client query (`getGlobal` falls back to\n // the published row for a caller without `readDrafts`).\n { slug: globalConfig.slug, drafts: globalConfig.versions.drafts },\n props.token ? { token: props.token } : undefined,\n );\n const globalLivePreview = resolveLivePreviewSettings({\n config: props.config.admin.livePreview,\n kind: \"global\",\n slug: globalConfig.slug,\n admin: globalConfig.admin.livePreview,\n });\n return (\n <GlobalEditView\n global={globalConfig.slug}\n initialData={global}\n initialPreviewPanelOpen={\n globalLivePreview !== undefined &&\n readLivePreviewPanelCookie({\n cookieValue: cookieStore.get(livePreviewPanelCookieName({ slug: globalConfig.slug }))\n ?.value,\n defaultOpen: globalLivePreview.defaultOpen,\n })\n }\n initialPreviewPanelSize={readLivePreviewLayoutCookie({\n cookieValue: cookieStore.get(livePreviewLayoutCookieName({ slug: globalConfig.slug }))\n ?.value,\n })}\n initialPreviewUrl={\n globalLivePreview === undefined\n ? undefined\n : await resolveInitialPreviewUrl({\n args: { collection: globalConfig.slug, kind: \"global\", values: {} },\n slug: globalConfig.slug,\n token: props.token,\n })\n }\n />\n );\n }\n\n const collection = props.config.collections.find((c) => c.slug === collectionSlug);\n const mediaCollection = props.config.mediaCollections.find((mc) => mc.slug === collectionSlug);\n\n if (!collection && !mediaCollection) {\n return (\n <div>\n <p className=\"text-muted-foreground p-6\">\n Collection &quot;{collectionSlug}&quot; not found.\n </p>\n <p>TODO: add not found view</p>\n </div>\n );\n }\n\n if (mediaCollection && documentId) {\n const initialData = await fetchQuery(\n vexConvexApi.get,\n { id: documentId, collection: mediaCollection.slug },\n props.token ? { token: props.token } : undefined,\n );\n return (\n <MediaCollectionEditView\n collection={mediaCollection.slug}\n documentId={documentId}\n initialData={initialData as null | VexMediaDocument}\n />\n );\n }\n\n if (mediaCollection) {\n const numItems = Math.max(\n mediaCollection.admin.table.serverPageSize,\n mediaCollection.admin.table.defaultPageSize,\n );\n const initialData = await fetchQuery(\n vexConvexApi.find,\n {\n collection: collectionSlug as CollectionSlug,\n paginationOpts: { numItems, totalDocs: true, cursor: null },\n },\n props.token ? { token: props.token } : undefined,\n );\n return (\n <MediaCollectionListView\n collection={mediaCollection.slug}\n initialData={initialData as PaginationResult<VexMediaDocument>}\n />\n );\n }\n\n if (collection && documentId) {\n const initialData = await fetchQuery(\n vexConvexApi.get,\n // Same args as `CollectionEditView`'s client query (`get` falls back to\n // published-only for a caller without `readDrafts`).\n { id: documentId, collection: collection.slug, drafts: collection.versions.drafts },\n props.token ? { token: props.token } : undefined,\n );\n const collectionLivePreview = resolveLivePreviewSettings({\n config: props.config.admin.livePreview,\n kind: \"collection\",\n slug: collection.slug,\n admin: collection.admin.livePreview,\n });\n return (\n <CollectionEditView\n collection={collection.slug}\n documentId={documentId}\n initialData={initialData}\n initialPreviewPanelOpen={\n collectionLivePreview !== undefined &&\n readLivePreviewPanelCookie({\n cookieValue: cookieStore.get(livePreviewPanelCookieName({ slug: collection.slug }))\n ?.value,\n defaultOpen: collectionLivePreview.defaultOpen,\n })\n }\n initialPreviewPanelSize={readLivePreviewLayoutCookie({\n cookieValue: cookieStore.get(livePreviewLayoutCookieName({ slug: collection.slug }))\n ?.value,\n })}\n initialPreviewUrl={\n collectionLivePreview === undefined\n ? undefined\n : await resolveInitialPreviewUrl({\n args: { collection: collection.slug, kind: \"collection\", documentId, values: {} },\n slug: collection.slug,\n token: props.token,\n })\n }\n />\n );\n }\n\n if (!collection) {\n throw new Error(\"invalid collection slug\");\n }\n\n const numItems = Math.max(\n collection.admin.table.serverPageSize,\n collection.admin.table.defaultPageSize,\n );\n const initialData = await fetchQuery(\n vexConvexApi.findPaginated,\n {\n collection: collectionSlug as CollectionSlug,\n // Same args as `CollectionListView`'s client query, so the hydrated\n // initial data belongs to the query the client subscribes to.\n drafts: collection.versions.drafts ? true : undefined,\n depth: 1,\n paginationOpts: { cursor: null, numItems, totalDocs: true },\n },\n props.token ? { token: props.token } : undefined,\n );\n return <CollectionListView collection={collection.slug} initialData={initialData} />;\n}\n"],"mappings":"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","names":["initialData","numItems"]}