diff --git a/.changeset/better-forks-ring.md b/.changeset/better-forks-ring.md new file mode 100644 index 00000000000..6b4ba92156f --- /dev/null +++ b/.changeset/better-forks-ring.md @@ -0,0 +1,5 @@ +--- +"next-sanity": major +--- + +Remove `type DraftPerspective`, `type DraftEnvironment` and `type ClientPerspective` from `import 'next-sanity/hooks'` diff --git a/.changeset/bright-jokes-stay.md b/.changeset/bright-jokes-stay.md new file mode 100644 index 00000000000..3204bdba86c --- /dev/null +++ b/.changeset/bright-jokes-stay.md @@ -0,0 +1,5 @@ +--- +"next-sanity": major +--- + +Remove the experimental `` API diff --git a/.changeset/fair-rats-sell.md b/.changeset/fair-rats-sell.md new file mode 100644 index 00000000000..cb32c8d6e4e --- /dev/null +++ b/.changeset/fair-rats-sell.md @@ -0,0 +1,5 @@ +--- +"next-sanity": patch +--- + +Add temp debug function diff --git a/.changeset/four-jobs-type.md b/.changeset/four-jobs-type.md new file mode 100644 index 00000000000..afc60d24202 --- /dev/null +++ b/.changeset/four-jobs-type.md @@ -0,0 +1,5 @@ +--- +"next-sanity": major +--- + +Merge `next-sanity/experimental/live` export into `next-sanity/live` diff --git a/.changeset/free-hoops-marry.md b/.changeset/free-hoops-marry.md new file mode 100644 index 00000000000..cf8da2910c5 --- /dev/null +++ b/.changeset/free-hoops-marry.md @@ -0,0 +1,5 @@ +--- +"next-sanity": patch +--- + +Fix missing server action diff --git a/.changeset/heavy-doors-ask.md b/.changeset/heavy-doors-ask.md new file mode 100644 index 00000000000..4a6c3761997 --- /dev/null +++ b/.changeset/heavy-doors-ask.md @@ -0,0 +1,5 @@ +--- +"next-sanity": minor +--- + +Add `import {sanity} from 'next-sanity/cache-life'` diff --git a/.changeset/moody-news-stay.md b/.changeset/moody-news-stay.md new file mode 100644 index 00000000000..e688bcd00ff --- /dev/null +++ b/.changeset/moody-news-stay.md @@ -0,0 +1,5 @@ +--- +"next-sanity": patch +--- + +Remove `next-sanity/debug` diff --git a/.changeset/pre.json b/.changeset/pre.json new file mode 100644 index 00000000000..1faa183a046 --- /dev/null +++ b/.changeset/pre.json @@ -0,0 +1,22 @@ +{ + "mode": "pre", + "tag": "cache-components", + "initialVersions": { + "next-sanity": "12.0.3" + }, + "changesets": [ + "better-forks-ring", + "bright-jokes-stay", + "fair-rats-sell", + "four-jobs-type", + "free-hoops-marry", + "heavy-doors-ask", + "moody-news-stay", + "some-towns-rush", + "tall-flies-sing", + "tender-parents-mix", + "true-dragons-wink", + "twelve-moose-work", + "yummy-bears-juggle" + ] +} diff --git a/.changeset/some-towns-rush.md b/.changeset/some-towns-rush.md new file mode 100644 index 00000000000..a6eca02d5e8 --- /dev/null +++ b/.changeset/some-towns-rush.md @@ -0,0 +1,5 @@ +--- +"next-sanity": major +--- + +`sanityFetch` and `SanityLive` are replaced by `fetch` and `SanityLive` diff --git a/.changeset/tall-flies-sing.md b/.changeset/tall-flies-sing.md new file mode 100644 index 00000000000..f8abbb15b75 --- /dev/null +++ b/.changeset/tall-flies-sing.md @@ -0,0 +1,5 @@ +--- +"next-sanity": patch +--- + +Dedupe `resolvePerspectiveFromCookies` diff --git a/.changeset/tender-parents-mix.md b/.changeset/tender-parents-mix.md new file mode 100644 index 00000000000..6a26e5d2697 --- /dev/null +++ b/.changeset/tender-parents-mix.md @@ -0,0 +1,6 @@ +--- +"next-sanity": patch +--- + +Test pre release + \ No newline at end of file diff --git a/.changeset/true-dragons-wink.md b/.changeset/true-dragons-wink.md new file mode 100644 index 00000000000..e2bab817bf8 --- /dev/null +++ b/.changeset/true-dragons-wink.md @@ -0,0 +1,5 @@ +--- +"next-sanity": patch +--- + +Use private import for `#isCorsOriginError` diff --git a/.changeset/twelve-moose-work.md b/.changeset/twelve-moose-work.md new file mode 100644 index 00000000000..ce634777c52 --- /dev/null +++ b/.changeset/twelve-moose-work.md @@ -0,0 +1,5 @@ +--- +"next-sanity": major +--- + +Removed `type DefineSanityLiveOptions`, `type DefinedSanityFetchType` and `type DefinedSanityLiveProps` type exports diff --git a/.changeset/yummy-bears-juggle.md b/.changeset/yummy-bears-juggle.md new file mode 100644 index 00000000000..4082a2f664a --- /dev/null +++ b/.changeset/yummy-bears-juggle.md @@ -0,0 +1,5 @@ +--- +"next-sanity": minor +--- + +Add `resolvePerspectiveFromCookies` export diff --git a/apps/mvp/app/(website)/PostsLayout.tsx b/apps/mvp/app/(website)/PostsLayout.tsx index f25d96f601c..08760d4a8a4 100644 --- a/apps/mvp/app/(website)/PostsLayout.tsx +++ b/apps/mvp/app/(website)/PostsLayout.tsx @@ -34,7 +34,7 @@ export type PostsLayoutProps = { } export default async function Posts(props: PostsLayoutProps) { - 'use cache: remote' + 'use cache' const posts = postsQuery.parse(props.data) // oxlint-disable-next-line no-console diff --git a/apps/mvp/app/(website)/layout.tsx b/apps/mvp/app/(website)/layout.tsx index dc2645a2ddb..0f8a9b4e639 100644 --- a/apps/mvp/app/(website)/layout.tsx +++ b/apps/mvp/app/(website)/layout.tsx @@ -1,18 +1,16 @@ 'use cache' import '../globals.css' -import { - // cookies, - draftMode, -} from 'next/headers' +import {resolvePerspectiveFromCookies, type LivePerspective} from 'next-sanity/live' import {VisualEditing} from 'next-sanity/visual-editing' +import {refresh, updateTag} from 'next/cache' +import {cookies, draftMode} from 'next/headers' +import {Suspense} from 'react' import {DebugStatus} from './DebugStatus' import {FormStatusLabel} from './FormStatus' -import {SanityLive} from './live' +import {Live} from './live' import {RefreshButton} from './RefreshButton' -// import {resolvePerspectiveFromCookies} from 'next-sanity/experimental/live' -// import {Suspense} from 'react' async function toggleDraftMode() { 'use server' @@ -26,6 +24,16 @@ async function toggleDraftMode() { } } +async function SanityLive() { + let perspective: LivePerspective = 'published' + const isDraftMode = (await draftMode()).isEnabled + if (isDraftMode) { + perspective = await resolvePerspectiveFromCookies({cookies: await cookies()}) + } + + return +} + export default async function RootLayout({children}: {children: React.ReactNode}) { const isDraftMode = (await draftMode()).isEnabled return ( @@ -33,6 +41,33 @@ export default async function RootLayout({children}: {children: React.ReactNode}
+

Debug: {JSON.stringify({env: 'unknown'})}

+
{ + 'use server' + updateTag('sanity:debug') + }} + > + +
+
{ + 'use server' + refresh() + }} + > + +
+
{ + 'use server' + updateTag('sanity:debug') + refresh() + }} + > + +
+

Draft mode: {isDraftMode ? 'On' : 'Off'}

{isDraftMode && }
@@ -44,7 +79,9 @@ export default async function RootLayout({children}: {children: React.ReactNode} {children} {isDraftMode && } - + + + ) diff --git a/apps/mvp/app/(website)/live.ts b/apps/mvp/app/(website)/live.ts index fe8ae629386..97a8f2207f5 100644 --- a/apps/mvp/app/(website)/live.ts +++ b/apps/mvp/app/(website)/live.ts @@ -1,10 +1,10 @@ -import {defineLive} from 'next-sanity/experimental/live' +import {defineLive} from 'next-sanity/live' import {client} from '@/app/sanity.client' const token = process.env.SANITY_API_READ_TOKEN! -export const {sanityFetch, SanityLive} = defineLive({ +export const {fetch, Live} = defineLive({ client, serverToken: token, browserToken: token, diff --git a/apps/mvp/app/(website)/no-resolve-perspective/page.tsx b/apps/mvp/app/(website)/no-resolve-perspective/page.tsx index 44a69cd6aa9..63d7650eac5 100644 --- a/apps/mvp/app/(website)/no-resolve-perspective/page.tsx +++ b/apps/mvp/app/(website)/no-resolve-perspective/page.tsx @@ -1,20 +1,28 @@ -'use cache' - import {unstable__adapter, unstable__environment} from 'next-sanity' +import {cacheLife} from 'next/cache' import {draftMode} from 'next/headers' import Link from 'next/link' import PostsLayout, {postsQuery} from '@/app/(website)/PostsLayout' -import {sanityFetch} from '../live' +import {fetch as sanityFetch} from '../live' + +async function getPosts(perspective: 'drafts' | 'published') { + 'use cache: remote' + + cacheLife('sanity') -export default async function IndexPage() { - const isDraftMode = (await draftMode()).isEnabled const {data} = await sanityFetch({ query: postsQuery.query, - perspective: isDraftMode ? 'drafts' : 'published', - stega: isDraftMode, + perspective, + stega: perspective !== 'published', }) + return data +} + +export default async function IndexPage() { + const isDraftMode = (await draftMode()).isEnabled + const data = await getPosts(isDraftMode ? 'drafts' : 'published') return ( <> diff --git a/apps/mvp/app/(website)/only-production/page.tsx b/apps/mvp/app/(website)/only-production/page.tsx index 5cc100377e1..3cde5b5ab29 100644 --- a/apps/mvp/app/(website)/only-production/page.tsx +++ b/apps/mvp/app/(website)/only-production/page.tsx @@ -1,18 +1,26 @@ 'use cache' import {unstable__adapter, unstable__environment} from 'next-sanity' +import {cacheLife} from 'next/cache' import Link from 'next/link' import PostsLayout, {postsQuery} from '@/app/(website)/PostsLayout' -import {sanityFetch} from '../live' +import {fetch as sanityFetch} from '../live' + +async function getPosts() { + 'use cache: remote' + + cacheLife('sanity') -export default async function IndexPage() { const {data} = await sanityFetch({ query: postsQuery.query, - perspective: 'published', - stega: false, }) + return data +} + +export default async function IndexPage() { + const data = await getPosts() return ( <> diff --git a/apps/mvp/app/(website)/page.tsx b/apps/mvp/app/(website)/page.tsx index 60ddcf928d7..2344164eb98 100644 --- a/apps/mvp/app/(website)/page.tsx +++ b/apps/mvp/app/(website)/page.tsx @@ -1,22 +1,34 @@ import {unstable__adapter, unstable__environment} from 'next-sanity' -import {resolvePerspectiveFromCookies} from 'next-sanity/experimental/live' +import {resolvePerspectiveFromCookies, type LivePerspective} from 'next-sanity/live' +import {cacheLife} from 'next/cache' import {cookies, draftMode} from 'next/headers' import Link from 'next/link' import PostsLayout, {postsQuery} from '@/app/(website)/PostsLayout' -import {sanityFetch} from './live' +import {fetch as sanityFetch} from './live' + +async function getPosts(perspective: LivePerspective) { + 'use cache: remote' + + cacheLife('sanity') -export default async function IndexPage() { - const isDraftMode = (await draftMode()).isEnabled - const perspective = isDraftMode - ? await resolvePerspectiveFromCookies({cookies: await cookies()}) - : 'published' const {data, tags} = await sanityFetch({ query: postsQuery.query, perspective, - stega: isDraftMode, + stega: perspective !== 'published', }) + return {data, tags} +} + +export default async function IndexPage() { + let perspective: LivePerspective = 'published' + const isDraftMode = (await draftMode()).isEnabled + if (isDraftMode) { + perspective = await resolvePerspectiveFromCookies({cookies: await cookies()}) + } + + const {data, tags} = await getPosts(perspective) return ( <> diff --git a/apps/mvp/app/api/revalidate-tag/route.ts b/apps/mvp/app/api/revalidate-tag/route.ts deleted file mode 100644 index 9963080abe3..00000000000 --- a/apps/mvp/app/api/revalidate-tag/route.ts +++ /dev/null @@ -1,32 +0,0 @@ -import {parseBody} from 'next-sanity/webhook' -import {revalidateTag} from 'next/cache' -import {type NextRequest, NextResponse} from 'next/server' - -// Triggers a revalidation of the static data in the example above -export async function POST(req: NextRequest): Promise { - try { - const {body, isValidSignature} = await parseBody<{ - _type: string - _id: string - slug?: string | undefined - }>(req, process.env.SANITY_REVALIDATE_SECRET) - if (!isValidSignature) { - const message = 'Invalid signature' - return new Response(message, {status: 401}) - } - - if (!body?._type) { - return new Response('Bad Request', {status: 400}) - } - - await Promise.all( - [body.slug, body._type, body._id].map( - (tag) => typeof tag === 'string' && revalidateTag(tag, 'max'), - ), - ) - return NextResponse.json({...body, router: 'app'}) - } catch (err: any) { - console.error(err) - return new Response(err.message, {status: 500}) - } -} diff --git a/apps/mvp/next.config.ts b/apps/mvp/next.config.ts index cea39b95236..2d4c5eaac45 100644 --- a/apps/mvp/next.config.ts +++ b/apps/mvp/next.config.ts @@ -1,9 +1,12 @@ import type {NextConfig} from 'next' +import {sanity} from 'next-sanity/cache-life' + const nextConfig: NextConfig = { // basePath: process.env.NEXT_PUBLIC_TEST_BASE_PATH, // trailingSlash: true, cacheComponents: true, + cacheLife: {sanity}, logging: { fetches: { fullUrl: false, diff --git a/fixtures/fail/server-only-live/next.config.ts b/fixtures/fail/server-only-live/next.config.ts index 12b35da6010..f968d6e6ef5 100644 --- a/fixtures/fail/server-only-live/next.config.ts +++ b/fixtures/fail/server-only-live/next.config.ts @@ -1,7 +1,7 @@ import type {NextConfig} from 'next' const nextConfig: NextConfig = { - cacheComponents: true, + cacheComponents: false, } export default nextConfig diff --git a/fixtures/pass/server-only-live/next.config.ts b/fixtures/pass/server-only-live/next.config.ts index 12b35da6010..f968d6e6ef5 100644 --- a/fixtures/pass/server-only-live/next.config.ts +++ b/fixtures/pass/server-only-live/next.config.ts @@ -1,7 +1,7 @@ import type {NextConfig} from 'next' const nextConfig: NextConfig = { - cacheComponents: true, + cacheComponents: false, } export default nextConfig diff --git a/packages/next-sanity/CHANGELOG.md b/packages/next-sanity/CHANGELOG.md index 0807058db47..d902e9a86b0 100644 --- a/packages/next-sanity/CHANGELOG.md +++ b/packages/next-sanity/CHANGELOG.md @@ -1,5 +1,71 @@ # next-sanity +## 13.0.0-cache-components.8 + +### Patch Changes + +- [#3109](https://github.com/sanity-io/next-sanity/pull/3109) [`522d696`](https://github.com/sanity-io/next-sanity/commit/522d696f7675ab17b5a441f71cb46da43ef28d05) Thanks [@stipsan](https://github.com/stipsan)! - Fix missing server action + +## 13.0.0-cache-components.7 + +### Major Changes + +- [#3109](https://github.com/sanity-io/next-sanity/pull/3109) [`14f595e`](https://github.com/sanity-io/next-sanity/commit/14f595ec0e46b7deb4432744dd540336eb2b8f21) Thanks [@stipsan](https://github.com/stipsan)! - `sanityFetch` and `SanityLive` are replaced by `fetch` and `SanityLive` + +- [#3109](https://github.com/sanity-io/next-sanity/pull/3109) [`d97d1f4`](https://github.com/sanity-io/next-sanity/commit/d97d1f44fbc0de02567dc3594868a7f518f467f8) Thanks [@stipsan](https://github.com/stipsan)! - Removed `type DefineSanityLiveOptions`, `type DefinedSanityFetchType` and `type DefinedSanityLiveProps` type exports + +### Patch Changes + +- [#3109](https://github.com/sanity-io/next-sanity/pull/3109) [`ddad769`](https://github.com/sanity-io/next-sanity/commit/ddad769dbf815dbd1de0c5d6b4fc11cf2fcb3061) Thanks [@stipsan](https://github.com/stipsan)! - Remove `next-sanity/debug` + +## 13.0.0-cache-components.6 + +### Minor Changes + +- [#3109](https://github.com/sanity-io/next-sanity/pull/3109) [`013d655`](https://github.com/sanity-io/next-sanity/commit/013d655159e5ce5fee3e03106b57a9f344beec9b) Thanks [@stipsan](https://github.com/stipsan)! - Add `import {sanity} from 'next-sanity/cache-life'` + +## 13.0.0-cache-components.5 + +### Patch Changes + +- [#3109](https://github.com/sanity-io/next-sanity/pull/3109) [`cecadb3`](https://github.com/sanity-io/next-sanity/commit/cecadb3cc9e99cd194614db966fea8f3045a7b15) Thanks [@stipsan](https://github.com/stipsan)! - Add temp debug function + +## 13.0.0-cache-components.4 + +### Major Changes + +- [#3109](https://github.com/sanity-io/next-sanity/pull/3109) [`a060c49`](https://github.com/sanity-io/next-sanity/commit/a060c49fa535bcb2e7c019186023f2f9adcf55ee) Thanks [@stipsan](https://github.com/stipsan)! - Remove `type DraftPerspective`, `type DraftEnvironment` and `type ClientPerspective` from `import 'next-sanity/hooks'` + +- [#3109](https://github.com/sanity-io/next-sanity/pull/3109) [`7b02967`](https://github.com/sanity-io/next-sanity/commit/7b02967c9349e206c74b10527ab4adf90a63b35b) Thanks [@stipsan](https://github.com/stipsan)! - Remove the experimental `` API + +## 13.0.0-cache-components.3 + +### Patch Changes + +- [#3109](https://github.com/sanity-io/next-sanity/pull/3109) [`424759f`](https://github.com/sanity-io/next-sanity/commit/424759f4c9fa86c37d796573a94a702c39de956f) Thanks [@stipsan](https://github.com/stipsan)! - Dedupe `resolvePerspectiveFromCookies` + +## 13.0.0-cache-components.2 + +### Major Changes + +- [#3109](https://github.com/sanity-io/next-sanity/pull/3109) [`21f75c0`](https://github.com/sanity-io/next-sanity/commit/21f75c0872b8db1907a3e488e797d6136bf131e4) Thanks [@stipsan](https://github.com/stipsan)! - Merge `next-sanity/experimental/live` export into `next-sanity/live` + +### Minor Changes + +- [#3109](https://github.com/sanity-io/next-sanity/pull/3109) [`bcd61a3`](https://github.com/sanity-io/next-sanity/commit/bcd61a30b905bd53023e4c317773f19d466bdbb2) Thanks [@stipsan](https://github.com/stipsan)! - Add `resolvePerspectiveFromCookies` export + +## 12.0.6-cache-components.1 + +### Patch Changes + +- [#3109](https://github.com/sanity-io/next-sanity/pull/3109) [`2bc7388`](https://github.com/sanity-io/next-sanity/commit/2bc7388b12a92ad698a772e2f371ea5f4b0f1001) Thanks [@stipsan](https://github.com/stipsan)! - Use private import for `#isCorsOriginError` + +## 12.0.6-cache-components.0 + +### Patch Changes + +- [#3109](https://github.com/sanity-io/next-sanity/pull/3109) [`b7a313f`](https://github.com/sanity-io/next-sanity/commit/b7a313fb4784474a30d368bf920027e18ee5ca82) Thanks [@stipsan](https://github.com/stipsan)! - Test pre release + ## 12.0.5 ### Patch Changes diff --git a/packages/next-sanity/package.json b/packages/next-sanity/package.json index f15c73249ed..da7b92aa95b 100644 --- a/packages/next-sanity/package.json +++ b/packages/next-sanity/package.json @@ -1,6 +1,6 @@ { "name": "next-sanity", - "version": "12.0.5", + "version": "13.0.0-cache-components.8", "description": "Sanity.io toolkit for Next.js", "keywords": [ "live", @@ -30,42 +30,46 @@ "main": "./dist/index.js", "module": "./dist/index.js", "types": "./dist/index.d.ts", + "imports": { + "#client-components/*": "./src/shared/client-components/*.tsx", + "#live/*": "./src/shared/live/*.ts" + }, "exports": { ".": "./src/index.ts", + "./cache-life": "./src/cache-life.ts", "./draft-mode": "./src/draft-mode/index.ts", "./experimental/client-components/live": "./src/experimental/client-components/live.tsx", - "./experimental/live": "./src/experimental/live.tsx", "./hooks": "./src/hooks/index.ts", "./image": "./src/image/index.ts", "./live": { - "react-server": "./src/live.ts", - "default": "./src/live.server-only.ts" + "next-js": "./src/live.next-js.tsx", + "react-server": "./src/live.react-server.tsx", + "default": "./src/live.tsx" }, "./live/client-components/live": "./src/live/client-components/live/index.ts", - "./live/client-components/live-stream": "./src/live/client-components/live-stream/index.ts", "./live/server-actions": "./src/live/server-actions/index.ts", - "./package.json": "./package.json", "./studio": "./src/studio/index.ts", "./studio/client-component": "./src/studio/client-component/index.ts", "./visual-editing": "./src/visual-editing/index.ts", "./visual-editing/client-component": "./src/visual-editing/client-component/index.ts", "./visual-editing/server-actions": "./src/visual-editing/server-actions/index.ts", - "./webhook": "./src/webhook/index.ts" + "./webhook": "./src/webhook/index.ts", + "./package.json": "./package.json" }, "publishConfig": { "exports": { ".": "./dist/index.js", + "./cache-life": "./dist/cache-life.js", "./draft-mode": "./dist/draft-mode/index.js", "./experimental/client-components/live": "./dist/experimental/client-components/live.js", - "./experimental/live": "./dist/experimental/live.js", "./hooks": "./dist/hooks/index.js", "./image": "./dist/image/index.js", "./live": { - "react-server": "./dist/live.js", - "default": "./dist/live.server-only.js" + "next-js": "./dist/live.next-js.js", + "react-server": "./dist/live.react-server.js", + "default": "./dist/live.js" }, "./live/client-components/live": "./dist/live/client-components/live/index.js", - "./live/client-components/live-stream": "./dist/live/client-components/live-stream/index.js", "./live/server-actions": "./dist/live/server-actions/index.js", "./studio": "./dist/studio/index.js", "./studio/client-component": "./dist/studio/client-component/index.js", @@ -92,8 +96,7 @@ "@sanity/visual-editing": "^5.0.3", "dequal": "^2.0.3", "groq": "^5.0.1", - "history": "^5.3.0", - "server-only": "^0.0.1" + "history": "^5.3.0" }, "devDependencies": { "@sanity/browserslist-config": "^1.0.5", diff --git a/packages/next-sanity/src/cache-life.ts b/packages/next-sanity/src/cache-life.ts new file mode 100644 index 00000000000..5fbd21bc7f1 --- /dev/null +++ b/packages/next-sanity/src/cache-life.ts @@ -0,0 +1,46 @@ +/** + * For usage with `cacheComponents: true`, and `defineLive`: + * ```ts + * // next.config.ts + * + * import type {NextConfig} from 'next' + * import {sanity} from 'next-sanity/cache-life' + * + * const nextConfig: NextConfig = { + * cacheComponents: true, + * cacheLife: { + * sanity + * } + * } + * + * export default nextConfig + * ``` + * + * ```ts + * + * async function sanityFetch() { + * 'use cache' + * cacheLife('sanity') + * const {data} = await fetch({query, params}) + * return data + * } + */ +export const sanity = { + /** + * Sanity Live handles on-demand revalidation, so the default 15min time based revalidation is too short + */ + revalidate: 7_776_000, // 90 days, +} as const satisfies { + /** + * This cache may be stale on clients for ... seconds before checking with the server. + */ + stale?: number + /** + * If the server receives a new request after ... seconds, start revalidating new values in the background. + */ + revalidate?: number + /** + * If this entry has no traffic for ... seconds it will expire. The next request will recompute it. + */ + expire?: number +} diff --git a/packages/next-sanity/src/experimental/client-components/PresentationComlink.tsx b/packages/next-sanity/src/experimental/client-components/PresentationComlink.tsx index cabf3f1fc2b..a783eaa5c13 100644 --- a/packages/next-sanity/src/experimental/client-components/PresentationComlink.tsx +++ b/packages/next-sanity/src/experimental/client-components/PresentationComlink.tsx @@ -1,5 +1,12 @@ import type {ClientPerspective} from '@sanity/client' +import { + setComlink, + setComlinkClientConfig, + setPerspective, + perspective, +} from '#client-components/context' +import {sanitizePerspective} from '#live/sanitizePerspective' import {createNode, createNodeMachine} from '@sanity/comlink' import { createCompatibilityActors, @@ -10,14 +17,6 @@ import {setPerspectiveCookie} from 'next-sanity/live/server-actions' import {useRouter} from 'next/navigation' import {startTransition, useEffect, useEffectEvent} from 'react' -import { - setComlink, - setComlinkClientConfig, - setPerspective, - perspective, -} from '../../live/hooks/context' -import {sanitizePerspective} from '../../live/utils' - export default function PresentationComlink(props: { projectId: string dataset: string diff --git a/packages/next-sanity/src/experimental/client-components/live.tsx b/packages/next-sanity/src/experimental/client-components/live.tsx index b2c96288cbd..b4f50d30787 100644 --- a/packages/next-sanity/src/experimental/client-components/live.tsx +++ b/packages/next-sanity/src/experimental/client-components/live.tsx @@ -1,5 +1,8 @@ 'use client' +import {setEnvironment, setPerspective} from '#client-components/context' +import {isCorsOriginError} from '#live/isCorsOriginError' +import {sanitizePerspective} from '#live/sanitizePerspective' import { createClient, type ClientPerspective, @@ -8,16 +11,14 @@ import { type SyncTag, } from '@sanity/client' import {isMaybePresentation, isMaybePreviewWindow} from '@sanity/presentation-comlink' +import {expireTags} from 'next-sanity/live/server-actions' import dynamic from 'next/dynamic' import {useRouter} from 'next/navigation' import {useEffect, useMemo, useRef, useState, useEffectEvent} from 'react' import type {SanityClientConfig} from '../types' -import {isCorsOriginError} from '../../isCorsOriginError' -import {setEnvironment, setPerspective} from '../../live/hooks/context' -import {sanitizePerspective} from '../../live/utils' -import {PUBLISHED_SYNC_TAG_PREFIX, type DRAFT_SYNC_TAG_PREFIX} from '../constants' +import {PUBLISHED_SYNC_TAG_PREFIX} from '../constants' const PresentationComlink = dynamic(() => import('./PresentationComlink'), {ssr: false}) const RefreshOnMount = dynamic(() => import('../../live/client-components/live/RefreshOnMount'), { @@ -48,9 +49,7 @@ export interface SanityLiveProps { onError?: (error: unknown) => void intervalOnGoAway?: number | false onGoAway?: (event: LiveEventGoAway, intervalOnGoAway: number | false) => void - revalidateSyncTags: ( - tags: `${typeof PUBLISHED_SYNC_TAG_PREFIX | typeof DRAFT_SYNC_TAG_PREFIX}${SyncTag}`[], - ) => Promise + revalidateSyncTags?: (tags: string[]) => Promise resolveDraftModePerspective: () => Promise } @@ -100,7 +99,7 @@ export default function SanityLive(props: SanityLiveProps): React.JSX.Element | requestTag = 'next-loader.live', onError = handleError, onGoAway = handleOnGoAway, - revalidateSyncTags, + revalidateSyncTags = expireTags, resolveDraftModePerspective, } = props const {projectId, dataset, apiHost, apiVersion, useProjectHostname, token, requestTagPrefix} = diff --git a/packages/next-sanity/src/experimental/live.tsx b/packages/next-sanity/src/experimental/live.tsx index 1ceaf56229a..3d6d8e9dbe2 100644 --- a/packages/next-sanity/src/experimental/live.tsx +++ b/packages/next-sanity/src/experimental/live.tsx @@ -1,244 +1,25 @@ -// oxlint-disable-next-line no-unassigned-import -import 'server-only' -import {stegaEncodeSourceMap} from '@sanity/client/stega' -import {perspectiveCookieName} from '@sanity/preview-url-secret/constants' -import { - createClient, - type ClientPerspective, - type ClientReturn, - type ContentSourceMap, - type LiveEventGoAway, - type QueryParams, - type SanityClient, - type SyncTag, -} from 'next-sanity' -import SanityLiveClientComponent, { - type SanityLiveProps, -} from 'next-sanity/experimental/client-components/live' -import {cacheTag, cacheLife, updateTag} from 'next/cache' +import type {DefinedFetchType, DefinedLiveProps, LiveOptions, PerspectiveType} from '#live/types' + +import {resolvePerspectiveFromCookies} from '#live/resolvePerspectiveFromCookies' +import SanityLiveClientComponent from 'next-sanity/experimental/client-components/live' +import {cacheTag} from 'next/cache' import {draftMode, cookies} from 'next/headers' +import {Suspense} from 'react' import {preconnect} from 'react-dom' -import type {SanityClientConfig} from './types' +import type {DefinedSanityFetchType, DefinedSanityLiveProps} from '../live/defineLive' -import {sanitizePerspective} from '../live/utils' import {DRAFT_SYNC_TAG_PREFIX, PUBLISHED_SYNC_TAG_PREFIX} from './constants' -/** - * @alpha CAUTION: This API does not follow semver and could have breaking changes in future minor releases. - */ -export async function resolvePerspectiveFromCookies({ - cookies: jar, -}: { - cookies: Awaited> -}): Promise> { - return jar.has(perspectiveCookieName) - ? sanitizePerspective(jar.get(perspectiveCookieName)?.value, 'drafts') - : 'drafts' -} - -async function sanityCachedFetch( - config: SanityClientConfig, - { - query, - params = {}, - perspective, - stega, - requestTag, - draftToken, - customCacheTags = [], - }: { - query: QueryString - params?: QueryParams - perspective: Exclude - stega: boolean - requestTag: string - draftToken?: string | false | undefined - customCacheTags?: string[] - }, -): Promise<{ - data: ClientReturn - sourceMap: ContentSourceMap | null - tags: string[] -}> { - 'use cache: remote' - - const client = createClient({...config, useCdn: true}) - const useCdn = perspective === 'published' - - const {result, resultSourceMap, syncTags} = await client.fetch(query, params, { - filterResponse: false, - returnQuery: false, - perspective, - useCdn, - resultSourceMap: stega ? 'withKeyArraySelector' : undefined, // @TODO allow passing csm for non-stega use - stega: false, - cacheMode: useCdn ? 'noStale' : undefined, - tag: requestTag, - token: perspective === 'published' ? config.token : draftToken || config.token, // @TODO can pass undefined instead of config.token here? - }) - const tags = [ - ...customCacheTags, - ...(syncTags || []).map( - (tag) => - `${perspective === 'published' ? PUBLISHED_SYNC_TAG_PREFIX : DRAFT_SYNC_TAG_PREFIX}${tag}`, - ), - ] - /** - * The tags used here, are expired later on in the `expireTags` Server Action with the `expireTag` function from `next/cache` - */ - cacheTag(...tags) - /** - * Sanity Live handles on-demand revalidation, so the default 15min time based revalidation is too short - */ - cacheLife({revalidate: 60 * 60 * 24 * 90}) - - return {data: result, sourceMap: resultSourceMap || null, tags} -} - -/** - * @alpha CAUTION: This API does not follow semver and could have breaking changes in future minor releases. - */ -export interface SanityFetchOptions { - query: QueryString - params?: QueryParams - /** - * @defaultValue 'published' - */ - perspective?: Exclude - /** - * Enables stega encoding of the data, this is typically only used in draft mode in conjunction with `perspective: 'drafts'` and with `@sanity/visual-editing` setup. - * @defaultValue `false` - */ - stega?: boolean - /** - * This request tag is used to identify the request when viewing request logs from your Sanity Content Lake. - * @see https://www.sanity.io/docs/reference-api-request-tags - * @defaultValue 'next-loader.fetch' - */ - requestTag?: string - /** - * Custom cache tags that can be used with next's `revalidateTag` and `updateTag` functions for custom webhook on-demand revalidation. - */ - tags?: string[] -} - -/** - * @alpha CAUTION: This API does not follow semver and could have breaking changes in future minor releases. - */ -export type DefinedSanityFetchType = ( - options: SanityFetchOptions, -) => Promise<{ - data: ClientReturn - /** - * The Content Source Map can be used for custom setups like `encodeSourceMap` for `data-sanity` attributes, or `stegaEncodeSourceMap` for stega encoding in your own way. - * The Content Source Map is only fetched by default in draft mode, if `stega` is `true`. Otherwise your client configuration will need to have `resultSourceMap: 'withKeyArraySelector' | true` - */ - sourceMap: ContentSourceMap | null - /** - * The cache tags used with `next/cache`, useful for debugging. - */ - tags: string[] -}> - -/** - * @alpha CAUTION: This API does not follow semver and could have breaking changes in future minor releases. - */ -export interface DefinedSanityLiveProps { - /** - * Automatic refresh of RSC when the component is mounted. - * @defaultValue `false` - */ - refreshOnMount?: boolean - /** - * Automatically refresh when window gets focused - * @defaultValue `false` - */ - refreshOnFocus?: boolean - /** - * Automatically refresh when the browser regains a network connection (via navigator.onLine) - * @defaultValue `false` - */ - refreshOnReconnect?: boolean - /** - * Automatically refresh on an interval when the Live Event API emits a `goaway` event, which indicates that the connection is rejected or closed. - * This typically happens if the connection limit is reached, or if the connection is idle for too long. - * To disable this long polling fallback behavior set `intervalOnGoAway` to `false` or `0`. - * You can also use `onGoAway` to handle the `goaway` event in your own way, and read the reason why the event was emitted. - * @defaultValue `30_000` 30 seconds interval - */ - intervalOnGoAway?: number | false - - /** - * This request tag is used to identify the request when viewing request logs from your Sanity Content Lake. - * @see https://www.sanity.io/docs/reference-api-request-tags - * @defaultValue 'next-loader.live' - */ - requestTag?: string - - /** - * Handle errors from the Live Events subscription. - * By default it's reported using `console.error`, you can override this prop to handle it in your own way. - */ - onError?: (error: unknown) => void - - /** - * Handle the `goaway` event if the connection is rejected/closed. - * `event.reason` will be a string of why the event was emitted, for example `'connection limit reached'`. - * When this happens the `` will fallback to long polling with a default interval of 30 seconds, providing your own `onGoAway` handler does not change this behavior. - * If you want to disable long polling set `intervalOnGoAway` to `false` or `0`. - */ - onGoAway?: (event: LiveEventGoAway, intervalOnGoAway: number | false) => void - - /** - * Override how cache tags are invalidated, you need to pass a server action here. - * You can also pass a `use client` function here, and have `router.refresh()` be called if the promise resolves to `'refresh'`. - */ - // @TODO remove, replace with onLiveEvent - revalidateSyncTags?: ( - tags: `${typeof PUBLISHED_SYNC_TAG_PREFIX | typeof DRAFT_SYNC_TAG_PREFIX}${SyncTag}`[], - ) => Promise - - // @TODO add - // decide how to handle a live event coming in - // onLiveEvent?: (event: LiveEvent, mode: 'production' | 'preview) => void - - /** - * Control how the draft mode perspective is resolved, by default it resolves from the `sanity-preview-perspective` cookie. - */ - resolveDraftModePerspective?: () => Promise -} - -/** - * @alpha CAUTION: This API does not follow semver and could have breaking changes in future minor releases. - */ -export interface DefineSanityLiveOptions { - /** - * Required for `sanityFetch` and `SanityLive` to work - */ - client: SanityClient +export function defineLive(config: LiveOptions): { + fetch: DefinedFetchType + Live: React.ComponentType /** - * Optional. If provided then the token needs to have permissions to query documents with `drafts.` prefixes in order for `perspective: 'drafts'` to work. - * This token is not shared with the browser. - */ - serverToken?: string | false - /** - * Optional. This token is shared with the browser, and should only have access to query published documents. - * It is used to setup a `Live Draft Content` EventSource connection, and enables live previewing drafts stand-alone, outside of Presentation Tool. - */ - browserToken?: string | false -} - -/** - * @alpha CAUTION: This API does not follow semver and could have breaking changes in future minor releases. - */ -export function defineLive(config: DefineSanityLiveOptions): { - /** - * Use this function to fetch data from Sanity in your React Server Components. + * @deprecated use `fetch` instead, and define your own `sanityFetch` function with logic for when to toggle `stega` and `perspective` */ sanityFetch: DefinedSanityFetchType /** - * Render this in your root layout.tsx to make your page revalidate on new content live, automatically. + * @deprecated use `Live` instead, and define your own `SanityLive` component with logic for when to toggle `perspective` */ SanityLive: React.ComponentType } { @@ -260,77 +41,100 @@ export function defineLive(config: DefineSanityLiveOptions): { ) } - const client = _client.withConfig({allowReconfigure: false, useCdn: false}) - const { - token: originalToken, - apiHost, - apiVersion, - useProjectHostname, - dataset, - projectId, - requestTagPrefix, - stega: stegaConfig, - } = client.config() + const client = _client.withConfig({allowReconfigure: false, useCdn: true}) + const {token: originalToken} = client.config() - const sanityFetch: DefinedSanityFetchType = function sanityFetch< - const QueryString extends string, - >({ + const fetch: DefinedFetchType = async function fetch({ query, params = {}, - stega = false, perspective = 'published', + stega = false, tags: customCacheTags = [], - requestTag = 'next-loader.fetch', - }: { - query: QueryString - params?: QueryParams - stega?: boolean - tags?: string[] - perspective?: Exclude - requestTag?: string + requestTag = 'next-loader.fetch.cache-components', }) { - return sanityCachedFetch( - { - apiHost, - apiVersion, - useProjectHostname, - dataset, - projectId, - requestTagPrefix, - token: originalToken, - }, - { - query, - params, - perspective, - stega, - requestTag, - draftToken: serverToken, - customCacheTags, - }, - ).then(({data, sourceMap, tags}) => ({ - data: - stega && sourceMap - ? stegaEncodeSourceMap(data, sourceMap, {...stegaConfig, enabled: true}) - : data, - sourceMap, - tags, - })) + const useCdn = perspective === 'published' + + const {result, resultSourceMap, syncTags} = await client.fetch(query, params, { + filterResponse: false, + returnQuery: false, + perspective, + useCdn, + stega, + cacheMode: useCdn ? 'noStale' : undefined, + tag: requestTag, + token: perspective === 'published' ? originalToken : serverToken || originalToken, // @TODO can pass undefined instead of config.token here? + }) + const tags = [ + ...customCacheTags, + ...(syncTags || []).map( + (tag) => + `${perspective === 'published' ? PUBLISHED_SYNC_TAG_PREFIX : DRAFT_SYNC_TAG_PREFIX}${tag}`, + ), + ] + /** + * The tags used here, are expired later on in the `expireTags` Server Action with the `expireTag` function from `next/cache` + */ + cacheTag(...tags) + /** + * Sanity Live handles on-demand revalidation, so the default 15min time based revalidation is too short + */ + // cacheLife({revalidate: 60 * 60 * 24 * 90}) + + return {data: result, sourceMap: resultSourceMap || null, tags} + + // return sanityCachedFetch( + // { + // apiHost, + // apiVersion, + // useProjectHostname, + // dataset, + // projectId, + // requestTagPrefix, + // token: originalToken, + // }, + // { + // query, + // params, + // perspective, + // stega, + // requestTag, + // draftToken: serverToken, + // customCacheTags, + // }, + // ).then(({data, sourceMap, tags}) => ({ + // data: + // stega && sourceMap + // ? stegaEncodeSourceMap(data, sourceMap, {...stegaConfig, enabled: true}) + // : data, + // sourceMap, + // tags, + // })) } - const SanityLive: React.ComponentType = function SanityLive(props) { + const Live: React.ComponentType = function Live(props) { const { - // perspective, + perspective = 'published', + onChange, + onChangeIncludingDrafts, + onStudioPerspective, refreshOnMount = false, refreshOnFocus = false, refreshOnReconnect = false, - requestTag, + requestTag = 'next-loader.live.cache-components', onError, onGoAway, intervalOnGoAway, - revalidateSyncTags = expireTags, } = props + if (onChangeIncludingDrafts) { + console.warn('`onChangeIncludingDrafts` is not implemented yet') + } + if (onStudioPerspective) { + console.warn('`onStudioPerspective` is not implemented yet') + } + + const includeDrafts = typeof browserToken === 'string' && perspective !== 'published' + const {projectId, dataset, apiHost, apiVersion, useProjectHostname, requestTagPrefix} = client.config() const {origin} = new URL(client.getUrl('', false)) @@ -339,114 +143,50 @@ export function defineLive(config: DefineSanityLiveOptions): { preconnect(origin) return ( - + + + ) } - return {sanityFetch, SanityLive} -} - -interface SanityLiveServerComponentProps extends Omit< - SanityLiveProps, - 'draftModeEnabled' | 'token' | 'draftModePerspective' -> { - browserToken: string | false | undefined - // origin: string - // perspective?: Exclude -} - -const SanityLiveServerComponent: React.ComponentType = - async function SanityLiveServerComponent(props) { - // 'use cache' - // @TODO should this be 'max' instead?, or configured by changing the default cache profile? - // cacheLife({ - // stale: Infinity, - // revalidate: Infinity, - // expire: Infinity, - // }) - const { - config, - requestTag, - intervalOnGoAway, - onError, - onGoAway, - refreshOnFocus, - refreshOnMount, - refreshOnReconnect, - revalidateSyncTags, - browserToken, - // origin, - // perspective, - resolveDraftModePerspective, - } = props - - const {isEnabled: isDraftModeEnabled} = await draftMode() - - // // Preconnect to the Live Event API origin early, as the Sanity API is almost always on a different origin than the app - // preconnect(origin) - - return ( - - ) - } - -// @TODO expose parseTags function that returns the correct array of tags -// we already have s1: prefixes, but they could change -// use sp: for prod, sd: for draft, keep em short -async function expireTags(_tags: unknown): Promise { - 'use server' - // @TODO Draft Mode bypasses cache anyway so we don't bother with expiring tags for draft content - // const isDraftMode = (await draftMode()).isEnabled - // const tags = _tags.map((tag) => `${isDraftMode ? 'drafts' : 'sanity'}:${tag}`) - if (!Array.isArray(_tags)) { - console.warn(' `expireTags` called with non-array tags', _tags) - return undefined - } - const tags = _tags.filter( - (tag) => typeof tag === 'string' && tag.startsWith(PUBLISHED_SYNC_TAG_PREFIX), - ) - if (!tags.length) { - console.warn(' `expireTags` called with no valid tags', _tags) - return undefined - } - for (const tag of tags) { - updateTag(tag) + return { + fetch, + Live, + sanityFetch: () => { + throw new Error( + '`defineLive().sanityFetch` is not available when `cacheComponents: true`, use `defineLive().fetch` instead', + ) + }, + SanityLive: () => { + throw new Error( + '`defineLive().SanityLive` is not available when `cacheComponents: true`, use `defineLive().Live` instead', + ) + }, } - // oxlint-disable-next-line no-console - console.log(` updated tags: ${tags.join(', ')}`) } -async function resolveDraftModePerspective(): Promise { +async function resolveDraftModePerspective(): Promise { 'use server' if ((await draftMode()).isEnabled) { const jar = await cookies() diff --git a/packages/next-sanity/src/index.ts b/packages/next-sanity/src/index.ts index 0cd683dc756..f1e816db6a4 100644 --- a/packages/next-sanity/src/index.ts +++ b/packages/next-sanity/src/index.ts @@ -2,4 +2,4 @@ export * from './client' export * from './create-data-attribute' export * from '@portabletext/react' export {defineQuery, default as groq} from 'groq' -export {isCorsOriginError} from './isCorsOriginError' +export {isCorsOriginError} from '#live/isCorsOriginError' diff --git a/packages/next-sanity/src/live.next-js.tsx b/packages/next-sanity/src/live.next-js.tsx new file mode 100644 index 00000000000..483ff315579 --- /dev/null +++ b/packages/next-sanity/src/live.next-js.tsx @@ -0,0 +1,8 @@ +// This is the implementation of `import {defineLive} from 'next-sanity/live' that is used when `cacheComponents: true` is set in `next.config.ts`. +// Next.js will, when `cacheComponents: true`, automatically import `next-js` conditions instead of `react-server`, to allow targeting this mode. + +export {isCorsOriginError} from '#live/isCorsOriginError' +export {defineLive} from './experimental/live' +export {resolvePerspectiveFromCookies} from '#live/resolvePerspectiveFromCookies' + +export type {PerspectiveType as LivePerspective} from '#live/types' diff --git a/packages/next-sanity/src/live.react-server.tsx b/packages/next-sanity/src/live.react-server.tsx new file mode 100644 index 00000000000..62d559a37f4 --- /dev/null +++ b/packages/next-sanity/src/live.react-server.tsx @@ -0,0 +1,10 @@ +// This is the implementation of `import {defineLive} from 'next-sanity/live' that is used when `cacheComponents: false` is set in `next.config.ts`. +// Or more accurately, that `next.config.ts` does not have `cacheComponents: true` set as it is opt-in. +// While this implementation works, it's not super ideal, and we should warn in NOdE_ENV !== 'production' that we recommend using `cacheComponents: true` instead. +// Among other reasons we have to double-fetch to set cache tags, while the customer doesn't pay for the additional fetch it still adds latency to the server render. + +export {isCorsOriginError} from '#live/isCorsOriginError' +export {defineLive} from './live/defineLive' +export {resolvePerspectiveFromCookies} from '#live/resolvePerspectiveFromCookies' + +export type {PerspectiveType as LivePerspective} from '#live/types' diff --git a/packages/next-sanity/src/live.server-only.ts b/packages/next-sanity/src/live.server-only.ts deleted file mode 100644 index 741a992e39a..00000000000 --- a/packages/next-sanity/src/live.server-only.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { - DefineSanityLiveOptions, - DefinedSanityFetchType, - DefinedSanityLiveProps, - DefinedSanityLiveStreamType, -} from './live/defineLive' - -/** - * @public - */ -export function defineLive(_config: DefineSanityLiveOptions): { - sanityFetch: DefinedSanityFetchType - SanityLive: React.ComponentType - SanityLiveStream: DefinedSanityLiveStreamType -} { - throw new Error('defineLive can only be used in React Server Components') -} - -/** - * @public - */ -export type { - DefineSanityLiveOptions, - DefinedSanityFetchType, - DefinedSanityLiveProps, - DefinedSanityLiveStreamType, -} - -// @TODO deprecate, so that we can simplify this branching and just use `import 'server-only'` instead -export {isCorsOriginError} from './isCorsOriginError' diff --git a/packages/next-sanity/src/live.ts b/packages/next-sanity/src/live.ts deleted file mode 100644 index b3550de7e6b..00000000000 --- a/packages/next-sanity/src/live.ts +++ /dev/null @@ -1,8 +0,0 @@ -export { - type DefineSanityLiveOptions, - type DefinedSanityFetchType, - type DefinedSanityLiveProps, - type DefinedSanityLiveStreamType, - defineLive, -} from './live/defineLive' -export {isCorsOriginError} from './isCorsOriginError' diff --git a/packages/next-sanity/src/live.tsx b/packages/next-sanity/src/live.tsx new file mode 100644 index 00000000000..ac3057f6c2a --- /dev/null +++ b/packages/next-sanity/src/live.tsx @@ -0,0 +1,44 @@ +// This is the fallback export condition for `import 'next-sanity/live'`, +// it should have the same type definitions as the other conditions so that userland don't have to worry about setting the right +// `customCondition` in their `tsconfig.json` in order to get accurate typings. +// The implementation here though should all throw errors, as importing this file means userland made a mistake and somehow a client component is +// trying to pull in something it shouldn't. + +import type {ResolvePerspectiveFromCookies} from '#live/resolvePerspectiveFromCookies' +import type {DefinedFetchType, DefinedLiveProps} from '#live/types' + +import type { + DefineSanityLiveOptions, + DefinedSanityFetchType, + DefinedSanityLiveProps, +} from './live/defineLive' + +export {isCorsOriginError} from '#live/isCorsOriginError' + +/** + * @public + */ +export function defineLive(_config: DefineSanityLiveOptions): { + fetch: DefinedFetchType + Live: React.ComponentType + /** + * @deprecated use `fetch` instead, and define your own `sanityFetch` function with logic for when to toggle `stega` and `perspective` + */ + sanityFetch: DefinedSanityFetchType + /** + * @deprecated use `Live` instead, and define your own `SanityLive` component with logic for when to toggle `perspective` + */ + SanityLive: React.ComponentType +} { + throw new Error(`defineLive can't be imported by a client component`) +} + +/** + * Resolves the perspective from the cookie that is set by `import { defineEnableDraftMode } from "next-sanity/draft-mode"` + * @public + */ +export const resolvePerspectiveFromCookies: ResolvePerspectiveFromCookies = () => { + throw new Error(`resolvePerspectiveFromCookies can't be imported by a client component`) +} + +export type {PerspectiveType as LivePerspective} from '#live/types' diff --git a/packages/next-sanity/src/live/client-components/live-stream/SanityLiveStream.tsx b/packages/next-sanity/src/live/client-components/live-stream/SanityLiveStream.tsx deleted file mode 100644 index 0a1cb94808c..00000000000 --- a/packages/next-sanity/src/live/client-components/live-stream/SanityLiveStream.tsx +++ /dev/null @@ -1,145 +0,0 @@ -import type {LoaderControllerMsg} from '@sanity/presentation-comlink' - -// oxlint-disable no-unsafe-type-assertion -import { - type ClientPerspective, - type ContentSourceMap, - type InitializedClientConfig, - type QueryParams, -} from '@sanity/client' -import {stegaEncodeSourceMap} from '@sanity/client/stega' -import {dequal} from 'dequal/lite' -import {use, useCallback, useEffect, useState, useSyncExternalStore, useEffectEvent} from 'react' - -import {comlinkListeners, comlink as comlinkSnapshot} from '../../hooks/context' - -/** - * @public - */ -export interface SanityLiveStreamProps extends Pick< - InitializedClientConfig, - 'projectId' | 'dataset' -> { - query: string - params?: QueryParams - perspective?: Exclude - stega?: boolean - initial: Promise - children: (result: { - data: unknown - sourceMap: ContentSourceMap | null - tags: string[] - }) => Promise -} - -const LISTEN_HEARTBEAT_INTERVAL = 10_000 - -/** - * @public - */ -export default function SanityLiveStream(props: SanityLiveStreamProps): React.JSX.Element | null { - const {query, dataset, params = {}, perspective, projectId, stega} = props - - const subscribe = useCallback((listener: () => void) => { - comlinkListeners.add(listener) - return () => comlinkListeners.delete(listener) - }, []) - - const comlink = useSyncExternalStore( - subscribe, - () => comlinkSnapshot, - () => null, - ) - const [children, setChildren] = useState(undefined) - - const handleQueryHeartbeat = useEffectEvent((comlink: NonNullable) => { - comlink.post('loader/query-listen', { - projectId: projectId!, - dataset: dataset!, - perspective: perspective! as ClientPerspective, - query, - params: params, - heartbeat: LISTEN_HEARTBEAT_INTERVAL, - }) - }) - const handleQueryChange = useEffectEvent( - (event: Extract['data']) => { - if ( - dequal( - { - projectId, - dataset, - query, - params, - }, - { - projectId: event.projectId, - dataset: event.dataset, - query: event.query, - params: event.params, - }, - ) - ) { - const {result, resultSourceMap, tags} = event - const data = stega - ? stegaEncodeSourceMap(result, resultSourceMap, {enabled: true, studioUrl: '/'}) - : result - // console.log('server function streaming is disabled', { - // startTransition, - // setPromise, - // data, - // resultSourceMap, - // tags, - // }) - // console.log('rendering with server action') - // startTransition(() => - // setPromise( - // props.children({ - // data, - // sourceMap: resultSourceMap!, - // tags: tags || [], - // }) as Promise, - // ), - // ) - // oxlint-disable-next-line no-console - console.groupCollapsed('rendering with server action') - ;( - props.children({ - data, - sourceMap: resultSourceMap!, - tags: tags || [], - }) as Promise - ) - .then( - (children) => { - // oxlint-disable-next-line no-console - console.log('setChildren(children)') - // startTransition(() => setChildren(children)) - setChildren(children) - }, - (reason: unknown) => { - console.error('rendering with server action: render children error', reason) - }, - ) - // oxlint-disable-next-line no-console - .finally(() => console.groupEnd()) - } - }, - ) - useEffect(() => { - if (!comlink) return - - const unsubscribe = comlink.on('loader/query-change', handleQueryChange) - const interval = setInterval(() => handleQueryHeartbeat(comlink), LISTEN_HEARTBEAT_INTERVAL) - return () => { - clearInterval(interval) - unsubscribe() - } - }, [comlink]) - - if (!comlink || children === undefined) { - return use(props.initial) as React.JSX.Element - } - - return <>{children} -} diff --git a/packages/next-sanity/src/live/client-components/live-stream/SanityLiveStreamLazy.tsx b/packages/next-sanity/src/live/client-components/live-stream/SanityLiveStreamLazy.tsx deleted file mode 100644 index 4794487fb4f..00000000000 --- a/packages/next-sanity/src/live/client-components/live-stream/SanityLiveStreamLazy.tsx +++ /dev/null @@ -1,15 +0,0 @@ -/** - * This file works around a new restriction in Next v15 where server components are not allowed - * to use dynamic(() => import('...), {ssr: false}) - * only Client Components can set ssr: false. - */ - -import dynamic from 'next/dynamic' - -import type {SanityLiveStreamProps} from './SanityLiveStream' - -const SanityLiveStreamClientComponent = dynamic(() => import('./SanityLiveStream'), {ssr: false}) - -export function SanityLiveStreamLazyClientComponent(props: SanityLiveStreamProps): React.ReactNode { - return -} diff --git a/packages/next-sanity/src/live/client-components/live-stream/index.ts b/packages/next-sanity/src/live/client-components/live-stream/index.ts deleted file mode 100644 index d603eefb7a9..00000000000 --- a/packages/next-sanity/src/live/client-components/live-stream/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -'use client' - -export type {SanityLiveStreamProps} from './SanityLiveStream' -export {SanityLiveStreamLazyClientComponent as default} from './SanityLiveStreamLazy' diff --git a/packages/next-sanity/src/live/client-components/live/PresentationComlink.tsx b/packages/next-sanity/src/live/client-components/live/PresentationComlink.tsx index 0333d1ea2d9..ddf61c867aa 100644 --- a/packages/next-sanity/src/live/client-components/live/PresentationComlink.tsx +++ b/packages/next-sanity/src/live/client-components/live/PresentationComlink.tsx @@ -5,6 +5,7 @@ import { createNodeMachine, // type Node, } from '@sanity/comlink' +import {setComlink, setComlinkClientConfig} from '#client-components/context' import { createCompatibilityActors, type LoaderControllerMsg, @@ -14,8 +15,6 @@ import {setPerspectiveCookie} from 'next-sanity/live/server-actions' import {useRouter} from 'next/navigation' import {useEffect, useEffectEvent} from 'react' -import {setComlink, setComlinkClientConfig} from '../../hooks/context' - function PresentationComlink(props: { projectId: string dataset: string diff --git a/packages/next-sanity/src/live/client-components/live/SanityLive.tsx b/packages/next-sanity/src/live/client-components/live/SanityLive.tsx index 220847eab72..4722ebb1ebe 100644 --- a/packages/next-sanity/src/live/client-components/live/SanityLive.tsx +++ b/packages/next-sanity/src/live/client-components/live/SanityLive.tsx @@ -1,3 +1,5 @@ +import {setEnvironment, setPerspective} from '#client-components/context' +import {isCorsOriginError} from '#live/isCorsOriginError' import { createClient, type ClientPerspective, @@ -12,9 +14,6 @@ import dynamic from 'next/dynamic' import {useRouter} from 'next/navigation' import {useEffect, useMemo, useRef, useState, useEffectEvent} from 'react' -import {isCorsOriginError} from '../../../isCorsOriginError' -import {setEnvironment, setPerspective} from '../../hooks/context' - const PresentationComlink = dynamic(() => import('./PresentationComlink'), {ssr: false}) const RefreshOnMount = dynamic(() => import('./RefreshOnMount'), {ssr: false}) const RefreshOnFocus = dynamic(() => import('./RefreshOnFocus'), {ssr: false}) diff --git a/packages/next-sanity/src/live/defineLive.tsx b/packages/next-sanity/src/live/defineLive.tsx index 13bcf8f0aef..6f6c30df945 100644 --- a/packages/next-sanity/src/live/defineLive.tsx +++ b/packages/next-sanity/src/live/defineLive.tsx @@ -1,3 +1,5 @@ +// import type {DefinedFetchType, DefinedLiveProps} from '#live/types' + import { type ClientPerspective, type ClientReturn, @@ -8,7 +10,6 @@ import { type SyncTag, } from '@sanity/client' import SanityLiveClientComponent from 'next-sanity/live/client-components/live' -import SanityLiveStreamClientComponent from 'next-sanity/live/client-components/live-stream' import {draftMode} from 'next/headers' import {prefetchDNS, preconnect} from 'react-dom' @@ -29,10 +30,6 @@ export type DefinedSanityFetchType = (options: tags?: string[] perspective?: Exclude stega?: boolean - /** - * @deprecated use `requestTag` instead - */ - tag?: never /** * This request tag is used to identify the request when viewing request logs from your Sanity Content Lake. * @see https://www.sanity.io/docs/reference-api-request-tags @@ -45,40 +42,6 @@ export type DefinedSanityFetchType = (options: tags: string[] }> -/** - * @public - */ -export type DefinedSanityLiveStreamType = (props: { - query: QueryString - params?: QueryParams | Promise - /** - * Add custom `next.tags` to the underlying fetch request. - * @see https://nextjs.org/docs/app/api-reference/functions/fetch#optionsnexttags - * This can be used in conjunction with custom fallback revalidation strategies, as well as with custom Server Actions that mutate data and want to render with fresh data right away (faster than the Live Event latency). - * @defaultValue `['sanity']` - */ - tags?: string[] - perspective?: Exclude - stega?: boolean - /** - * @deprecated use `requestTag` instead - */ - tag?: never - /** - * This request tag is used to identify the request when viewing request logs from your Sanity Content Lake. - * @see https://www.sanity.io/docs/reference-api-request-tags - * @defaultValue 'next-loader.live-stream.fetch' - */ - requestTag?: string - children: (result: { - data: ClientReturn - sourceMap: ContentSourceMap | null - tags: string[] - }) => Promise> - // @TODO follow up on this after React 19: https://github.com/vercel/next.js/discussions/67365#discussioncomment-9935377 - // }) => Promise> -}) => React.ReactNode - /** * @public */ @@ -110,11 +73,6 @@ export interface DefinedSanityLiveProps { */ intervalOnGoAway?: number | false - /** - * @deprecated use `requestTag` instead - */ - tag?: never - /** * This request tag is used to identify the request when viewing request logs from your Sanity Content Lake. * @see https://www.sanity.io/docs/reference-api-request-tags @@ -178,29 +136,17 @@ export interface DefineSanityLiveOptions { stega?: boolean } -// export type VerifyPreviewSecretType = ( -// secret: string, -// ) => Promise<{isValid: boolean; studioUrl: string | null}> - -/** - * @public - */ export function defineLive(config: DefineSanityLiveOptions): { /** - * Use this function to fetch data from Sanity in your React Server Components. - * @public + * @deprecated use `fetch` instead, and define your own `sanityFetch` function with logic for when to toggle `stega` and `perspective` */ sanityFetch: DefinedSanityFetchType /** - * Render this in your root layout.tsx to make your page revalidate on new content live, automatically. - * @public + * @deprecated use `Live` instead, and define your own `SanityLive` component with logic for when to toggle `perspective` */ SanityLive: React.ComponentType - /** - * @alpha experimental, it may change or even be removed at any time - */ - SanityLiveStream: DefinedSanityLiveStreamType - // verifyPreviewSecret: VerifyPreviewSecretType + // fetch: DefinedFetchType + // Live: React.ComponentType } { const { client: _client, @@ -292,8 +238,7 @@ export function defineLive(config: DefineSanityLiveOptions): { refreshOnMount, refreshOnFocus, refreshOnReconnect, - tag, - requestTag = tag, + requestTag, onError, onGoAway, intervalOnGoAway, @@ -332,90 +277,8 @@ export function defineLive(config: DefineSanityLiveOptions): { ) } - const SanityLiveStream: DefinedSanityLiveStreamType = async function SanityLiveStream(props) { - const { - query, - params, - perspective: _perspective, - stega: _stega, - tags, - children, - tag, - requestTag = tag ?? 'next-loader.live-stream.fetch', - } = props - const { - data, - sourceMap, - tags: cacheTags, - } = await sanityFetch({ - query, - params, - tags, - perspective: _perspective, - stega: _stega, - requestTag, - }) - const {isEnabled: isDraftModeEnabled} = await draftMode() - - if (isDraftModeEnabled) { - const stega = _stega ?? (stegaEnabled && studioUrlDefined && (await draftMode()).isEnabled) - const perspective = _perspective ?? (await resolveCookiePerspective()) - const {projectId, dataset} = client.config() - return ( - - ) - } - - return children({data, sourceMap, tags: cacheTags}) - } - - // const verifyPreviewSecret: VerifyPreviewSecretType = async (secret) => { - // if (!serverToken) { - // throw new Error( - // '`serverToken` is required to verify a preview secrets and initiate draft mode', - // ) - // } - - // if (typeof secret !== 'string') { - // throw new TypeError('`secret` must be a string') - // } - // if (!secret.trim()) { - // throw new Error('`secret` must not be an empty string') - // } - - // const client = _client.withConfig({ - // // Use the token that is setup to query draft documents, it should also have permission to query for secrets - // token: serverToken, - // // Userland might be using an API version that's too old to use perspectives - // apiVersion, - // // We can't use the CDN, the secret is typically validated right after it's created - // useCdn: false, - // // Don't waste time returning a source map, we don't need it - // resultSourceMap: false, - // // Stega is not needed - // stega: false, - // }) - // const {isValid, studioUrl} = await validateSecret(client, secret, false) - // return {isValid, studioUrl} - // } - return { sanityFetch, SanityLive, - SanityLiveStream, - // verifyPreviewSecret } } diff --git a/packages/next-sanity/src/live/hooks/index.ts b/packages/next-sanity/src/live/hooks/index.ts index 0ef61ef881a..655c0912c2e 100644 --- a/packages/next-sanity/src/live/hooks/index.ts +++ b/packages/next-sanity/src/live/hooks/index.ts @@ -1,6 +1,4 @@ export * from './useDraftMode' -export type {DraftPerspective, DraftEnvironment} from './context' -export type {ClientPerspective} from '@sanity/client' export * from './useIsPresentationTool' export * from './useIsLivePreview' export * from './usePresentationQuery' diff --git a/packages/next-sanity/src/live/hooks/useDraftMode.ts b/packages/next-sanity/src/live/hooks/useDraftMode.ts index 52bc29bd1b9..77a8c2f3548 100644 --- a/packages/next-sanity/src/live/hooks/useDraftMode.ts +++ b/packages/next-sanity/src/live/hooks/useDraftMode.ts @@ -1,13 +1,12 @@ -import {useCallback, useSyncExternalStore} from 'react' - import { environment, environmentListeners, perspective, perspectiveListeners, - type DraftEnvironment, - type DraftPerspective, -} from './context' + type LiveEnvironment, + type LivePerspective, +} from '#client-components/context' +import {useCallback, useSyncExternalStore} from 'react' /** * Reports the current draft mode environment. @@ -19,7 +18,7 @@ import { * - Your app is not previewing anything (that could be detected). * @public */ -export function useDraftModeEnvironment(): DraftEnvironment { +export function useDraftModeEnvironment(): LiveEnvironment { const subscribe = useCallback((listener: () => void) => { environmentListeners.add(listener) return () => environmentListeners.delete(listener) @@ -38,7 +37,7 @@ export function useDraftModeEnvironment(): DraftEnvironment { * If the hook is used but the `` component is not present then it'll stay in `'checking'` and console warn after a timeout that it seems like you're missing the component. * @public */ -export function useDraftModePerspective(): DraftPerspective { +export function useDraftModePerspective(): LivePerspective { const subscribe = useCallback((listener: () => void) => { perspectiveListeners.add(listener) return () => perspectiveListeners.delete(listener) diff --git a/packages/next-sanity/src/live/hooks/usePresentationQuery.ts b/packages/next-sanity/src/live/hooks/usePresentationQuery.ts index d7453903d50..a53f2c209b3 100644 --- a/packages/next-sanity/src/live/hooks/usePresentationQuery.ts +++ b/packages/next-sanity/src/live/hooks/usePresentationQuery.ts @@ -1,16 +1,16 @@ import type {ClientPerspective, ClientReturn, ContentSourceMap, QueryParams} from '@sanity/client' import type {LoaderControllerMsg} from '@sanity/presentation-comlink' -import {stegaEncodeSourceMap} from '@sanity/client/stega' -import {dequal} from 'dequal/lite' -import {useEffect, useMemo, useReducer, useSyncExternalStore, useEffectEvent} from 'react' - import { comlinkDataset, comlinkListeners, comlinkProjectId, comlink as comlinkSnapshot, -} from './context' +} from '#client-components/context' +import {stegaEncodeSourceMap} from '@sanity/client/stega' +import {dequal} from 'dequal/lite' +import {useEffect, useMemo, useReducer, useSyncExternalStore, useEffectEvent} from 'react' + import {useDraftModePerspective} from './useDraftMode' /** @alpha */ diff --git a/packages/next-sanity/src/live/resolveCookiePerspective.ts b/packages/next-sanity/src/live/resolveCookiePerspective.ts index d104d49998d..fea9e69f994 100644 --- a/packages/next-sanity/src/live/resolveCookiePerspective.ts +++ b/packages/next-sanity/src/live/resolveCookiePerspective.ts @@ -1,10 +1,9 @@ import type {ClientPerspective} from '@sanity/client' +import {sanitizePerspective} from '#live/sanitizePerspective' import {perspectiveCookieName} from '@sanity/preview-url-secret/constants' import {cookies, draftMode} from 'next/headers' -import {sanitizePerspective} from './utils' - /** * @internal */ diff --git a/packages/next-sanity/src/live/server-actions/index.ts b/packages/next-sanity/src/live/server-actions/index.ts index 1cc1090dc04..2e069686a64 100644 --- a/packages/next-sanity/src/live/server-actions/index.ts +++ b/packages/next-sanity/src/live/server-actions/index.ts @@ -2,11 +2,12 @@ import type {ClientPerspective, SyncTag} from '@sanity/client' +import {sanitizePerspective} from '#live/sanitizePerspective' import {perspectiveCookieName} from '@sanity/preview-url-secret/constants' -import {revalidateTag} from 'next/cache' +import {revalidateTag, updateTag} from 'next/cache' import {cookies, draftMode} from 'next/headers' -import {sanitizePerspective} from '../utils' +import {PUBLISHED_SYNC_TAG_PREFIX} from '../../experimental/constants' export async function revalidateSyncTags(tags: SyncTag[]): Promise { revalidateTag('sanity:fetch-sync-tags', 'max') @@ -40,3 +41,28 @@ export async function setPerspectiveCookie(perspective: ClientPerspective): Prom }, ) } + +// @TODO expose parseTags function that returns the correct array of tags +// we already have s1: prefixes, but they could change +// use sp: for prod, sd: for draft, keep em short +export async function expireTags(_tags: unknown): Promise { + // @TODO Draft Mode bypasses cache anyway so we don't bother with expiring tags for draft content + // const isDraftMode = (await draftMode()).isEnabled + // const tags = _tags.map((tag) => `${isDraftMode ? 'drafts' : 'sanity'}:${tag}`) + if (!Array.isArray(_tags)) { + console.warn(' `expireTags` called with non-array tags', _tags) + return undefined + } + const tags = _tags.filter( + (tag) => typeof tag === 'string' && tag.startsWith(PUBLISHED_SYNC_TAG_PREFIX), + ) + if (!tags.length) { + console.warn(' `expireTags` called with no valid tags', _tags) + return undefined + } + for (const tag of tags) { + updateTag(tag) + } + // oxlint-disable-next-line no-console + console.log(` updated tags: ${tags.join(', ')}`) +} diff --git a/packages/next-sanity/src/live/hooks/context.ts b/packages/next-sanity/src/shared/client-components/context.tsx similarity index 56% rename from packages/next-sanity/src/live/hooks/context.ts rename to packages/next-sanity/src/shared/client-components/context.tsx index 7ff5e1494d4..0a86c782328 100644 --- a/packages/next-sanity/src/live/hooks/context.ts +++ b/packages/next-sanity/src/shared/client-components/context.tsx @@ -1,22 +1,12 @@ import type {ClientPerspective} from '@sanity/client' +import type {Node} from '@sanity/comlink' +import type {LoaderControllerMsg, LoaderNodeMsg} from '@sanity/presentation-comlink' -import {type Node} from '@sanity/comlink' -import {type LoaderControllerMsg, type LoaderNodeMsg} from '@sanity/presentation-comlink' +export type LivePerspective = 'checking' | 'unknown' | ClientPerspective -/** - * The Sanity Client perspective used when fetching data in Draft Mode, in the `sanityFetch` calls - * used by React Server Components on the page. Note that some of them might set the `perspective` to a different value. - * This value is what's used by default. - * @public - */ -export type DraftPerspective = 'checking' | 'unknown' | ClientPerspective - -/** @internal */ export const perspectiveListeners: Set<() => void> = new Set() -/** @internal */ -export let perspective: DraftPerspective = 'checking' -/** @internal */ -export function setPerspective(nextPerspective: DraftPerspective): void { +export let perspective: LivePerspective = 'checking' +export function setPerspective(nextPerspective: LivePerspective): void { if (perspective.toString() === nextPerspective.toString()) return perspective = nextPerspective for (const onPerspectiveChange of perspectiveListeners) { @@ -24,11 +14,7 @@ export function setPerspective(nextPerspective: DraftPerspective): void { } } -/** - * - * @public - */ -export type DraftEnvironment = +export type LiveEnvironment = | 'checking' | 'presentation-iframe' | 'presentation-window' @@ -36,34 +22,25 @@ export type DraftEnvironment = | 'static' | 'unknown' -/** @internal */ export const environmentListeners: Set<() => void> = new Set() -/** @internal */ -export let environment: DraftEnvironment = 'checking' -/** @internal */ -export function setEnvironment(nextEnvironment: DraftEnvironment): void { +export let environment: LiveEnvironment = 'checking' +export function setEnvironment(nextEnvironment: LiveEnvironment): void { environment = nextEnvironment for (const onEnvironmentChange of environmentListeners) { onEnvironmentChange() } } -/** @internal */ export const comlinkListeners: Set<() => void> = new Set() -/** @internal */ export let comlink: Node | null = null -/** @internal */ export let comlinkProjectId: string | null = null -/** @internal */ export let comlinkDataset: string | null = null -/** @internal */ export function setComlink(nextComlink: Node | null): void { comlink = nextComlink for (const onComlinkChange of comlinkListeners) { onComlinkChange() } } -/** @internal */ export function setComlinkClientConfig( nextComlinkProjectId: string | null, nextComlinkDataset: string | null, diff --git a/packages/next-sanity/src/isCorsOriginError.ts b/packages/next-sanity/src/shared/live/isCorsOriginError.ts similarity index 100% rename from packages/next-sanity/src/isCorsOriginError.ts rename to packages/next-sanity/src/shared/live/isCorsOriginError.ts diff --git a/packages/next-sanity/src/shared/live/resolvePerspectiveFromCookies.ts b/packages/next-sanity/src/shared/live/resolvePerspectiveFromCookies.ts new file mode 100644 index 00000000000..696460079cd --- /dev/null +++ b/packages/next-sanity/src/shared/live/resolvePerspectiveFromCookies.ts @@ -0,0 +1,33 @@ +import type {ClientPerspective} from '@sanity/client' +import type {cookies} from 'next/headers' + +import {sanitizePerspective} from '#live/sanitizePerspective' +import {perspectiveCookieName} from '@sanity/preview-url-secret/constants' + +export type ResolvePerspectiveFromCookies = (options: { + /** + * You must await the cookies() function from next/headers + * and pass it here. + * Example: + * ```ts + * import { cookies } from 'next/headers' + * + * const perspective = await resolvePerspectiveFromCookies({cookies: await cookies()}) + * ``` + */ + cookies: Awaited> +}) => Promise> + +/** + * Resolves the perspective from the cookie that is set by `import { defineEnableDraftMode } from "next-sanity/draft-mode"` + * @public + */ +export const resolvePerspectiveFromCookies = async function resolvePerspectiveFromCookies({ + cookies: jar, +}: { + cookies: Awaited> +}): Promise> { + return jar.has(perspectiveCookieName) + ? sanitizePerspective(jar.get(perspectiveCookieName)?.value, 'drafts') + : 'drafts' +} diff --git a/packages/next-sanity/src/live/utils.ts b/packages/next-sanity/src/shared/live/sanitizePerspective.ts similarity index 97% rename from packages/next-sanity/src/live/utils.ts rename to packages/next-sanity/src/shared/live/sanitizePerspective.ts index ef34842b762..739ef5a3279 100644 --- a/packages/next-sanity/src/live/utils.ts +++ b/packages/next-sanity/src/shared/live/sanitizePerspective.ts @@ -1,6 +1,5 @@ import {validateApiPerspective, type ClientPerspective} from '@sanity/client' -/** @internal */ export function sanitizePerspective( _perspective: unknown, fallback: 'drafts' | 'published', diff --git a/packages/next-sanity/src/shared/live/types.ts b/packages/next-sanity/src/shared/live/types.ts new file mode 100644 index 00000000000..9416563088f --- /dev/null +++ b/packages/next-sanity/src/shared/live/types.ts @@ -0,0 +1,136 @@ +import type { + ClientPerspective, + ClientReturn, + ContentSourceMap, + LiveEventGoAway, + QueryParams, + SanityClient, +} from 'next-sanity' + +/** + * Perspectives supported by Sanity Live. + * Using the legacy `'raw'` perspective is not supported and leads to undefined behavior. + */ +export type PerspectiveType = Exclude + +/** + * TODO: docs + */ +export type DefinedFetchType = (options: { + query: QueryString + params?: QueryParams + /** + * @defaultValue 'published' + */ + perspective?: PerspectiveType + /** + * Enables stega encoding of the data, this is typically only used in draft mode in conjunction with `perspective: 'drafts'` and with `@sanity/visual-editing` setup. + * @defaultValue `false` + */ + stega?: boolean + /** + * Custom cache tags that can be used with next's `updateTag` functions for custom `read-your-write` server actions, + * for example a like button that uses client.mutate to update a document and then immediately shows the result. + */ + tags?: string[] + /** + * This request tag is used to identify the request when viewing request logs from your Sanity Content Lake. + * @see https://www.sanity.io/docs/reference-api-request-tags + * @defaultValue 'next-loader.fetch' + */ + requestTag?: string +}) => Promise<{ + data: ClientReturn + sourceMap: ContentSourceMap | null + tags: string[] +}> + +export interface DefinedLiveProps { + /** + * TODO: should match the `perspective` you give `defineLive().fetch()`, setting it to a value other than `"published"` + * and with `browserToken` set will cause it to subscribe to draft content changes as well as published content. + */ + perspective?: PerspectiveType + + /** + * TODO: If Presentation Tool is present this event will fire with the current `perspective` stack used in the + * Sanity Studio global perspective menu. The default event handler will store this state in a cookie, + * which can be read with `resolvePerspectiveFromCookies` and used to ensure data fetching in the preview + * matches the perspective and content viewed in the Studio, allowing you to quickly switch and preview different perspectives. + */ + onStudioPerspective?: (perspective: PerspectiveType) => void + + /** + * Automatic refresh of RSC when the component is mounted. + * @defaultValue `false` + */ + refreshOnMount?: boolean + /** + * Automatically refresh when window gets focused + * @defaultValue `false` + */ + refreshOnFocus?: boolean + /** + * Automatically refresh when the browser regains a network connection (via navigator.onLine) + * @defaultValue `false` + */ + refreshOnReconnect?: boolean + /** + * Automatically refresh on an interval when the Live Event API emits a `goaway` event, which indicates that the connection is rejected or closed. + * This typically happens if the connection limit is reached, or if the connection is idle for too long. + * To disable this long polling fallback behavior set `intervalOnGoAway` to `false` or `0`. + * You can also use `onGoAway` to handle the `goaway` event in your own way, and read the reason why the event was emitted. + * @defaultValue `30_000` 30 seconds interval + */ + intervalOnGoAway?: number | false + + /** + * This request tag is used to identify the request when viewing request logs from your Sanity Content Lake. + * @see https://www.sanity.io/docs/reference-api-request-tags + * @defaultValue 'next-loader.live' + */ + requestTag?: string + + /** + * Handle errors from the Live Events subscription. + * By default it's reported using `console.error`, you can override this prop to handle it in your own way. + */ + onError?: (error: unknown) => void + + /** + * Handle the `goaway` event if the connection is rejected/closed. + * `event.reason` will be a string of why the event was emitted, for example `'connection limit reached'`. + * When this happens the `` will fallback to long polling with a default interval of 30 seconds, providing your own `onGoAway` handler does not change this behavior. + * If you want to disable long polling set `intervalOnGoAway` to `false` or `0`. + */ + onGoAway?: (event: LiveEventGoAway, intervalOnGoAway: number | false) => void + + /** + * TODO: docs, this handles events for published content only, and can be used to revalidate content for all your users when in presentation tool + */ + onChange?: (tags: string[]) => Promise + + /** + * TODO: docs, this handles events for all changes, published, drafts and even version documents in content releases. + * It's only used when `browserToken` is provided, and the `perspective` prop is other than `"published"`. + * Wether you should just `refresh()` or use `updateTag` to expire tags depends on how you fetch draft content and wether it's cached or not. + */ + onChangeIncludingDrafts?: (tags: string[]) => Promise +} + +export interface LiveOptions { + /** + * Required for `fetch()` and `` to work + */ + client: SanityClient + /** + * Optional. If provided then the token needs to have permissions to query documents with `drafts.` prefixes in order for `perspective: 'drafts'` to work. + * This token is never shared with the browser, unless you reuse it in `browserToken`.. + */ + serverToken?: string | false + /** + * Optional. This token is shared with the browser when `` is given a `perspective` prop other than `"published"`, and should only have access to query published documents. + * It is used to setup a `Live Draft Content` EventSource connection, and enables live previewing drafts stand-alone, outside of Presentation Tool. + */ + browserToken?: string | false +} diff --git a/packages/next-sanity/tsdown.config.ts b/packages/next-sanity/tsdown.config.ts index 6d3a8bc7925..5437644f921 100644 --- a/packages/next-sanity/tsdown.config.ts +++ b/packages/next-sanity/tsdown.config.ts @@ -7,16 +7,16 @@ import {defineConfig} from 'tsdown' export default defineConfig({ tsconfig: 'tsconfig.build.json', entry: [ + './src/cache-life.ts', './src/draft-mode/index.ts', './src/hooks/index.ts', './src/image/index.ts', - './src/experimental/live.tsx', './src/experimental/client-components/live.tsx', './src/index.ts', - './src/live.ts', - './src/live.server-only.ts', + './src/live.tsx', + './src/live.next-js.tsx', + './src/live.react-server.tsx', './src/live/client-components/live/index.ts', - './src/live/client-components/live-stream/index.ts', './src/live/server-actions/index.ts', './src/studio/client-component/index.ts', './src/studio/index.ts', @@ -25,16 +25,7 @@ export default defineConfig({ './src/visual-editing/server-actions/index.ts', './src/webhook/index.ts', ], - external: [ - 'next-sanity', - 'next-sanity/experimental/client-components/live', - 'next-sanity/live/client-components/live', - 'next-sanity/live/client-components/live-stream', - 'next-sanity/live/server-actions', - 'next-sanity/studio/client-component', - 'next-sanity/visual-editing/client-component', - 'next-sanity/visual-editing/server-actions', - ], + external: /^next-sanity(?:\/|$)/, sourcemap: true, hash: false, exports: { @@ -42,10 +33,12 @@ export default defineConfig({ devExports: true, customExports(pkg) { pkg['./live'] = { - 'react-server': pkg['./live'], - 'default': pkg['./live.server-only'], + 'next-js': pkg['./live.next-js'], + 'react-server': pkg['./live.react-server'], + 'default': pkg['./live'], } - delete pkg['./live.server-only'] + delete pkg['./live.react-server'] + delete pkg['./live.next-js'] return pkg }, }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 246be27a332..f171d67cefd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -82,7 +82,7 @@ importers: version: 4.0.1(@sanity/client@7.13.2) '@sanity/vision': specifier: 'catalog:' - version: 5.0.1(@babel/runtime@7.28.4)(@codemirror/lint@6.9.2)(@codemirror/theme-one-dark@6.1.3)(@emotion/is-prop-valid@1.4.0)(@sanity/styled-components@6.1.23(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(codemirror@6.0.2)(react-dom@19.2.3(react@19.2.3))(react-is@19.2.3)(react@19.2.3)(sanity@5.0.1(@emotion/is-prop-valid@1.4.0)(@portabletext/sanity-bridge@2.0.0(@types/react@19.2.7))(@sanity/styled-components@6.1.23(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/node@24.10.4)(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)) + version: 5.0.1(@babel/runtime@7.28.4)(@codemirror/lint@6.9.2)(@codemirror/theme-one-dark@6.1.3)(@emotion/is-prop-valid@1.4.0)(codemirror@6.0.2)(react-dom@19.2.3(react@19.2.3))(react-is@19.2.3)(react@19.2.3)(sanity@5.0.1(@emotion/is-prop-valid@1.4.0)(@portabletext/sanity-bridge@2.0.0(@types/react@19.2.7))(@types/node@24.10.4)(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(styled-components@6.1.19(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(styled-components@6.1.19(react-dom@19.2.3(react@19.2.3))(react@19.2.3)) groqd: specifier: 'catalog:' version: 1.7.1 @@ -100,7 +100,7 @@ importers: version: 19.2.3(react@19.2.3) sanity: specifier: 5.0.1 - version: 5.0.1(@emotion/is-prop-valid@1.4.0)(@portabletext/sanity-bridge@2.0.0(@types/react@19.2.7))(@sanity/styled-components@6.1.23(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/node@24.10.4)(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) + version: 5.0.1(@emotion/is-prop-valid@1.4.0)(@portabletext/sanity-bridge@2.0.0(@types/react@19.2.7))(@types/node@24.10.4)(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(styled-components@6.1.19(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) devDependencies: '@next/env': specifier: 16.1.1-canary.0 @@ -140,7 +140,7 @@ importers: version: 2.0.2 '@sanity/vision': specifier: 'catalog:' - version: 5.0.1(@babel/runtime@7.28.4)(@codemirror/lint@6.9.2)(@codemirror/theme-one-dark@6.1.3)(@emotion/is-prop-valid@1.4.0)(@sanity/styled-components@6.1.23(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(codemirror@6.0.2)(react-dom@19.2.3(react@19.2.3))(react-is@19.2.3)(react@19.2.3)(sanity@5.0.1(@emotion/is-prop-valid@1.4.0)(@portabletext/sanity-bridge@2.0.0(@types/react@19.2.7))(@sanity/styled-components@6.1.23(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/node@24.10.4)(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)) + version: 5.0.1(@babel/runtime@7.28.4)(@codemirror/lint@6.9.2)(@codemirror/theme-one-dark@6.1.3)(@emotion/is-prop-valid@1.4.0)(codemirror@6.0.2)(react-dom@19.2.3(react@19.2.3))(react-is@19.2.3)(react@19.2.3)(sanity@5.0.1(@emotion/is-prop-valid@1.4.0)(@portabletext/sanity-bridge@2.0.0(@types/react@19.2.7))(@types/node@24.10.4)(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(styled-components@6.1.19(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(styled-components@6.1.19(react-dom@19.2.3(react@19.2.3))(react@19.2.3)) groqd: specifier: 'catalog:' version: 1.7.1 @@ -158,7 +158,7 @@ importers: version: 19.2.3(react@19.2.3) sanity: specifier: 5.0.1 - version: 5.0.1(@emotion/is-prop-valid@1.4.0)(@portabletext/sanity-bridge@2.0.0(@types/react@19.2.7))(@sanity/styled-components@6.1.23(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/node@24.10.4)(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) + version: 5.0.1(@emotion/is-prop-valid@1.4.0)(@portabletext/sanity-bridge@2.0.0(@types/react@19.2.7))(@types/node@24.10.4)(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(styled-components@6.1.19(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) devDependencies: '@next/env': specifier: 16.1.1-canary.0 @@ -270,9 +270,6 @@ importers: sanity: specifier: 5.0.1 version: 5.0.1(@emotion/is-prop-valid@1.4.0)(@portabletext/sanity-bridge@2.0.0(@types/react@19.2.7))(@sanity/styled-components@6.1.23(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/node@24.10.4)(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) - server-only: - specifier: ^0.0.1 - version: 0.0.1 styled-components: specifier: npm:@sanity/styled-components@6.1.23 version: '@sanity/styled-components@6.1.23(react-dom@19.2.3(react@19.2.3))(react@19.2.3)' @@ -324,17 +321,17 @@ importers: dependencies: '@sanity/assist': specifier: ^5.0.3 - version: 5.0.3(@emotion/is-prop-valid@1.4.0)(@sanity/mutator@5.0.1(@types/react@19.2.7))(@sanity/styled-components@6.1.23(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react-is@19.2.3)(react@19.2.3)(sanity@5.0.1(@emotion/is-prop-valid@1.4.0)(@portabletext/sanity-bridge@2.0.0(@types/react@19.2.7))(@sanity/styled-components@6.1.23(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/node@25.0.3)(@types/react@19.2.7)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)) + version: 5.0.3(@emotion/is-prop-valid@1.4.0)(@sanity/mutator@5.0.1(@types/react@19.2.7))(react-dom@19.2.3(react@19.2.3))(react-is@19.2.3)(react@19.2.3)(sanity@5.0.1(@emotion/is-prop-valid@1.4.0)(@portabletext/sanity-bridge@2.0.0(@types/react@19.2.7))(@types/node@25.0.3)(@types/react@19.2.7)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(styled-components@6.1.19(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(styled-components@6.1.19(react-dom@19.2.3(react@19.2.3))(react@19.2.3)) devDependencies: '@repo/typescript-config': specifier: workspace:* version: link:../typescript-config '@sanity/vision': specifier: 'catalog:' - version: 5.0.1(@babel/runtime@7.28.4)(@codemirror/lint@6.9.2)(@codemirror/theme-one-dark@6.1.3)(@emotion/is-prop-valid@1.4.0)(@sanity/styled-components@6.1.23(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(codemirror@6.0.2)(react-dom@19.2.3(react@19.2.3))(react-is@19.2.3)(react@19.2.3)(sanity@5.0.1(@emotion/is-prop-valid@1.4.0)(@portabletext/sanity-bridge@2.0.0(@types/react@19.2.7))(@sanity/styled-components@6.1.23(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/node@25.0.3)(@types/react@19.2.7)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)) + version: 5.0.1(@babel/runtime@7.28.4)(@codemirror/lint@6.9.2)(@codemirror/theme-one-dark@6.1.3)(@emotion/is-prop-valid@1.4.0)(codemirror@6.0.2)(react-dom@19.2.3(react@19.2.3))(react-is@19.2.3)(react@19.2.3)(sanity@5.0.1(@emotion/is-prop-valid@1.4.0)(@portabletext/sanity-bridge@2.0.0(@types/react@19.2.7))(@types/node@25.0.3)(@types/react@19.2.7)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(styled-components@6.1.19(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(styled-components@6.1.19(react-dom@19.2.3(react@19.2.3))(react@19.2.3)) sanity: specifier: 5.0.1 - version: 5.0.1(@emotion/is-prop-valid@1.4.0)(@portabletext/sanity-bridge@2.0.0(@types/react@19.2.7))(@sanity/styled-components@6.1.23(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/node@25.0.3)(@types/react@19.2.7)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) + version: 5.0.1(@emotion/is-prop-valid@1.4.0)(@portabletext/sanity-bridge@2.0.0(@types/react@19.2.7))(@types/node@25.0.3)(@types/react@19.2.7)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(styled-components@6.1.19(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) packages/typescript-config: dependencies: @@ -1133,15 +1130,24 @@ packages: '@emnapi/wasi-threads@1.1.0': resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} + '@emotion/is-prop-valid@1.2.2': + resolution: {integrity: sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==} + '@emotion/is-prop-valid@1.4.0': resolution: {integrity: sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==} + '@emotion/memoize@0.8.1': + resolution: {integrity: sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==} + '@emotion/memoize@0.9.0': resolution: {integrity: sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==} '@emotion/unitless@0.10.0': resolution: {integrity: sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==} + '@emotion/unitless@0.8.1': + resolution: {integrity: sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==} + '@esbuild/aix-ppc64@0.25.12': resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} engines: {node: '>=18'} @@ -3038,6 +3044,9 @@ packages: '@types/speakingurl@13.0.6': resolution: {integrity: sha512-ywkRHNHBwq0mFs/2HRgW6TEBAzH66G8f2Txzh1aGR0UC9ZoAUHfHxLZGDhwMpck4BpSnB61eNFIFmlV+TJ+KUA==} + '@types/stylis@4.2.5': + resolution: {integrity: sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==} + '@types/stylis@4.2.7': resolution: {integrity: sha512-VgDNokpBoKF+wrdvhAAfS55OMQpL6QRglwTwNC3kIgBrzZxA4WsFj+2eLfEA/uMUDzBcEhYmjSbwQakn/i3ajA==} @@ -3363,6 +3372,9 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} + camelize@1.0.1: + resolution: {integrity: sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==} + caniuse-lite@1.0.30001760: resolution: {integrity: sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw==} @@ -3530,9 +3542,16 @@ packages: resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==} engines: {node: '>=8'} + css-color-keywords@1.0.0: + resolution: {integrity: sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==} + engines: {node: '>=4'} + css-select@5.2.2: resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==} + css-to-react-native@3.2.0: + resolution: {integrity: sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==} + css-what@6.2.2: resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==} engines: {node: '>= 6'} @@ -4985,10 +5004,17 @@ packages: resolution: {integrity: sha512-OBatVyC/N7SCW/FaDHrSd+vn0o5cS855TOmYi4OkdWUMSJCET/xip//ch8xGUvtr3i44X9LVyWwQlRMTN3pwSA==} engines: {node: '>=10'} + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + postcss@8.4.31: resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} engines: {node: ^10 || ^12 || >=14} + postcss@8.4.49: + resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==} + engines: {node: ^10 || ^12 || >=14} + postcss@8.5.6: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} @@ -5348,9 +5374,6 @@ packages: engines: {node: '>=10'} hasBin: true - server-only@0.0.1: - resolution: {integrity: sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==} - sha256-uint8array@0.10.7: resolution: {integrity: sha512-1Q6JQU4tX9NqsDGodej6pkrUVQVNapLZnvkwIhddH/JqzBZF1fSaxSWNY6sziXBE8aEa2twtGkXUrwzGeZCMpQ==} @@ -5516,6 +5539,13 @@ packages: style-mod@4.1.3: resolution: {integrity: sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==} + styled-components@6.1.19: + resolution: {integrity: sha512-1v/e3Dl1BknC37cXMhwGomhO8AkYmN41CqyX9xhUDxry1ns3BFQy2lLDRQXJRdVVWB9OHemv/53xaStimvWyuA==} + engines: {node: '>= 16'} + peerDependencies: + react: ^19.2.3 + react-dom: ^19.2.3 + styled-jsx@5.1.6: resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} engines: {node: '>= 12.0.0'} @@ -5529,6 +5559,9 @@ packages: babel-plugin-macros: optional: true + stylis@4.3.2: + resolution: {integrity: sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==} + stylis@4.3.6: resolution: {integrity: sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==} @@ -5671,6 +5704,9 @@ packages: unplugin-unused: optional: true + tslib@2.6.2: + resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} + tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -7323,14 +7359,22 @@ snapshots: tslib: 2.8.1 optional: true + '@emotion/is-prop-valid@1.2.2': + dependencies: + '@emotion/memoize': 0.8.1 + '@emotion/is-prop-valid@1.4.0': dependencies: '@emotion/memoize': 0.9.0 + '@emotion/memoize@0.8.1': {} + '@emotion/memoize@0.9.0': {} '@emotion/unitless@0.10.0': {} + '@emotion/unitless@0.8.1': {} + '@esbuild/aix-ppc64@0.25.12': optional: true @@ -8647,12 +8691,12 @@ snapshots: '@sanity/asset-utils@2.3.0': {} - '@sanity/assist@5.0.3(@emotion/is-prop-valid@1.4.0)(@sanity/mutator@5.0.1(@types/react@19.2.7))(@sanity/styled-components@6.1.23(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react-is@19.2.3)(react@19.2.3)(sanity@5.0.1(@emotion/is-prop-valid@1.4.0)(@portabletext/sanity-bridge@2.0.0(@types/react@19.2.7))(@sanity/styled-components@6.1.23(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/node@25.0.3)(@types/react@19.2.7)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))': + '@sanity/assist@5.0.3(@emotion/is-prop-valid@1.4.0)(@sanity/mutator@5.0.1(@types/react@19.2.7))(react-dom@19.2.3(react@19.2.3))(react-is@19.2.3)(react@19.2.3)(sanity@5.0.1(@emotion/is-prop-valid@1.4.0)(@portabletext/sanity-bridge@2.0.0(@types/react@19.2.7))(@types/node@25.0.3)(@types/react@19.2.7)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(styled-components@6.1.19(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(styled-components@6.1.19(react-dom@19.2.3(react@19.2.3))(react@19.2.3))': dependencies: '@sanity/icons': 3.7.4(react@19.2.3) '@sanity/incompatible-plugin': 1.0.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@sanity/mutator': 5.0.1(@types/react@19.2.7) - '@sanity/ui': 3.1.11(@emotion/is-prop-valid@1.4.0)(@sanity/styled-components@6.1.23(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react-is@19.2.3)(react@19.2.3) + '@sanity/ui': 3.1.11(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.3(react@19.2.3))(react-is@19.2.3)(react@19.2.3)(styled-components@6.1.19(react-dom@19.2.3(react@19.2.3))(react@19.2.3)) date-fns: 3.6.0 lodash: 4.17.21 lodash-es: 4.17.22 @@ -8660,8 +8704,8 @@ snapshots: react-fast-compare: 3.2.2 rxjs: 7.8.2 rxjs-exhaustmap-with-trailing: 2.1.1(rxjs@7.8.2) - sanity: 5.0.1(@emotion/is-prop-valid@1.4.0)(@portabletext/sanity-bridge@2.0.0(@types/react@19.2.7))(@sanity/styled-components@6.1.23(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/node@25.0.3)(@types/react@19.2.7)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) - styled-components: '@sanity/styled-components@6.1.23(react-dom@19.2.3(react@19.2.3))(react@19.2.3)' + sanity: 5.0.1(@emotion/is-prop-valid@1.4.0)(@portabletext/sanity-bridge@2.0.0(@types/react@19.2.7))(@types/node@25.0.3)(@types/react@19.2.7)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(styled-components@6.1.19(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) + styled-components: 6.1.19(react-dom@19.2.3(react@19.2.3))(react@19.2.3) transitivePeerDependencies: - '@emotion/is-prop-valid' - react-dom @@ -9020,6 +9064,19 @@ snapshots: - react-dom - styled-components + '@sanity/insert-menu@3.0.2(@emotion/is-prop-valid@1.4.0)(@sanity/types@5.0.1(@types/react@19.2.7))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(styled-components@6.1.19(react-dom@19.2.3(react@19.2.3))(react@19.2.3))': + dependencies: + '@sanity/icons': 3.7.4(react@19.2.3) + '@sanity/types': 5.0.1(@types/react@19.2.7)(debug@4.4.3) + '@sanity/ui': 3.1.11(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.3(react@19.2.3))(react-is@19.2.3)(react@19.2.3)(styled-components@6.1.19(react-dom@19.2.3(react@19.2.3))(react@19.2.3)) + lodash-es: 4.17.22 + react: 19.2.3 + react-is: 19.2.3 + transitivePeerDependencies: + - '@emotion/is-prop-valid' + - react-dom + - styled-components + '@sanity/json-match@1.0.5': {} '@sanity/logos@2.2.2(react@19.2.3)': @@ -9331,6 +9388,24 @@ snapshots: transitivePeerDependencies: - '@emotion/is-prop-valid' + '@sanity/ui@3.1.11(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.3(react@19.2.3))(react-is@19.2.3)(react@19.2.3)(styled-components@6.1.19(react-dom@19.2.3(react@19.2.3))(react@19.2.3))': + dependencies: + '@floating-ui/react-dom': 2.1.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@juggle/resize-observer': 3.4.0 + '@sanity/color': 3.0.6 + '@sanity/icons': 3.7.4(react@19.2.3) + csstype: 3.2.3 + motion: 12.23.26(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + react: 19.2.3 + react-compiler-runtime: 1.0.0(react@19.2.3) + react-dom: 19.2.3(react@19.2.3) + react-is: 19.2.3 + react-refractor: 4.0.0(react@19.2.3) + styled-components: 6.1.19(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + use-effect-event: 2.0.3(react@19.2.3) + transitivePeerDependencies: + - '@emotion/is-prop-valid' + '@sanity/util@5.0.1(@types/react@19.2.7)(debug@4.4.3)': dependencies: '@date-fns/tz': 1.4.1 @@ -9348,7 +9423,7 @@ snapshots: '@types/uuid': 8.3.4 uuid: 8.3.2 - '@sanity/vision@5.0.1(@babel/runtime@7.28.4)(@codemirror/lint@6.9.2)(@codemirror/theme-one-dark@6.1.3)(@emotion/is-prop-valid@1.4.0)(@sanity/styled-components@6.1.23(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(codemirror@6.0.2)(react-dom@19.2.3(react@19.2.3))(react-is@19.2.3)(react@19.2.3)(sanity@5.0.1(@emotion/is-prop-valid@1.4.0)(@portabletext/sanity-bridge@2.0.0(@types/react@19.2.7))(@sanity/styled-components@6.1.23(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/node@24.10.4)(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))': + '@sanity/vision@5.0.1(@babel/runtime@7.28.4)(@codemirror/lint@6.9.2)(@codemirror/theme-one-dark@6.1.3)(@emotion/is-prop-valid@1.4.0)(codemirror@6.0.2)(react-dom@19.2.3(react@19.2.3))(react-is@19.2.3)(react@19.2.3)(sanity@5.0.1(@emotion/is-prop-valid@1.4.0)(@portabletext/sanity-bridge@2.0.0(@types/react@19.2.7))(@types/node@24.10.4)(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(styled-components@6.1.19(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(styled-components@6.1.19(react-dom@19.2.3(react@19.2.3))(react@19.2.3))': dependencies: '@codemirror/autocomplete': 6.20.0 '@codemirror/commands': 6.10.1 @@ -9363,7 +9438,7 @@ snapshots: '@rexxars/react-split-pane': 1.0.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@sanity/color': 3.0.6 '@sanity/icons': 3.7.4(react@19.2.3) - '@sanity/ui': 3.1.11(@emotion/is-prop-valid@1.4.0)(@sanity/styled-components@6.1.23(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react-is@19.2.3)(react@19.2.3) + '@sanity/ui': 3.1.11(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.3(react@19.2.3))(react-is@19.2.3)(react@19.2.3)(styled-components@6.1.19(react-dom@19.2.3(react@19.2.3))(react@19.2.3)) '@sanity/uuid': 3.0.2 '@uiw/react-codemirror': 4.25.4(@babel/runtime@7.28.4)(@codemirror/autocomplete@6.20.0)(@codemirror/language@6.11.3)(@codemirror/lint@6.9.2)(@codemirror/search@6.5.11)(@codemirror/state@6.5.2)(@codemirror/theme-one-dark@6.1.3)(@codemirror/view@6.39.4)(codemirror@6.0.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) is-hotkey-esm: 1.0.0 @@ -9375,8 +9450,8 @@ snapshots: react-fast-compare: 3.2.2 react-rx: 4.2.2(react@19.2.3)(rxjs@7.8.2) rxjs: 7.8.2 - sanity: 5.0.1(@emotion/is-prop-valid@1.4.0)(@portabletext/sanity-bridge@2.0.0(@types/react@19.2.7))(@sanity/styled-components@6.1.23(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/node@24.10.4)(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) - styled-components: '@sanity/styled-components@6.1.23(react-dom@19.2.3(react@19.2.3))(react@19.2.3)' + sanity: 5.0.1(@emotion/is-prop-valid@1.4.0)(@portabletext/sanity-bridge@2.0.0(@types/react@19.2.7))(@types/node@24.10.4)(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(styled-components@6.1.19(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) + styled-components: 6.1.19(react-dom@19.2.3(react@19.2.3))(react@19.2.3) transitivePeerDependencies: - '@babel/runtime' - '@codemirror/lint' @@ -9386,7 +9461,7 @@ snapshots: - react-dom - react-is - '@sanity/vision@5.0.1(@babel/runtime@7.28.4)(@codemirror/lint@6.9.2)(@codemirror/theme-one-dark@6.1.3)(@emotion/is-prop-valid@1.4.0)(@sanity/styled-components@6.1.23(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(codemirror@6.0.2)(react-dom@19.2.3(react@19.2.3))(react-is@19.2.3)(react@19.2.3)(sanity@5.0.1(@emotion/is-prop-valid@1.4.0)(@portabletext/sanity-bridge@2.0.0(@types/react@19.2.7))(@sanity/styled-components@6.1.23(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/node@25.0.3)(@types/react@19.2.7)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))': + '@sanity/vision@5.0.1(@babel/runtime@7.28.4)(@codemirror/lint@6.9.2)(@codemirror/theme-one-dark@6.1.3)(@emotion/is-prop-valid@1.4.0)(codemirror@6.0.2)(react-dom@19.2.3(react@19.2.3))(react-is@19.2.3)(react@19.2.3)(sanity@5.0.1(@emotion/is-prop-valid@1.4.0)(@portabletext/sanity-bridge@2.0.0(@types/react@19.2.7))(@types/node@25.0.3)(@types/react@19.2.7)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(styled-components@6.1.19(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2))(styled-components@6.1.19(react-dom@19.2.3(react@19.2.3))(react@19.2.3))': dependencies: '@codemirror/autocomplete': 6.20.0 '@codemirror/commands': 6.10.1 @@ -9401,7 +9476,7 @@ snapshots: '@rexxars/react-split-pane': 1.0.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@sanity/color': 3.0.6 '@sanity/icons': 3.7.4(react@19.2.3) - '@sanity/ui': 3.1.11(@emotion/is-prop-valid@1.4.0)(@sanity/styled-components@6.1.23(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react-is@19.2.3)(react@19.2.3) + '@sanity/ui': 3.1.11(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.3(react@19.2.3))(react-is@19.2.3)(react@19.2.3)(styled-components@6.1.19(react-dom@19.2.3(react@19.2.3))(react@19.2.3)) '@sanity/uuid': 3.0.2 '@uiw/react-codemirror': 4.25.4(@babel/runtime@7.28.4)(@codemirror/autocomplete@6.20.0)(@codemirror/language@6.11.3)(@codemirror/lint@6.9.2)(@codemirror/search@6.5.11)(@codemirror/state@6.5.2)(@codemirror/theme-one-dark@6.1.3)(@codemirror/view@6.39.4)(codemirror@6.0.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) is-hotkey-esm: 1.0.0 @@ -9413,8 +9488,8 @@ snapshots: react-fast-compare: 3.2.2 react-rx: 4.2.2(react@19.2.3)(rxjs@7.8.2) rxjs: 7.8.2 - sanity: 5.0.1(@emotion/is-prop-valid@1.4.0)(@portabletext/sanity-bridge@2.0.0(@types/react@19.2.7))(@sanity/styled-components@6.1.23(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/node@25.0.3)(@types/react@19.2.7)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) - styled-components: '@sanity/styled-components@6.1.23(react-dom@19.2.3(react@19.2.3))(react@19.2.3)' + sanity: 5.0.1(@emotion/is-prop-valid@1.4.0)(@portabletext/sanity-bridge@2.0.0(@types/react@19.2.7))(@types/node@25.0.3)(@types/react@19.2.7)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(styled-components@6.1.19(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) + styled-components: 6.1.19(react-dom@19.2.3(react@19.2.3))(react@19.2.3) transitivePeerDependencies: - '@babel/runtime' - '@codemirror/lint' @@ -9679,6 +9754,8 @@ snapshots: '@types/speakingurl@13.0.6': {} + '@types/stylis@4.2.5': {} + '@types/stylis@4.2.7': {} '@types/tar-stream@3.1.4': @@ -9729,6 +9806,18 @@ snapshots: '@vercel/stega@1.0.0': {} + '@vitejs/plugin-react@5.1.2(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2))': + dependencies: + '@babel/core': 7.28.5 + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.5) + '@rolldown/pluginutils': 1.0.0-beta.53 + '@types/babel__core': 7.20.5 + react-refresh: 0.18.0 + vite: 7.3.0(@types/node@24.10.4)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2) + transitivePeerDependencies: + - supports-color + '@vitejs/plugin-react@5.1.2(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2))': dependencies: '@babel/core': 7.28.5 @@ -10012,6 +10101,8 @@ snapshots: callsites@3.1.0: {} + camelize@1.0.1: {} + caniuse-lite@1.0.30001760: {} cardinal@2.1.1: @@ -10188,6 +10279,8 @@ snapshots: crypto-random-string@2.0.0: {} + css-color-keywords@1.0.0: {} + css-select@5.2.2: dependencies: boolbase: 1.0.0 @@ -10196,6 +10289,12 @@ snapshots: domutils: 3.2.2 nth-check: 2.1.1 + css-to-react-native@3.2.0: + dependencies: + camelize: 1.0.1 + css-color-keywords: 1.0.0 + postcss-value-parser: 4.2.0 + css-what@6.2.2: {} cssstyle@4.6.0: @@ -11604,12 +11703,20 @@ snapshots: dependencies: '@babel/runtime': 7.28.4 + postcss-value-parser@4.2.0: {} + postcss@8.4.31: dependencies: nanoid: 3.3.11 picocolors: 1.1.1 source-map-js: 1.2.1 + postcss@8.4.49: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + postcss@8.5.6: dependencies: nanoid: 3.3.11 @@ -12031,7 +12138,7 @@ snapshots: '@types/tar-stream': 3.1.4 '@types/use-sync-external-store': 1.5.0 '@types/which': 3.0.4 - '@vitejs/plugin-react': 5.1.2(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2)) + '@vitejs/plugin-react': 5.1.2(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2)) '@xstate/react': 6.0.0(@types/react@19.2.7)(react@19.2.3)(xstate@5.25.0) archiver: 7.0.1 arrify: 2.0.1 @@ -12323,6 +12430,356 @@ snapshots: - utf-8-validate - yaml + sanity@5.0.1(@emotion/is-prop-valid@1.4.0)(@portabletext/sanity-bridge@2.0.0(@types/react@19.2.7))(@types/node@24.10.4)(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(styled-components@6.1.19(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2): + dependencies: + '@date-fns/tz': 1.4.1 + '@dnd-kit/core': 6.3.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@dnd-kit/modifiers': 6.0.1(@dnd-kit/core@6.3.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3) + '@dnd-kit/sortable': 7.0.2(@dnd-kit/core@6.3.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3) + '@dnd-kit/utilities': 3.2.2(react@19.2.3) + '@isaacs/ttlcache': 1.4.1 + '@juggle/resize-observer': 3.4.0 + '@mux/mux-player-react': 3.10.2(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@portabletext/block-tools': 5.0.0(@types/react@19.2.7)(debug@4.4.3) + '@portabletext/editor': 4.1.0(@portabletext/sanity-bridge@2.0.0(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rxjs@7.8.2) + '@portabletext/patches': 2.0.3 + '@portabletext/plugin-markdown-shortcuts': 5.0.4(@portabletext/editor@4.1.0(@portabletext/sanity-bridge@2.0.0(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rxjs@7.8.2))(@types/react@19.2.7)(react@19.2.3) + '@portabletext/plugin-one-line': 4.0.4(@portabletext/editor@4.1.0(@portabletext/sanity-bridge@2.0.0(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rxjs@7.8.2))(react@19.2.3) + '@portabletext/plugin-typography': 5.0.4(@portabletext/editor@4.1.0(@portabletext/sanity-bridge@2.0.0(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rxjs@7.8.2))(@types/react@19.2.7)(react@19.2.3) + '@portabletext/react': 6.0.0(react@19.2.3) + '@portabletext/toolkit': 5.0.0 + '@rexxars/react-json-inspector': 9.0.1(react@19.2.3) + '@sanity/asset-utils': 2.3.0 + '@sanity/bifur-client': 0.4.1 + '@sanity/cli': 5.0.1(@types/node@24.10.4)(lightningcss@1.30.2)(react@19.2.3)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) + '@sanity/client': 7.13.2(debug@4.4.3) + '@sanity/color': 3.0.6 + '@sanity/comlink': 4.0.1 + '@sanity/diff': 5.0.1 + '@sanity/diff-match-patch': 3.2.0 + '@sanity/diff-patch': 5.0.0 + '@sanity/eventsource': 5.0.2 + '@sanity/export': 6.0.2 + '@sanity/icons': 3.7.4(react@19.2.3) + '@sanity/id-utils': 1.0.0 + '@sanity/image-url': 2.0.2 + '@sanity/import': 4.0.1(@types/node@24.10.4)(@types/react@19.2.7)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2) + '@sanity/insert-menu': 3.0.2(@emotion/is-prop-valid@1.4.0)(@sanity/types@5.0.1(@types/react@19.2.7))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(styled-components@6.1.19(react-dom@19.2.3(react@19.2.3))(react@19.2.3)) + '@sanity/logos': 2.2.2(react@19.2.3) + '@sanity/media-library-types': 1.0.2 + '@sanity/message-protocol': 0.17.8 + '@sanity/migrate': 5.0.1(@types/react@19.2.7) + '@sanity/mutator': 5.0.1(@types/react@19.2.7) + '@sanity/presentation-comlink': 2.0.1(@sanity/client@7.13.2)(@sanity/types@5.0.1(@types/react@19.2.7)) + '@sanity/preview-url-secret': 4.0.1(@sanity/client@7.13.2) + '@sanity/schema': 5.0.1(@types/react@19.2.7)(debug@4.4.3) + '@sanity/sdk': 2.1.2(@types/react@19.2.7)(debug@4.4.3)(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)) + '@sanity/telemetry': 0.8.1(react@19.2.3) + '@sanity/types': 5.0.1(@types/react@19.2.7)(debug@4.4.3) + '@sanity/ui': 3.1.11(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.3(react@19.2.3))(react-is@19.2.3)(react@19.2.3)(styled-components@6.1.19(react-dom@19.2.3(react@19.2.3))(react@19.2.3)) + '@sanity/util': 5.0.1(@types/react@19.2.7)(debug@4.4.3) + '@sanity/uuid': 3.0.2 + '@sentry/react': 8.55.0(react@19.2.3) + '@tanstack/react-table': 8.21.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@tanstack/react-virtual': 3.13.13(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@types/react-is': 19.2.0 + '@types/shallow-equals': 1.0.3 + '@types/speakingurl': 13.0.6 + '@types/tar-stream': 3.1.4 + '@types/use-sync-external-store': 1.5.0 + '@types/which': 3.0.4 + '@vitejs/plugin-react': 5.1.2(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2)) + '@xstate/react': 6.0.0(@types/react@19.2.7)(react@19.2.3)(xstate@5.25.0) + archiver: 7.0.1 + arrify: 2.0.1 + async-mutex: 0.5.0 + chalk: 4.1.2 + chokidar: 3.6.0 + classnames: 2.5.1 + color2k: 2.0.3 + configstore: 5.0.1 + console-table-printer: 2.15.0 + dataloader: 2.2.3 + date-fns: 4.1.0 + debug: 4.4.3(supports-color@8.1.1) + esbuild: 0.27.1 + esbuild-register: 3.6.0(esbuild@0.27.1) + execa: 2.1.0 + exif-component: 1.0.1 + fast-deep-equal: 3.1.3 + form-data: 4.0.5 + get-it: 8.7.0(debug@4.4.3) + groq-js: 1.24.0 + gunzip-maybe: 1.4.2 + history: 5.3.0 + i18next: 23.16.8 + import-fresh: 3.3.1 + is-hotkey-esm: 1.0.0 + is-tar: 1.0.0 + isomorphic-dompurify: 2.26.0 + jsdom: 26.1.0 + jsdom-global: 3.0.2(jsdom@26.1.0) + json-lexer: 1.2.0 + json-reduce: 3.0.0 + json5: 2.2.3 + lodash-es: 4.17.22 + log-symbols: 2.2.0 + mendoza: 3.0.8 + module-alias: 2.2.3 + motion: 12.23.26(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + nano-pubsub: 3.0.0 + nanoid: 3.3.11 + node-html-parser: 6.1.13 + observable-callback: 1.0.3(rxjs@7.8.2) + oneline: 1.0.4 + open: 8.4.2 + p-map: 7.0.4 + path-to-regexp: 6.3.0 + peek-stream: 1.1.3 + pirates: 4.0.7 + player.style: 0.1.10(react@19.2.3) + pluralize-esm: 9.0.5 + polished: 4.3.1 + preferred-pm: 4.1.1 + pretty-ms: 7.0.1 + quick-lru: 7.3.0 + raf: 3.4.1 + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + react-fast-compare: 3.2.2 + react-focus-lock: 2.13.7(@types/react@19.2.7)(react@19.2.3) + react-i18next: 15.6.1(i18next@23.16.8)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3) + react-is: 19.2.3 + react-refractor: 4.0.0(react@19.2.3) + react-rx: 4.2.2(react@19.2.3)(rxjs@7.8.2) + read-pkg-up: 7.0.1 + refractor: 5.0.0 + resolve-from: 5.0.0 + resolve.exports: 2.0.3 + rimraf: 5.0.10 + rxjs: 7.8.2 + rxjs-exhaustmap-with-trailing: 2.1.1(rxjs@7.8.2) + rxjs-mergemap-array: 0.1.0(rxjs@7.8.2) + scroll-into-view-if-needed: 3.1.0 + scrollmirror: 1.2.4 + semver: 7.7.3 + shallow-equals: 1.0.0 + speakingurl: 14.0.1 + styled-components: 6.1.19(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + tar-fs: 2.1.4 + tar-stream: 3.1.7 + tinyglobby: 0.2.15 + urlpattern-polyfill: 10.1.0 + use-device-pixel-ratio: 1.1.2(react@19.2.3) + use-hot-module-reload: 2.0.0(react@19.2.3) + use-sync-external-store: 1.6.0(react@19.2.3) + uuid: 11.1.0 + vite: 7.3.0(@types/node@24.10.4)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2) + which: 5.0.0 + xstate: 5.25.0 + yargs: 17.7.2 + transitivePeerDependencies: + - '@emotion/is-prop-valid' + - '@portabletext/sanity-bridge' + - '@types/node' + - '@types/react' + - '@types/react-dom' + - babel-plugin-react-compiler + - bare-abort-controller + - bufferutil + - canvas + - immer + - jiti + - less + - lightningcss + - react-native + - react-native-b4a + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - typescript + - utf-8-validate + - yaml + + sanity@5.0.1(@emotion/is-prop-valid@1.4.0)(@portabletext/sanity-bridge@2.0.0(@types/react@19.2.7))(@types/node@25.0.3)(@types/react@19.2.7)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(styled-components@6.1.19(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2): + dependencies: + '@date-fns/tz': 1.4.1 + '@dnd-kit/core': 6.3.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@dnd-kit/modifiers': 6.0.1(@dnd-kit/core@6.3.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3) + '@dnd-kit/sortable': 7.0.2(@dnd-kit/core@6.3.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3) + '@dnd-kit/utilities': 3.2.2(react@19.2.3) + '@isaacs/ttlcache': 1.4.1 + '@juggle/resize-observer': 3.4.0 + '@mux/mux-player-react': 3.10.2(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@portabletext/block-tools': 5.0.0(@types/react@19.2.7)(debug@4.4.3) + '@portabletext/editor': 4.1.0(@portabletext/sanity-bridge@2.0.0(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rxjs@7.8.2) + '@portabletext/patches': 2.0.3 + '@portabletext/plugin-markdown-shortcuts': 5.0.4(@portabletext/editor@4.1.0(@portabletext/sanity-bridge@2.0.0(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rxjs@7.8.2))(@types/react@19.2.7)(react@19.2.3) + '@portabletext/plugin-one-line': 4.0.4(@portabletext/editor@4.1.0(@portabletext/sanity-bridge@2.0.0(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rxjs@7.8.2))(react@19.2.3) + '@portabletext/plugin-typography': 5.0.4(@portabletext/editor@4.1.0(@portabletext/sanity-bridge@2.0.0(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rxjs@7.8.2))(@types/react@19.2.7)(react@19.2.3) + '@portabletext/react': 6.0.0(react@19.2.3) + '@portabletext/toolkit': 5.0.0 + '@rexxars/react-json-inspector': 9.0.1(react@19.2.3) + '@sanity/asset-utils': 2.3.0 + '@sanity/bifur-client': 0.4.1 + '@sanity/cli': 5.0.1(@types/node@25.0.3)(lightningcss@1.30.2)(react@19.2.3)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) + '@sanity/client': 7.13.2(debug@4.4.3) + '@sanity/color': 3.0.6 + '@sanity/comlink': 4.0.1 + '@sanity/diff': 5.0.1 + '@sanity/diff-match-patch': 3.2.0 + '@sanity/diff-patch': 5.0.0 + '@sanity/eventsource': 5.0.2 + '@sanity/export': 6.0.2 + '@sanity/icons': 3.7.4(react@19.2.3) + '@sanity/id-utils': 1.0.0 + '@sanity/image-url': 2.0.2 + '@sanity/import': 4.0.1(@types/node@25.0.3)(@types/react@19.2.7)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2) + '@sanity/insert-menu': 3.0.2(@emotion/is-prop-valid@1.4.0)(@sanity/types@5.0.1(@types/react@19.2.7))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(styled-components@6.1.19(react-dom@19.2.3(react@19.2.3))(react@19.2.3)) + '@sanity/logos': 2.2.2(react@19.2.3) + '@sanity/media-library-types': 1.0.2 + '@sanity/message-protocol': 0.17.8 + '@sanity/migrate': 5.0.1(@types/react@19.2.7) + '@sanity/mutator': 5.0.1(@types/react@19.2.7) + '@sanity/presentation-comlink': 2.0.1(@sanity/client@7.13.2)(@sanity/types@5.0.1(@types/react@19.2.7)) + '@sanity/preview-url-secret': 4.0.1(@sanity/client@7.13.2) + '@sanity/schema': 5.0.1(@types/react@19.2.7)(debug@4.4.3) + '@sanity/sdk': 2.1.2(@types/react@19.2.7)(debug@4.4.3)(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)) + '@sanity/telemetry': 0.8.1(react@19.2.3) + '@sanity/types': 5.0.1(@types/react@19.2.7)(debug@4.4.3) + '@sanity/ui': 3.1.11(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.3(react@19.2.3))(react-is@19.2.3)(react@19.2.3)(styled-components@6.1.19(react-dom@19.2.3(react@19.2.3))(react@19.2.3)) + '@sanity/util': 5.0.1(@types/react@19.2.7)(debug@4.4.3) + '@sanity/uuid': 3.0.2 + '@sentry/react': 8.55.0(react@19.2.3) + '@tanstack/react-table': 8.21.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@tanstack/react-virtual': 3.13.13(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@types/react-is': 19.2.0 + '@types/shallow-equals': 1.0.3 + '@types/speakingurl': 13.0.6 + '@types/tar-stream': 3.1.4 + '@types/use-sync-external-store': 1.5.0 + '@types/which': 3.0.4 + '@vitejs/plugin-react': 5.1.2(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2)) + '@xstate/react': 6.0.0(@types/react@19.2.7)(react@19.2.3)(xstate@5.25.0) + archiver: 7.0.1 + arrify: 2.0.1 + async-mutex: 0.5.0 + chalk: 4.1.2 + chokidar: 3.6.0 + classnames: 2.5.1 + color2k: 2.0.3 + configstore: 5.0.1 + console-table-printer: 2.15.0 + dataloader: 2.2.3 + date-fns: 4.1.0 + debug: 4.4.3(supports-color@8.1.1) + esbuild: 0.27.1 + esbuild-register: 3.6.0(esbuild@0.27.1) + execa: 2.1.0 + exif-component: 1.0.1 + fast-deep-equal: 3.1.3 + form-data: 4.0.5 + get-it: 8.7.0(debug@4.4.3) + groq-js: 1.24.0 + gunzip-maybe: 1.4.2 + history: 5.3.0 + i18next: 23.16.8 + import-fresh: 3.3.1 + is-hotkey-esm: 1.0.0 + is-tar: 1.0.0 + isomorphic-dompurify: 2.26.0 + jsdom: 26.1.0 + jsdom-global: 3.0.2(jsdom@26.1.0) + json-lexer: 1.2.0 + json-reduce: 3.0.0 + json5: 2.2.3 + lodash-es: 4.17.22 + log-symbols: 2.2.0 + mendoza: 3.0.8 + module-alias: 2.2.3 + motion: 12.23.26(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + nano-pubsub: 3.0.0 + nanoid: 3.3.11 + node-html-parser: 6.1.13 + observable-callback: 1.0.3(rxjs@7.8.2) + oneline: 1.0.4 + open: 8.4.2 + p-map: 7.0.4 + path-to-regexp: 6.3.0 + peek-stream: 1.1.3 + pirates: 4.0.7 + player.style: 0.1.10(react@19.2.3) + pluralize-esm: 9.0.5 + polished: 4.3.1 + preferred-pm: 4.1.1 + pretty-ms: 7.0.1 + quick-lru: 7.3.0 + raf: 3.4.1 + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + react-fast-compare: 3.2.2 + react-focus-lock: 2.13.7(@types/react@19.2.7)(react@19.2.3) + react-i18next: 15.6.1(i18next@23.16.8)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3) + react-is: 19.2.3 + react-refractor: 4.0.0(react@19.2.3) + react-rx: 4.2.2(react@19.2.3)(rxjs@7.8.2) + read-pkg-up: 7.0.1 + refractor: 5.0.0 + resolve-from: 5.0.0 + resolve.exports: 2.0.3 + rimraf: 5.0.10 + rxjs: 7.8.2 + rxjs-exhaustmap-with-trailing: 2.1.1(rxjs@7.8.2) + rxjs-mergemap-array: 0.1.0(rxjs@7.8.2) + scroll-into-view-if-needed: 3.1.0 + scrollmirror: 1.2.4 + semver: 7.7.3 + shallow-equals: 1.0.0 + speakingurl: 14.0.1 + styled-components: 6.1.19(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + tar-fs: 2.1.4 + tar-stream: 3.1.7 + tinyglobby: 0.2.15 + urlpattern-polyfill: 10.1.0 + use-device-pixel-ratio: 1.1.2(react@19.2.3) + use-hot-module-reload: 2.0.0(react@19.2.3) + use-sync-external-store: 1.6.0(react@19.2.3) + uuid: 11.1.0 + vite: 7.3.0(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2) + which: 5.0.0 + xstate: 5.25.0 + yargs: 17.7.2 + transitivePeerDependencies: + - '@emotion/is-prop-valid' + - '@portabletext/sanity-bridge' + - '@types/node' + - '@types/react' + - '@types/react-dom' + - babel-plugin-react-compiler + - bare-abort-controller + - bufferutil + - canvas + - immer + - jiti + - less + - lightningcss + - react-native + - react-native-b4a + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - typescript + - utf-8-validate + - yaml + saxes@6.0.0: dependencies: xmlchars: 2.2.0 @@ -12341,8 +12798,6 @@ snapshots: semver@7.7.3: {} - server-only@0.0.1: {} - sha256-uint8array@0.10.7: {} shallow-clone@3.0.1: @@ -12533,6 +12988,20 @@ snapshots: style-mod@4.1.3: {} + styled-components@6.1.19(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + dependencies: + '@emotion/is-prop-valid': 1.2.2 + '@emotion/unitless': 0.8.1 + '@types/stylis': 4.2.5 + css-to-react-native: 3.2.0 + csstype: 3.1.3 + postcss: 8.4.49 + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + shallowequal: 1.1.0 + stylis: 4.3.2 + tslib: 2.6.2 + styled-jsx@5.1.6(@babel/core@7.28.5)(react@19.2.3): dependencies: client-only: 0.0.1 @@ -12540,6 +13009,8 @@ snapshots: optionalDependencies: '@babel/core': 7.28.5 + stylis@4.3.2: {} + stylis@4.3.6: {} supports-color@5.5.0: @@ -12680,6 +13151,8 @@ snapshots: - synckit - vue-tsc + tslib@2.6.2: {} + tslib@2.8.1: {} tsx@4.21.0: