From 40e5df7f551aa24a399cdb2c7f3a63155ba8f39d Mon Sep 17 00:00:00 2001 From: Cody Olsen Date: Thu, 18 Dec 2025 23:50:14 +0100 Subject: [PATCH 01/12] SanityLive + `cacheComponents: true` --- .changeset/better-forks-ring.md | 5 + .changeset/bright-jokes-stay.md | 5 + .changeset/fair-rats-sell.md | 5 + .changeset/four-jobs-type.md | 5 + .changeset/pre.json | 17 + .changeset/tall-flies-sing.md | 5 + .changeset/tender-parents-mix.md | 6 + .changeset/true-dragons-wink.md | 5 + .changeset/yummy-bears-juggle.md | 5 + apps/mvp/app/(website)/layout.tsx | 38 +- apps/mvp/app/(website)/live.ts | 2 +- apps/mvp/app/(website)/page.tsx | 2 +- apps/mvp/app/api/revalidate-tag/route.ts | 32 -- packages/next-sanity/CHANGELOG.md | 42 ++ packages/next-sanity/package.json | 29 +- packages/next-sanity/src/debug.ts | 16 + .../client-components/PresentationComlink.tsx | 15 +- .../experimental/client-components/live.tsx | 6 +- .../next-sanity/src/experimental/live.tsx | 18 +- packages/next-sanity/src/index.ts | 2 +- packages/next-sanity/src/live.next-js.tsx | 14 + .../next-sanity/src/live.react-server.tsx | 14 + packages/next-sanity/src/live.server-only.ts | 30 - packages/next-sanity/src/live.ts | 8 - packages/next-sanity/src/live.tsx | 38 ++ .../live-stream/SanityLiveStream.tsx | 145 ----- .../live-stream/SanityLiveStreamLazy.tsx | 15 - .../client-components/live-stream/index.ts | 4 - .../live/PresentationComlink.tsx | 3 +- .../client-components/live/SanityLive.tsx | 5 +- packages/next-sanity/src/live/defineLive.tsx | 122 ---- packages/next-sanity/src/live/hooks/index.ts | 2 - .../src/live/hooks/useDraftMode.ts | 13 +- .../src/live/hooks/usePresentationQuery.ts | 10 +- .../src/live/resolveCookiePerspective.ts | 3 +- .../src/live/server-actions/index.ts | 3 +- .../client-components/context.tsx} | 39 +- .../{ => shared/live}/isCorsOriginError.ts | 0 .../live/resolvePerspectiveFromCookies.ts | 33 ++ .../live/sanitizePerspective.ts} | 1 - packages/next-sanity/tsdown.config.ts | 27 +- pnpm-lock.yaml | 529 +++++++++++++++++- 42 files changed, 816 insertions(+), 502 deletions(-) create mode 100644 .changeset/better-forks-ring.md create mode 100644 .changeset/bright-jokes-stay.md create mode 100644 .changeset/fair-rats-sell.md create mode 100644 .changeset/four-jobs-type.md create mode 100644 .changeset/pre.json create mode 100644 .changeset/tall-flies-sing.md create mode 100644 .changeset/tender-parents-mix.md create mode 100644 .changeset/true-dragons-wink.md create mode 100644 .changeset/yummy-bears-juggle.md delete mode 100644 apps/mvp/app/api/revalidate-tag/route.ts create mode 100644 packages/next-sanity/src/debug.ts create mode 100644 packages/next-sanity/src/live.next-js.tsx create mode 100644 packages/next-sanity/src/live.react-server.tsx delete mode 100644 packages/next-sanity/src/live.server-only.ts delete mode 100644 packages/next-sanity/src/live.ts create mode 100644 packages/next-sanity/src/live.tsx delete mode 100644 packages/next-sanity/src/live/client-components/live-stream/SanityLiveStream.tsx delete mode 100644 packages/next-sanity/src/live/client-components/live-stream/SanityLiveStreamLazy.tsx delete mode 100644 packages/next-sanity/src/live/client-components/live-stream/index.ts rename packages/next-sanity/src/{live/hooks/context.ts => shared/client-components/context.tsx} (56%) rename packages/next-sanity/src/{ => shared/live}/isCorsOriginError.ts (100%) create mode 100644 packages/next-sanity/src/shared/live/resolvePerspectiveFromCookies.ts rename packages/next-sanity/src/{live/utils.ts => shared/live/sanitizePerspective.ts} (97%) 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/pre.json b/.changeset/pre.json new file mode 100644 index 00000000000..cdf5a19c0c4 --- /dev/null +++ b/.changeset/pre.json @@ -0,0 +1,17 @@ +{ + "mode": "pre", + "tag": "cache-components", + "initialVersions": { + "next-sanity": "12.0.3" + }, + "changesets": [ + "better-forks-ring", + "bright-jokes-stay", + "fair-rats-sell", + "four-jobs-type", + "tall-flies-sing", + "tender-parents-mix", + "true-dragons-wink", + "yummy-bears-juggle" + ] +} 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/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)/layout.tsx b/apps/mvp/app/(website)/layout.tsx index dc2645a2ddb..b9e9c677a90 100644 --- a/apps/mvp/app/(website)/layout.tsx +++ b/apps/mvp/app/(website)/layout.tsx @@ -5,14 +5,21 @@ import { // cookies, draftMode, } from 'next/headers' +// import {resolvePerspectiveFromCookies} from 'next-sanity/experimental/live' +// import {Suspense} from 'react' +import {debug} from 'next-sanity/debug' import {VisualEditing} from 'next-sanity/visual-editing' +import {refresh, updateTag} from 'next/cache' import {DebugStatus} from './DebugStatus' import {FormStatusLabel} from './FormStatus' import {SanityLive} from './live' import {RefreshButton} from './RefreshButton' -// import {resolvePerspectiveFromCookies} from 'next-sanity/experimental/live' -// import {Suspense} from 'react' + +async function cacheDebug() { + 'use cache: remote' + return await debug() +} async function toggleDraftMode() { 'use server' @@ -33,6 +40,33 @@ export default async function RootLayout({children}: {children: React.ReactNode}
+

Debug: {JSON.stringify(await cacheDebug())}

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

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

{isDraftMode && }
diff --git a/apps/mvp/app/(website)/live.ts b/apps/mvp/app/(website)/live.ts index fe8ae629386..38ff5e2d6dd 100644 --- a/apps/mvp/app/(website)/live.ts +++ b/apps/mvp/app/(website)/live.ts @@ -1,4 +1,4 @@ -import {defineLive} from 'next-sanity/experimental/live' +import {defineLive} from 'next-sanity/live' import {client} from '@/app/sanity.client' diff --git a/apps/mvp/app/(website)/page.tsx b/apps/mvp/app/(website)/page.tsx index 60ddcf928d7..002a71fda98 100644 --- a/apps/mvp/app/(website)/page.tsx +++ b/apps/mvp/app/(website)/page.tsx @@ -1,5 +1,5 @@ import {unstable__adapter, unstable__environment} from 'next-sanity' -import {resolvePerspectiveFromCookies} from 'next-sanity/experimental/live' +import {resolvePerspectiveFromCookies} from 'next-sanity/live' import {cookies, draftMode} from 'next/headers' import Link from 'next/link' 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/packages/next-sanity/CHANGELOG.md b/packages/next-sanity/CHANGELOG.md index 0807058db47..5d5b230d011 100644 --- a/packages/next-sanity/CHANGELOG.md +++ b/packages/next-sanity/CHANGELOG.md @@ -1,5 +1,47 @@ # next-sanity +## 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..9d715436f50 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.5", "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", + "./debug": "./src/debug.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", + "./debug": "./dist/debug.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/debug.ts b/packages/next-sanity/src/debug.ts new file mode 100644 index 00000000000..a76d644d077 --- /dev/null +++ b/packages/next-sanity/src/debug.ts @@ -0,0 +1,16 @@ +import {cacheTag} from 'next/cache' + +const tag = 'sanity:debug' + +export async function debug(): Promise<{data: number; cacheTags: [typeof tag]}> { + const {resolve, promise} = Promise.withResolvers() + + cacheTag(tag) + + setTimeout(() => resolve(Math.random()), 1_000) + + return { + data: await promise, + cacheTags: [tag], + } +} 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..fdc16089934 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, @@ -14,9 +17,6 @@ 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' const PresentationComlink = dynamic(() => import('./PresentationComlink'), {ssr: false}) diff --git a/packages/next-sanity/src/experimental/live.tsx b/packages/next-sanity/src/experimental/live.tsx index 1ceaf56229a..938b720a68b 100644 --- a/packages/next-sanity/src/experimental/live.tsx +++ b/packages/next-sanity/src/experimental/live.tsx @@ -1,7 +1,5 @@ -// oxlint-disable-next-line no-unassigned-import -import 'server-only' +import {resolvePerspectiveFromCookies} from '#live/resolvePerspectiveFromCookies' import {stegaEncodeSourceMap} from '@sanity/client/stega' -import {perspectiveCookieName} from '@sanity/preview-url-secret/constants' import { createClient, type ClientPerspective, @@ -21,22 +19,8 @@ import {preconnect} from 'react-dom' import type {SanityClientConfig} from './types' -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, { 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..2092365c5d1 --- /dev/null +++ b/packages/next-sanity/src/live.next-js.tsx @@ -0,0 +1,14 @@ +// 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 { + type DefineSanityLiveOptions, + type DefinedSanityFetchType, + type DefinedSanityLiveProps, + type SanityFetchOptions, + defineLive, +} from './experimental/live' + +export {resolvePerspectiveFromCookies} from '#live/resolvePerspectiveFromCookies' 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..fec69e0758a --- /dev/null +++ b/packages/next-sanity/src/live.react-server.tsx @@ -0,0 +1,14 @@ +// 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 { + type DefineSanityLiveOptions, + type DefinedSanityFetchType, + type DefinedSanityLiveProps, + defineLive, +} from './live/defineLive' + +export {resolvePerspectiveFromCookies} from '#live/resolvePerspectiveFromCookies' 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..dda147f75ac --- /dev/null +++ b/packages/next-sanity/src/live.tsx @@ -0,0 +1,38 @@ +// 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. + +export {isCorsOriginError} from '#live/isCorsOriginError' + +import type { + DefineSanityLiveOptions, + DefinedSanityFetchType, + DefinedSanityLiveProps, +} from './live/defineLive' + +/** + * @public + */ +export function defineLive(_config: DefineSanityLiveOptions): { + sanityFetch: DefinedSanityFetchType + SanityLive: React.ComponentType +} { + throw new Error(`defineLive can't be imported by a client component`) +} + +/** + * @public + */ +export type {DefineSanityLiveOptions, DefinedSanityFetchType, DefinedSanityLiveProps} + +import type {ResolvePerspectiveFromCookies} from '#live/resolvePerspectiveFromCookies' + +/** + * 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`) +} 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..3d7f1e1be1f 100644 --- a/packages/next-sanity/src/live/defineLive.tsx +++ b/packages/next-sanity/src/live/defineLive.tsx @@ -8,7 +8,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' @@ -45,40 +44,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 */ @@ -196,11 +161,6 @@ export function defineLive(config: DefineSanityLiveOptions): { * @public */ SanityLive: React.ComponentType - /** - * @alpha experimental, it may change or even be removed at any time - */ - SanityLiveStream: DefinedSanityLiveStreamType - // verifyPreviewSecret: VerifyPreviewSecretType } { const { client: _client, @@ -332,90 +292,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..0c6376e87b2 100644 --- a/packages/next-sanity/src/live/server-actions/index.ts +++ b/packages/next-sanity/src/live/server-actions/index.ts @@ -2,12 +2,11 @@ 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 {cookies, draftMode} from 'next/headers' -import {sanitizePerspective} from '../utils' - export async function revalidateSyncTags(tags: SyncTag[]): Promise { revalidateTag('sanity:fetch-sync-tags', 'max') 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/tsdown.config.ts b/packages/next-sanity/tsdown.config.ts index 6d3a8bc7925..af85c5c9659 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/debug.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: From 013d655159e5ce5fee3e03106b57a9f344beec9b Mon Sep 17 00:00:00 2001 From: Cody Olsen Date: Fri, 19 Dec 2025 14:44:22 +0100 Subject: [PATCH 02/12] Add `import {sanity} from 'next-sanity/cache-life'` --- .changeset/heavy-doors-ask.md | 5 +++ apps/mvp/app/(website)/layout.tsx | 3 +- apps/mvp/next.config.ts | 3 ++ packages/next-sanity/package.json | 2 ++ packages/next-sanity/src/cache-life.ts | 43 ++++++++++++++++++++++++++ packages/next-sanity/tsdown.config.ts | 1 + 6 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 .changeset/heavy-doors-ask.md create mode 100644 packages/next-sanity/src/cache-life.ts 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/apps/mvp/app/(website)/layout.tsx b/apps/mvp/app/(website)/layout.tsx index b9e9c677a90..f508b6c5d90 100644 --- a/apps/mvp/app/(website)/layout.tsx +++ b/apps/mvp/app/(website)/layout.tsx @@ -9,7 +9,7 @@ import { // import {Suspense} from 'react' import {debug} from 'next-sanity/debug' import {VisualEditing} from 'next-sanity/visual-editing' -import {refresh, updateTag} from 'next/cache' +import {cacheLife, refresh, updateTag} from 'next/cache' import {DebugStatus} from './DebugStatus' import {FormStatusLabel} from './FormStatus' @@ -18,6 +18,7 @@ import {RefreshButton} from './RefreshButton' async function cacheDebug() { 'use cache: remote' + cacheLife('sanity') return await debug() } 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/packages/next-sanity/package.json b/packages/next-sanity/package.json index 9d715436f50..527445d3b18 100644 --- a/packages/next-sanity/package.json +++ b/packages/next-sanity/package.json @@ -36,6 +36,7 @@ }, "exports": { ".": "./src/index.ts", + "./cache-life": "./src/cache-life.ts", "./debug": "./src/debug.ts", "./draft-mode": "./src/draft-mode/index.ts", "./experimental/client-components/live": "./src/experimental/client-components/live.tsx", @@ -59,6 +60,7 @@ "publishConfig": { "exports": { ".": "./dist/index.js", + "./cache-life": "./dist/cache-life.js", "./debug": "./dist/debug.js", "./draft-mode": "./dist/draft-mode/index.js", "./experimental/client-components/live": "./dist/experimental/client-components/live.js", diff --git a/packages/next-sanity/src/cache-life.ts b/packages/next-sanity/src/cache-life.ts new file mode 100644 index 00000000000..fdf38bc10de --- /dev/null +++ b/packages/next-sanity/src/cache-life.ts @@ -0,0 +1,43 @@ +/** + * 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 = { + 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/tsdown.config.ts b/packages/next-sanity/tsdown.config.ts index af85c5c9659..d654a1707e1 100644 --- a/packages/next-sanity/tsdown.config.ts +++ b/packages/next-sanity/tsdown.config.ts @@ -7,6 +7,7 @@ import {defineConfig} from 'tsdown' export default defineConfig({ tsconfig: 'tsconfig.build.json', entry: [ + './src/cache-life.ts', './src/debug.ts', './src/draft-mode/index.ts', './src/hooks/index.ts', From 375dcc128498ce632b8a78ccefde8e290b8215b2 Mon Sep 17 00:00:00 2001 From: "squiggler[bot]" <128108030+squiggler[bot]@users.noreply.github.com> Date: Fri, 19 Dec 2025 14:46:06 +0100 Subject: [PATCH 03/12] Version Packages (cache-components) (#3121) Co-authored-by: squiggler[bot] <128108030+squiggler[bot]@users.noreply.github.com> --- .changeset/pre.json | 1 + packages/next-sanity/CHANGELOG.md | 6 ++++++ packages/next-sanity/package.json | 2 +- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.changeset/pre.json b/.changeset/pre.json index cdf5a19c0c4..a26ec3a7d0f 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -9,6 +9,7 @@ "bright-jokes-stay", "fair-rats-sell", "four-jobs-type", + "heavy-doors-ask", "tall-flies-sing", "tender-parents-mix", "true-dragons-wink", diff --git a/packages/next-sanity/CHANGELOG.md b/packages/next-sanity/CHANGELOG.md index 5d5b230d011..bebc15d5f5b 100644 --- a/packages/next-sanity/CHANGELOG.md +++ b/packages/next-sanity/CHANGELOG.md @@ -1,5 +1,11 @@ # next-sanity +## 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 diff --git a/packages/next-sanity/package.json b/packages/next-sanity/package.json index 527445d3b18..cd0111ba8f1 100644 --- a/packages/next-sanity/package.json +++ b/packages/next-sanity/package.json @@ -1,6 +1,6 @@ { "name": "next-sanity", - "version": "13.0.0-cache-components.5", + "version": "13.0.0-cache-components.6", "description": "Sanity.io toolkit for Next.js", "keywords": [ "live", From ddad769dbf815dbd1de0c5d6b4fc11cf2fcb3061 Mon Sep 17 00:00:00 2001 From: Cody Olsen Date: Fri, 19 Dec 2025 14:53:02 +0100 Subject: [PATCH 04/12] Remove `next-sanity/debug` --- .changeset/moody-news-stay.md | 5 +++++ apps/mvp/app/(website)/layout.tsx | 11 ++--------- packages/next-sanity/package.json | 2 -- packages/next-sanity/src/debug.ts | 16 ---------------- packages/next-sanity/tsdown.config.ts | 1 - 5 files changed, 7 insertions(+), 28 deletions(-) create mode 100644 .changeset/moody-news-stay.md delete mode 100644 packages/next-sanity/src/debug.ts 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/apps/mvp/app/(website)/layout.tsx b/apps/mvp/app/(website)/layout.tsx index f508b6c5d90..1dbb6f075a8 100644 --- a/apps/mvp/app/(website)/layout.tsx +++ b/apps/mvp/app/(website)/layout.tsx @@ -7,21 +7,14 @@ import { } from 'next/headers' // import {resolvePerspectiveFromCookies} from 'next-sanity/experimental/live' // import {Suspense} from 'react' -import {debug} from 'next-sanity/debug' import {VisualEditing} from 'next-sanity/visual-editing' -import {cacheLife, refresh, updateTag} from 'next/cache' +import {refresh, updateTag} from 'next/cache' import {DebugStatus} from './DebugStatus' import {FormStatusLabel} from './FormStatus' import {SanityLive} from './live' import {RefreshButton} from './RefreshButton' -async function cacheDebug() { - 'use cache: remote' - cacheLife('sanity') - return await debug() -} - async function toggleDraftMode() { 'use server' @@ -41,7 +34,7 @@ export default async function RootLayout({children}: {children: React.ReactNode}
-

Debug: {JSON.stringify(await cacheDebug())}

+

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

{ 'use server' diff --git a/packages/next-sanity/package.json b/packages/next-sanity/package.json index cd0111ba8f1..54914bccf90 100644 --- a/packages/next-sanity/package.json +++ b/packages/next-sanity/package.json @@ -37,7 +37,6 @@ "exports": { ".": "./src/index.ts", "./cache-life": "./src/cache-life.ts", - "./debug": "./src/debug.ts", "./draft-mode": "./src/draft-mode/index.ts", "./experimental/client-components/live": "./src/experimental/client-components/live.tsx", "./hooks": "./src/hooks/index.ts", @@ -61,7 +60,6 @@ "exports": { ".": "./dist/index.js", "./cache-life": "./dist/cache-life.js", - "./debug": "./dist/debug.js", "./draft-mode": "./dist/draft-mode/index.js", "./experimental/client-components/live": "./dist/experimental/client-components/live.js", "./hooks": "./dist/hooks/index.js", diff --git a/packages/next-sanity/src/debug.ts b/packages/next-sanity/src/debug.ts deleted file mode 100644 index a76d644d077..00000000000 --- a/packages/next-sanity/src/debug.ts +++ /dev/null @@ -1,16 +0,0 @@ -import {cacheTag} from 'next/cache' - -const tag = 'sanity:debug' - -export async function debug(): Promise<{data: number; cacheTags: [typeof tag]}> { - const {resolve, promise} = Promise.withResolvers() - - cacheTag(tag) - - setTimeout(() => resolve(Math.random()), 1_000) - - return { - data: await promise, - cacheTags: [tag], - } -} diff --git a/packages/next-sanity/tsdown.config.ts b/packages/next-sanity/tsdown.config.ts index d654a1707e1..5437644f921 100644 --- a/packages/next-sanity/tsdown.config.ts +++ b/packages/next-sanity/tsdown.config.ts @@ -8,7 +8,6 @@ export default defineConfig({ tsconfig: 'tsconfig.build.json', entry: [ './src/cache-life.ts', - './src/debug.ts', './src/draft-mode/index.ts', './src/hooks/index.ts', './src/image/index.ts', From d97d1f44fbc0de02567dc3594868a7f518f467f8 Mon Sep 17 00:00:00 2001 From: Cody Olsen Date: Fri, 19 Dec 2025 15:54:27 +0100 Subject: [PATCH 05/12] streamline exports --- .changeset/twelve-moose-work.md | 5 + packages/next-sanity/src/cache-life.ts | 3 + .../next-sanity/src/experimental/live.tsx | 9 +- packages/next-sanity/src/live.next-js.tsx | 10 +- .../next-sanity/src/live.react-server.tsx | 8 +- packages/next-sanity/src/shared/live/types.ts | 136 ++++++++++++++++++ 6 files changed, 151 insertions(+), 20 deletions(-) create mode 100644 .changeset/twelve-moose-work.md create mode 100644 packages/next-sanity/src/shared/live/types.ts 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/packages/next-sanity/src/cache-life.ts b/packages/next-sanity/src/cache-life.ts index fdf38bc10de..5fbd21bc7f1 100644 --- a/packages/next-sanity/src/cache-life.ts +++ b/packages/next-sanity/src/cache-life.ts @@ -26,6 +26,9 @@ * } */ 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 { /** diff --git a/packages/next-sanity/src/experimental/live.tsx b/packages/next-sanity/src/experimental/live.tsx index 938b720a68b..36cf3ae460e 100644 --- a/packages/next-sanity/src/experimental/live.tsx +++ b/packages/next-sanity/src/experimental/live.tsx @@ -13,7 +13,7 @@ import { import SanityLiveClientComponent, { type SanityLiveProps, } from 'next-sanity/experimental/client-components/live' -import {cacheTag, cacheLife, updateTag} from 'next/cache' +import {cacheTag, updateTag} from 'next/cache' import {draftMode, cookies} from 'next/headers' import {preconnect} from 'react-dom' @@ -45,7 +45,7 @@ async function sanityCachedFetch( sourceMap: ContentSourceMap | null tags: string[] }> { - 'use cache: remote' + // 'use cache: remote' const client = createClient({...config, useCdn: true}) const useCdn = perspective === 'published' @@ -75,7 +75,7 @@ async function sanityCachedFetch( /** * Sanity Live handles on-demand revalidation, so the default 15min time based revalidation is too short */ - cacheLife({revalidate: 60 * 60 * 24 * 90}) + // cacheLife({revalidate: 60 * 60 * 24 * 90}) return {data: result, sourceMap: resultSourceMap || null, tags} } @@ -102,7 +102,8 @@ export interface SanityFetchOptions { */ requestTag?: string /** - * Custom cache tags that can be used with next's `revalidateTag` and `updateTag` functions for custom webhook on-demand revalidation. + * 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[] } diff --git a/packages/next-sanity/src/live.next-js.tsx b/packages/next-sanity/src/live.next-js.tsx index 2092365c5d1..adaa61c6c70 100644 --- a/packages/next-sanity/src/live.next-js.tsx +++ b/packages/next-sanity/src/live.next-js.tsx @@ -2,13 +2,5 @@ // 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 { - type DefineSanityLiveOptions, - type DefinedSanityFetchType, - type DefinedSanityLiveProps, - type SanityFetchOptions, - defineLive, -} from './experimental/live' - +export {defineLive} from './experimental/live' export {resolvePerspectiveFromCookies} from '#live/resolvePerspectiveFromCookies' diff --git a/packages/next-sanity/src/live.react-server.tsx b/packages/next-sanity/src/live.react-server.tsx index fec69e0758a..1fc6b60949c 100644 --- a/packages/next-sanity/src/live.react-server.tsx +++ b/packages/next-sanity/src/live.react-server.tsx @@ -4,11 +4,5 @@ // 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 { - type DefineSanityLiveOptions, - type DefinedSanityFetchType, - type DefinedSanityLiveProps, - defineLive, -} from './live/defineLive' - +export {defineLive} from './live/defineLive' export {resolvePerspectiveFromCookies} from '#live/resolvePerspectiveFromCookies' 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 +} From 14f595ec0e46b7deb4432744dd540336eb2b8f21 Mon Sep 17 00:00:00 2001 From: Cody Olsen Date: Fri, 19 Dec 2025 17:38:50 +0100 Subject: [PATCH 06/12] `sanityFetch` and `SanityLive` are replaced by `fetch` and `SanityLive` --- .changeset/some-towns-rush.md | 5 + apps/mvp/app/(website)/layout.tsx | 25 +- apps/mvp/app/(website)/live.ts | 2 +- .../(website)/no-resolve-perspective/page.tsx | 24 +- .../app/(website)/only-production/page.tsx | 16 +- apps/mvp/app/(website)/page.tsx | 28 +- .../next-sanity/src/experimental/live.tsx | 474 +++++------------- packages/next-sanity/src/live.next-js.tsx | 2 + .../next-sanity/src/live.react-server.tsx | 2 + packages/next-sanity/src/live.tsx | 22 +- packages/next-sanity/src/live/defineLive.tsx | 29 +- 11 files changed, 225 insertions(+), 404 deletions(-) create mode 100644 .changeset/some-towns-rush.md 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/apps/mvp/app/(website)/layout.tsx b/apps/mvp/app/(website)/layout.tsx index 1dbb6f075a8..0f8a9b4e639 100644 --- a/apps/mvp/app/(website)/layout.tsx +++ b/apps/mvp/app/(website)/layout.tsx @@ -1,18 +1,15 @@ 'use cache' import '../globals.css' -import { - // cookies, - draftMode, -} from 'next/headers' -// import {resolvePerspectiveFromCookies} from 'next-sanity/experimental/live' -// import {Suspense} from 'react' +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' async function toggleDraftMode() { @@ -27,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 ( @@ -72,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 38ff5e2d6dd..97a8f2207f5 100644 --- a/apps/mvp/app/(website)/live.ts +++ b/apps/mvp/app/(website)/live.ts @@ -4,7 +4,7 @@ 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..21179a5a747 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' -export default async function IndexPage() { - const isDraftMode = (await draftMode()).isEnabled - const {data} = await sanityFetch({ +async function getPosts(perspective: 'drafts' | 'published') { + 'use cache' + + cacheLife('sanity') + + const {data, tags} = await sanityFetch({ query: postsQuery.query, - perspective: isDraftMode ? 'drafts' : 'published', - stega: isDraftMode, + perspective, + stega: perspective !== 'published', }) + return {data, tags} +} + +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..1ce8e2cecf6 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' + + 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 002a71fda98..d9d1f40f3d4 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/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' + + 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/packages/next-sanity/src/experimental/live.tsx b/packages/next-sanity/src/experimental/live.tsx index 36cf3ae460e..eea03f32821 100644 --- a/packages/next-sanity/src/experimental/live.tsx +++ b/packages/next-sanity/src/experimental/live.tsx @@ -1,229 +1,25 @@ +import type {DefinedFetchType, DefinedLiveProps, LiveOptions, PerspectiveType} from '#live/types' + import {resolvePerspectiveFromCookies} from '#live/resolvePerspectiveFromCookies' -import {stegaEncodeSourceMap} from '@sanity/client/stega' -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 SanityLiveClientComponent from 'next-sanity/experimental/client-components/live' import {cacheTag, updateTag} 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 {DRAFT_SYNC_TAG_PREFIX, PUBLISHED_SYNC_TAG_PREFIX} from './constants' -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 `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[] -} - -/** - * @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 - /** - * 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): { +export function defineLive(config: LiveOptions): { + fetch: DefinedFetchType + Live: React.ComponentType /** - * 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 } { @@ -245,77 +41,103 @@ 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 (onChange) { + console.warn('`onChange` is not implemented yet') + } + 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)) @@ -324,86 +146,48 @@ 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 ( - - ) + 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', + ) + }, } +} // @TODO expose parseTags function that returns the correct array of tags // we already have s1: prefixes, but they could change @@ -431,7 +215,7 @@ async function expireTags(_tags: unknown): Promise { 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/live.next-js.tsx b/packages/next-sanity/src/live.next-js.tsx index adaa61c6c70..483ff315579 100644 --- a/packages/next-sanity/src/live.next-js.tsx +++ b/packages/next-sanity/src/live.next-js.tsx @@ -4,3 +4,5 @@ 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 index 1fc6b60949c..62d559a37f4 100644 --- a/packages/next-sanity/src/live.react-server.tsx +++ b/packages/next-sanity/src/live.react-server.tsx @@ -6,3 +6,5 @@ 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.tsx b/packages/next-sanity/src/live.tsx index dda147f75ac..ac3057f6c2a 100644 --- a/packages/next-sanity/src/live.tsx +++ b/packages/next-sanity/src/live.tsx @@ -4,7 +4,8 @@ // 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. -export {isCorsOriginError} from '#live/isCorsOriginError' +import type {ResolvePerspectiveFromCookies} from '#live/resolvePerspectiveFromCookies' +import type {DefinedFetchType, DefinedLiveProps} from '#live/types' import type { DefineSanityLiveOptions, @@ -12,23 +13,26 @@ import type { 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`) } -/** - * @public - */ -export type {DefineSanityLiveOptions, DefinedSanityFetchType, DefinedSanityLiveProps} - -import type {ResolvePerspectiveFromCookies} from '#live/resolvePerspectiveFromCookies' - /** * Resolves the perspective from the cookie that is set by `import { defineEnableDraftMode } from "next-sanity/draft-mode"` * @public @@ -36,3 +40,5 @@ import type {ResolvePerspectiveFromCookies} from '#live/resolvePerspectiveFromCo 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/defineLive.tsx b/packages/next-sanity/src/live/defineLive.tsx index 3d7f1e1be1f..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, @@ -28,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 @@ -75,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 @@ -143,24 +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 + // fetch: DefinedFetchType + // Live: React.ComponentType } { const { client: _client, @@ -252,8 +238,7 @@ export function defineLive(config: DefineSanityLiveOptions): { refreshOnMount, refreshOnFocus, refreshOnReconnect, - tag, - requestTag = tag, + requestTag, onError, onGoAway, intervalOnGoAway, From 2476f5e1381a34a3eea86bfd8fbbaeff57254f2f Mon Sep 17 00:00:00 2001 From: Cody Olsen Date: Fri, 19 Dec 2025 17:50:48 +0100 Subject: [PATCH 07/12] handle build error --- apps/mvp/app/(website)/PostsLayout.tsx | 2 +- .../(website)/no-resolve-perspective/page.tsx | 6 +- .../app/(website)/only-production/page.tsx | 3 +- apps/mvp/app/(website)/page.tsx | 2 +- apps/mvp/sanity.types.ts | 556 +++++++++--------- 5 files changed, 285 insertions(+), 284 deletions(-) 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)/no-resolve-perspective/page.tsx b/apps/mvp/app/(website)/no-resolve-perspective/page.tsx index 21179a5a747..63d7650eac5 100644 --- a/apps/mvp/app/(website)/no-resolve-perspective/page.tsx +++ b/apps/mvp/app/(website)/no-resolve-perspective/page.tsx @@ -8,16 +8,16 @@ import PostsLayout, {postsQuery} from '@/app/(website)/PostsLayout' import {fetch as sanityFetch} from '../live' async function getPosts(perspective: 'drafts' | 'published') { - 'use cache' + 'use cache: remote' cacheLife('sanity') - const {data, tags} = await sanityFetch({ + const {data} = await sanityFetch({ query: postsQuery.query, perspective, stega: perspective !== 'published', }) - return {data, tags} + return data } export default async function IndexPage() { diff --git a/apps/mvp/app/(website)/only-production/page.tsx b/apps/mvp/app/(website)/only-production/page.tsx index 1ce8e2cecf6..d5cb2bebc88 100644 --- a/apps/mvp/app/(website)/only-production/page.tsx +++ b/apps/mvp/app/(website)/only-production/page.tsx @@ -9,7 +9,7 @@ import PostsLayout, {postsQuery} from '@/app/(website)/PostsLayout' import {fetch as sanityFetch} from '../live' async function getPosts() { - 'use cache' + 'use cache: remote' cacheLife('sanity') @@ -21,6 +21,7 @@ async function getPosts() { 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 d9d1f40f3d4..2344164eb98 100644 --- a/apps/mvp/app/(website)/page.tsx +++ b/apps/mvp/app/(website)/page.tsx @@ -9,7 +9,7 @@ import PostsLayout, {postsQuery} from '@/app/(website)/PostsLayout' import {fetch as sanityFetch} from './live' async function getPosts(perspective: LivePerspective) { - 'use cache' + 'use cache: remote' cacheLife('sanity') diff --git a/apps/mvp/sanity.types.ts b/apps/mvp/sanity.types.ts index bd107feccc4..3ea6fee4a84 100644 --- a/apps/mvp/sanity.types.ts +++ b/apps/mvp/sanity.types.ts @@ -14,381 +14,381 @@ // Source: schema.json export type SanityImageAssetReference = { - _ref: string - _type: 'reference' - _weak?: boolean - [internalGroqTypeReferenceTo]?: 'sanity.imageAsset' -} + _ref: string; + _type: "reference"; + _weak?: boolean; + [internalGroqTypeReferenceTo]?: "sanity.imageAsset"; +}; export type BlockContent = Array< | { children?: Array<{ - marks?: Array - text?: string - _type: 'span' - _key: string - }> - style?: 'normal' | 'h1' | 'h2' | 'h3' | 'h4' | 'blockquote' - listItem?: 'bullet' + marks?: Array; + text?: string; + _type: "span"; + _key: string; + }>; + style?: "normal" | "h1" | "h2" | "h3" | "h4" | "blockquote"; + listItem?: "bullet"; markDefs?: Array<{ - href?: string - _type: 'link' - _key: string - }> - level?: number - _type: 'block' - _key: string + href?: string; + _type: "link"; + _key: string; + }>; + level?: number; + _type: "block"; + _key: string; } | { - asset?: SanityImageAssetReference - media?: unknown - hotspot?: SanityImageHotspot - crop?: SanityImageCrop - _type: 'image' - _key: string + asset?: SanityImageAssetReference; + media?: unknown; + hotspot?: SanityImageHotspot; + crop?: SanityImageCrop; + _type: "image"; + _key: string; } -> +>; export type Category = { - _id: string - _type: 'category' - _createdAt: string - _updatedAt: string - _rev: string - title?: string - description?: string -} + _id: string; + _type: "category"; + _createdAt: string; + _updatedAt: string; + _rev: string; + title?: string; + description?: string; +}; export type AuthorReference = { - _ref: string - _type: 'reference' - _weak?: boolean - [internalGroqTypeReferenceTo]?: 'author' -} + _ref: string; + _type: "reference"; + _weak?: boolean; + [internalGroqTypeReferenceTo]?: "author"; +}; export type CategoryReference = { - _ref: string - _type: 'reference' - _weak?: boolean - [internalGroqTypeReferenceTo]?: 'category' -} + _ref: string; + _type: "reference"; + _weak?: boolean; + [internalGroqTypeReferenceTo]?: "category"; +}; export type Post = { - _id: string - _type: 'post' - _createdAt: string - _updatedAt: string - _rev: string - title?: string - slug?: Slug - author?: AuthorReference + _id: string; + _type: "post"; + _createdAt: string; + _updatedAt: string; + _rev: string; + title?: string; + slug?: Slug; + author?: AuthorReference; mainImage?: { - asset?: SanityImageAssetReference - media?: unknown - hotspot?: SanityImageHotspot - crop?: SanityImageCrop - alt?: string - _type: 'image' - } + asset?: SanityImageAssetReference; + media?: unknown; + hotspot?: SanityImageHotspot; + crop?: SanityImageCrop; + alt?: string; + _type: "image"; + }; categories?: Array< { - _key: string + _key: string; } & CategoryReference - > - publishedAt?: string - body?: BlockContent -} + >; + publishedAt?: string; + body?: BlockContent; +}; export type SanityImageCrop = { - _type: 'sanity.imageCrop' - top?: number - bottom?: number - left?: number - right?: number -} + _type: "sanity.imageCrop"; + top?: number; + bottom?: number; + left?: number; + right?: number; +}; export type SanityImageHotspot = { - _type: 'sanity.imageHotspot' - x?: number - y?: number - height?: number - width?: number -} + _type: "sanity.imageHotspot"; + x?: number; + y?: number; + height?: number; + width?: number; +}; export type Author = { - _id: string - _type: 'author' - _createdAt: string - _updatedAt: string - _rev: string - name?: string - slug?: Slug + _id: string; + _type: "author"; + _createdAt: string; + _updatedAt: string; + _rev: string; + name?: string; + slug?: Slug; image?: { - asset?: SanityImageAssetReference - media?: unknown - hotspot?: SanityImageHotspot - crop?: SanityImageCrop - alt?: string - _type: 'image' - } + asset?: SanityImageAssetReference; + media?: unknown; + hotspot?: SanityImageHotspot; + crop?: SanityImageCrop; + alt?: string; + _type: "image"; + }; bio?: Array<{ children?: Array<{ - marks?: Array - text?: string - _type: 'span' - _key: string - }> - style?: 'normal' - listItem?: never + marks?: Array; + text?: string; + _type: "span"; + _key: string; + }>; + style?: "normal"; + listItem?: never; markDefs?: Array<{ - href?: string - _type: 'link' - _key: string - }> - level?: number - _type: 'block' - _key: string - }> -} + href?: string; + _type: "link"; + _key: string; + }>; + level?: number; + _type: "block"; + _key: string; + }>; +}; export type Slug = { - _type: 'slug' - current?: string - source?: string -} + _type: "slug"; + current?: string; + source?: string; +}; export type SanityAssistInstructionTask = { - _type: 'sanity.assist.instructionTask' - path?: string - instructionKey?: string - started?: string - updated?: string - info?: string -} + _type: "sanity.assist.instructionTask"; + path?: string; + instructionKey?: string; + started?: string; + updated?: string; + info?: string; +}; export type SanityAssistTaskStatus = { - _type: 'sanity.assist.task.status' + _type: "sanity.assist.task.status"; tasks?: Array< { - _key: string + _key: string; } & SanityAssistInstructionTask - > -} + >; +}; export type SanityAssistSchemaTypeAnnotations = { - _type: 'sanity.assist.schemaType.annotations' - title?: string + _type: "sanity.assist.schemaType.annotations"; + title?: string; fields?: Array< { - _key: string + _key: string; } & SanityAssistSchemaTypeField - > -} + >; +}; export type SanityAssistOutputType = { - _type: 'sanity.assist.output.type' - type?: string -} + _type: "sanity.assist.output.type"; + type?: string; +}; export type SanityAssistOutputField = { - _type: 'sanity.assist.output.field' - path?: string -} + _type: "sanity.assist.output.field"; + path?: string; +}; export type AssistInstructionContextReference = { - _ref: string - _type: 'reference' - _weak?: boolean - [internalGroqTypeReferenceTo]?: 'assist.instruction.context' -} + _ref: string; + _type: "reference"; + _weak?: boolean; + [internalGroqTypeReferenceTo]?: "assist.instruction.context"; +}; export type SanityAssistInstructionContext = { - _type: 'sanity.assist.instruction.context' - reference?: AssistInstructionContextReference -} + _type: "sanity.assist.instruction.context"; + reference?: AssistInstructionContextReference; +}; export type AssistInstructionContext = { - _id: string - _type: 'assist.instruction.context' - _createdAt: string - _updatedAt: string - _rev: string - title?: string + _id: string; + _type: "assist.instruction.context"; + _createdAt: string; + _updatedAt: string; + _rev: string; + title?: string; context?: Array<{ children?: Array<{ - marks?: Array - text?: string - _type: 'span' - _key: string - }> - style?: 'normal' - listItem?: never - markDefs?: null - level?: number - _type: 'block' - _key: string - }> -} + marks?: Array; + text?: string; + _type: "span"; + _key: string; + }>; + style?: "normal"; + listItem?: never; + markDefs?: null; + level?: number; + _type: "block"; + _key: string; + }>; +}; export type SanityAssistInstructionUserInput = { - _type: 'sanity.assist.instruction.userInput' - message?: string - description?: string -} + _type: "sanity.assist.instruction.userInput"; + message?: string; + description?: string; +}; export type SanityAssistInstructionPrompt = Array<{ children?: Array< | { - marks?: Array - text?: string - _type: 'span' - _key: string + marks?: Array; + text?: string; + _type: "span"; + _key: string; } | ({ - _key: string + _key: string; } & SanityAssistInstructionFieldRef) | ({ - _key: string + _key: string; } & SanityAssistInstructionContext) | ({ - _key: string + _key: string; } & SanityAssistInstructionUserInput) - > - style?: 'normal' - listItem?: never - markDefs?: null - level?: number - _type: 'block' - _key: string -}> + >; + style?: "normal"; + listItem?: never; + markDefs?: null; + level?: number; + _type: "block"; + _key: string; +}>; export type SanityAssistInstructionFieldRef = { - _type: 'sanity.assist.instruction.fieldRef' - path?: string -} + _type: "sanity.assist.instruction.fieldRef"; + path?: string; +}; export type SanityAssistInstruction = { - _type: 'sanity.assist.instruction' - prompt?: SanityAssistInstructionPrompt - icon?: string - title?: string - userId?: string - createdById?: string + _type: "sanity.assist.instruction"; + prompt?: SanityAssistInstructionPrompt; + icon?: string; + title?: string; + userId?: string; + createdById?: string; output?: Array< | ({ - _key: string + _key: string; } & SanityAssistOutputField) | ({ - _key: string + _key: string; } & SanityAssistOutputType) - > -} + >; +}; export type SanityAssistSchemaTypeField = { - _type: 'sanity.assist.schemaType.field' - path?: string + _type: "sanity.assist.schemaType.field"; + path?: string; instructions?: Array< { - _key: string + _key: string; } & SanityAssistInstruction - > -} + >; +}; export type SanityImagePaletteSwatch = { - _type: 'sanity.imagePaletteSwatch' - background?: string - foreground?: string - population?: number - title?: string -} + _type: "sanity.imagePaletteSwatch"; + background?: string; + foreground?: string; + population?: number; + title?: string; +}; export type SanityImagePalette = { - _type: 'sanity.imagePalette' - darkMuted?: SanityImagePaletteSwatch - lightVibrant?: SanityImagePaletteSwatch - darkVibrant?: SanityImagePaletteSwatch - vibrant?: SanityImagePaletteSwatch - dominant?: SanityImagePaletteSwatch - lightMuted?: SanityImagePaletteSwatch - muted?: SanityImagePaletteSwatch -} + _type: "sanity.imagePalette"; + darkMuted?: SanityImagePaletteSwatch; + lightVibrant?: SanityImagePaletteSwatch; + darkVibrant?: SanityImagePaletteSwatch; + vibrant?: SanityImagePaletteSwatch; + dominant?: SanityImagePaletteSwatch; + lightMuted?: SanityImagePaletteSwatch; + muted?: SanityImagePaletteSwatch; +}; export type SanityImageDimensions = { - _type: 'sanity.imageDimensions' - height?: number - width?: number - aspectRatio?: number -} + _type: "sanity.imageDimensions"; + height?: number; + width?: number; + aspectRatio?: number; +}; export type SanityImageMetadata = { - _type: 'sanity.imageMetadata' - location?: Geopoint - dimensions?: SanityImageDimensions - palette?: SanityImagePalette - lqip?: string - blurHash?: string - hasAlpha?: boolean - isOpaque?: boolean -} + _type: "sanity.imageMetadata"; + location?: Geopoint; + dimensions?: SanityImageDimensions; + palette?: SanityImagePalette; + lqip?: string; + blurHash?: string; + hasAlpha?: boolean; + isOpaque?: boolean; +}; export type SanityFileAsset = { - _id: string - _type: 'sanity.fileAsset' - _createdAt: string - _updatedAt: string - _rev: string - originalFilename?: string - label?: string - title?: string - description?: string - altText?: string - sha1hash?: string - extension?: string - mimeType?: string - size?: number - assetId?: string - uploadId?: string - path?: string - url?: string - source?: SanityAssetSourceData -} + _id: string; + _type: "sanity.fileAsset"; + _createdAt: string; + _updatedAt: string; + _rev: string; + originalFilename?: string; + label?: string; + title?: string; + description?: string; + altText?: string; + sha1hash?: string; + extension?: string; + mimeType?: string; + size?: number; + assetId?: string; + uploadId?: string; + path?: string; + url?: string; + source?: SanityAssetSourceData; +}; export type SanityAssetSourceData = { - _type: 'sanity.assetSourceData' - name?: string - id?: string - url?: string -} + _type: "sanity.assetSourceData"; + name?: string; + id?: string; + url?: string; +}; export type SanityImageAsset = { - _id: string - _type: 'sanity.imageAsset' - _createdAt: string - _updatedAt: string - _rev: string - originalFilename?: string - label?: string - title?: string - description?: string - altText?: string - sha1hash?: string - extension?: string - mimeType?: string - size?: number - assetId?: string - uploadId?: string - path?: string - url?: string - metadata?: SanityImageMetadata - source?: SanityAssetSourceData -} + _id: string; + _type: "sanity.imageAsset"; + _createdAt: string; + _updatedAt: string; + _rev: string; + originalFilename?: string; + label?: string; + title?: string; + description?: string; + altText?: string; + sha1hash?: string; + extension?: string; + mimeType?: string; + size?: number; + assetId?: string; + uploadId?: string; + path?: string; + url?: string; + metadata?: SanityImageMetadata; + source?: SanityAssetSourceData; +}; export type Geopoint = { - _type: 'geopoint' - lat?: number - lng?: number - alt?: number -} + _type: "geopoint"; + lat?: number; + lng?: number; + alt?: number; +}; export type AllSanitySchemaTypes = | SanityImageAssetReference @@ -421,6 +421,6 @@ export type AllSanitySchemaTypes = | SanityFileAsset | SanityAssetSourceData | SanityImageAsset - | Geopoint + | Geopoint; -export declare const internalGroqTypeReferenceTo: unique symbol +export declare const internalGroqTypeReferenceTo: unique symbol; From 2bd407451836c8b1775a81c9c900a5b822e1cbfd Mon Sep 17 00:00:00 2001 From: "squiggler[bot]" <128108030+squiggler[bot]@users.noreply.github.com> Date: Fri, 19 Dec 2025 17:56:34 +0100 Subject: [PATCH 08/12] Version Packages (cache-components) (#3122) Co-authored-by: squiggler[bot] <128108030+squiggler[bot]@users.noreply.github.com> --- .changeset/pre.json | 3 +++ packages/next-sanity/CHANGELOG.md | 12 ++++++++++++ packages/next-sanity/package.json | 2 +- 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/.changeset/pre.json b/.changeset/pre.json index a26ec3a7d0f..904e6f2ba95 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -10,9 +10,12 @@ "fair-rats-sell", "four-jobs-type", "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/packages/next-sanity/CHANGELOG.md b/packages/next-sanity/CHANGELOG.md index bebc15d5f5b..3d8470e9c0d 100644 --- a/packages/next-sanity/CHANGELOG.md +++ b/packages/next-sanity/CHANGELOG.md @@ -1,5 +1,17 @@ # next-sanity +## 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 diff --git a/packages/next-sanity/package.json b/packages/next-sanity/package.json index 54914bccf90..40fad2a0240 100644 --- a/packages/next-sanity/package.json +++ b/packages/next-sanity/package.json @@ -1,6 +1,6 @@ { "name": "next-sanity", - "version": "13.0.0-cache-components.6", + "version": "13.0.0-cache-components.7", "description": "Sanity.io toolkit for Next.js", "keywords": [ "live", From 1907db87edb115d505c1cd915bd259d34f630984 Mon Sep 17 00:00:00 2001 From: Cody Olsen Date: Fri, 19 Dec 2025 17:56:55 +0100 Subject: [PATCH 09/12] fix tests --- fixtures/fail/server-only-live/next.config.ts | 2 +- fixtures/pass/server-only-live/next.config.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 From 522d696f7675ab17b5a441f71cb46da43ef28d05 Mon Sep 17 00:00:00 2001 From: Cody Olsen Date: Fri, 19 Dec 2025 18:36:06 +0100 Subject: [PATCH 10/12] Fix missing server action --- .changeset/free-hoops-marry.md | 5 +++ .../experimental/client-components/live.tsx | 5 +-- .../next-sanity/src/experimental/live.tsx | 32 ++----------------- .../src/live/server-actions/index.ts | 30 ++++++++++++++++- 4 files changed, 40 insertions(+), 32 deletions(-) create mode 100644 .changeset/free-hoops-marry.md 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/packages/next-sanity/src/experimental/client-components/live.tsx b/packages/next-sanity/src/experimental/client-components/live.tsx index fdc16089934..12891922343 100644 --- a/packages/next-sanity/src/experimental/client-components/live.tsx +++ b/packages/next-sanity/src/experimental/client-components/live.tsx @@ -18,6 +18,7 @@ import {useEffect, useMemo, useRef, useState, useEffectEvent} from 'react' import type {SanityClientConfig} from '../types' import {PUBLISHED_SYNC_TAG_PREFIX, type DRAFT_SYNC_TAG_PREFIX} from '../constants' +import { expireTags } from 'next-sanity/live/server-actions' const PresentationComlink = dynamic(() => import('./PresentationComlink'), {ssr: false}) const RefreshOnMount = dynamic(() => import('../../live/client-components/live/RefreshOnMount'), { @@ -49,7 +50,7 @@ export interface SanityLiveProps { intervalOnGoAway?: number | false onGoAway?: (event: LiveEventGoAway, intervalOnGoAway: number | false) => void revalidateSyncTags: ( - tags: `${typeof PUBLISHED_SYNC_TAG_PREFIX | typeof DRAFT_SYNC_TAG_PREFIX}${SyncTag}`[], + tags: `${typeof PUBLISHED_SYNC_TAG_PREFIX | typeof DRAFT_SYNC_TAG_PREFIX}${string}`[], ) => Promise resolveDraftModePerspective: () => Promise } @@ -100,7 +101,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 eea03f32821..f9778a437cb 100644 --- a/packages/next-sanity/src/experimental/live.tsx +++ b/packages/next-sanity/src/experimental/live.tsx @@ -126,9 +126,7 @@ export function defineLive(config: LiveOptions): { intervalOnGoAway, } = props - if (onChange) { - console.warn('`onChange` is not implemented yet') - } + if (onChangeIncludingDrafts) { console.warn('`onChangeIncludingDrafts` is not implemented yet') } @@ -166,7 +164,7 @@ export function defineLive(config: LiveOptions): { onError={onError} onGoAway={onGoAway} intervalOnGoAway={intervalOnGoAway} - revalidateSyncTags={expireTags} + revalidateSyncTags={onChange} resolveDraftModePerspective={resolveDraftModePerspective} /> @@ -189,31 +187,7 @@ export function defineLive(config: LiveOptions): { } } -// @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) - } - // oxlint-disable-next-line no-console - console.log(` updated tags: ${tags.join(', ')}`) -} + async function resolveDraftModePerspective(): Promise { 'use server' diff --git a/packages/next-sanity/src/live/server-actions/index.ts b/packages/next-sanity/src/live/server-actions/index.ts index 0c6376e87b2..8668201fd29 100644 --- a/packages/next-sanity/src/live/server-actions/index.ts +++ b/packages/next-sanity/src/live/server-actions/index.ts @@ -4,8 +4,9 @@ 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 { PUBLISHED_SYNC_TAG_PREFIX } from '../../experimental/constants' export async function revalidateSyncTags(tags: SyncTag[]): Promise { revalidateTag('sanity:fetch-sync-tags', 'max') @@ -39,3 +40,30 @@ 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(', ')}`) +} \ No newline at end of file From eea2edf6da5eb65aa5e075980cf09f0c834633bf Mon Sep 17 00:00:00 2001 From: Cody Olsen Date: Fri, 19 Dec 2025 18:37:54 +0100 Subject: [PATCH 11/12] chore: fix build --- .../app/(website)/only-production/page.tsx | 1 - apps/mvp/sanity.types.ts | 556 +++++++++--------- .../experimental/client-components/live.tsx | 8 +- .../next-sanity/src/experimental/live.tsx | 5 +- .../src/live/server-actions/index.ts | 7 +- 5 files changed, 285 insertions(+), 292 deletions(-) diff --git a/apps/mvp/app/(website)/only-production/page.tsx b/apps/mvp/app/(website)/only-production/page.tsx index d5cb2bebc88..3cde5b5ab29 100644 --- a/apps/mvp/app/(website)/only-production/page.tsx +++ b/apps/mvp/app/(website)/only-production/page.tsx @@ -21,7 +21,6 @@ async function getPosts() { export default async function IndexPage() { const data = await getPosts() - return ( <> diff --git a/apps/mvp/sanity.types.ts b/apps/mvp/sanity.types.ts index 3ea6fee4a84..bd107feccc4 100644 --- a/apps/mvp/sanity.types.ts +++ b/apps/mvp/sanity.types.ts @@ -14,381 +14,381 @@ // Source: schema.json export type SanityImageAssetReference = { - _ref: string; - _type: "reference"; - _weak?: boolean; - [internalGroqTypeReferenceTo]?: "sanity.imageAsset"; -}; + _ref: string + _type: 'reference' + _weak?: boolean + [internalGroqTypeReferenceTo]?: 'sanity.imageAsset' +} export type BlockContent = Array< | { children?: Array<{ - marks?: Array; - text?: string; - _type: "span"; - _key: string; - }>; - style?: "normal" | "h1" | "h2" | "h3" | "h4" | "blockquote"; - listItem?: "bullet"; + marks?: Array + text?: string + _type: 'span' + _key: string + }> + style?: 'normal' | 'h1' | 'h2' | 'h3' | 'h4' | 'blockquote' + listItem?: 'bullet' markDefs?: Array<{ - href?: string; - _type: "link"; - _key: string; - }>; - level?: number; - _type: "block"; - _key: string; + href?: string + _type: 'link' + _key: string + }> + level?: number + _type: 'block' + _key: string } | { - asset?: SanityImageAssetReference; - media?: unknown; - hotspot?: SanityImageHotspot; - crop?: SanityImageCrop; - _type: "image"; - _key: string; + asset?: SanityImageAssetReference + media?: unknown + hotspot?: SanityImageHotspot + crop?: SanityImageCrop + _type: 'image' + _key: string } ->; +> export type Category = { - _id: string; - _type: "category"; - _createdAt: string; - _updatedAt: string; - _rev: string; - title?: string; - description?: string; -}; + _id: string + _type: 'category' + _createdAt: string + _updatedAt: string + _rev: string + title?: string + description?: string +} export type AuthorReference = { - _ref: string; - _type: "reference"; - _weak?: boolean; - [internalGroqTypeReferenceTo]?: "author"; -}; + _ref: string + _type: 'reference' + _weak?: boolean + [internalGroqTypeReferenceTo]?: 'author' +} export type CategoryReference = { - _ref: string; - _type: "reference"; - _weak?: boolean; - [internalGroqTypeReferenceTo]?: "category"; -}; + _ref: string + _type: 'reference' + _weak?: boolean + [internalGroqTypeReferenceTo]?: 'category' +} export type Post = { - _id: string; - _type: "post"; - _createdAt: string; - _updatedAt: string; - _rev: string; - title?: string; - slug?: Slug; - author?: AuthorReference; + _id: string + _type: 'post' + _createdAt: string + _updatedAt: string + _rev: string + title?: string + slug?: Slug + author?: AuthorReference mainImage?: { - asset?: SanityImageAssetReference; - media?: unknown; - hotspot?: SanityImageHotspot; - crop?: SanityImageCrop; - alt?: string; - _type: "image"; - }; + asset?: SanityImageAssetReference + media?: unknown + hotspot?: SanityImageHotspot + crop?: SanityImageCrop + alt?: string + _type: 'image' + } categories?: Array< { - _key: string; + _key: string } & CategoryReference - >; - publishedAt?: string; - body?: BlockContent; -}; + > + publishedAt?: string + body?: BlockContent +} export type SanityImageCrop = { - _type: "sanity.imageCrop"; - top?: number; - bottom?: number; - left?: number; - right?: number; -}; + _type: 'sanity.imageCrop' + top?: number + bottom?: number + left?: number + right?: number +} export type SanityImageHotspot = { - _type: "sanity.imageHotspot"; - x?: number; - y?: number; - height?: number; - width?: number; -}; + _type: 'sanity.imageHotspot' + x?: number + y?: number + height?: number + width?: number +} export type Author = { - _id: string; - _type: "author"; - _createdAt: string; - _updatedAt: string; - _rev: string; - name?: string; - slug?: Slug; + _id: string + _type: 'author' + _createdAt: string + _updatedAt: string + _rev: string + name?: string + slug?: Slug image?: { - asset?: SanityImageAssetReference; - media?: unknown; - hotspot?: SanityImageHotspot; - crop?: SanityImageCrop; - alt?: string; - _type: "image"; - }; + asset?: SanityImageAssetReference + media?: unknown + hotspot?: SanityImageHotspot + crop?: SanityImageCrop + alt?: string + _type: 'image' + } bio?: Array<{ children?: Array<{ - marks?: Array; - text?: string; - _type: "span"; - _key: string; - }>; - style?: "normal"; - listItem?: never; + marks?: Array + text?: string + _type: 'span' + _key: string + }> + style?: 'normal' + listItem?: never markDefs?: Array<{ - href?: string; - _type: "link"; - _key: string; - }>; - level?: number; - _type: "block"; - _key: string; - }>; -}; + href?: string + _type: 'link' + _key: string + }> + level?: number + _type: 'block' + _key: string + }> +} export type Slug = { - _type: "slug"; - current?: string; - source?: string; -}; + _type: 'slug' + current?: string + source?: string +} export type SanityAssistInstructionTask = { - _type: "sanity.assist.instructionTask"; - path?: string; - instructionKey?: string; - started?: string; - updated?: string; - info?: string; -}; + _type: 'sanity.assist.instructionTask' + path?: string + instructionKey?: string + started?: string + updated?: string + info?: string +} export type SanityAssistTaskStatus = { - _type: "sanity.assist.task.status"; + _type: 'sanity.assist.task.status' tasks?: Array< { - _key: string; + _key: string } & SanityAssistInstructionTask - >; -}; + > +} export type SanityAssistSchemaTypeAnnotations = { - _type: "sanity.assist.schemaType.annotations"; - title?: string; + _type: 'sanity.assist.schemaType.annotations' + title?: string fields?: Array< { - _key: string; + _key: string } & SanityAssistSchemaTypeField - >; -}; + > +} export type SanityAssistOutputType = { - _type: "sanity.assist.output.type"; - type?: string; -}; + _type: 'sanity.assist.output.type' + type?: string +} export type SanityAssistOutputField = { - _type: "sanity.assist.output.field"; - path?: string; -}; + _type: 'sanity.assist.output.field' + path?: string +} export type AssistInstructionContextReference = { - _ref: string; - _type: "reference"; - _weak?: boolean; - [internalGroqTypeReferenceTo]?: "assist.instruction.context"; -}; + _ref: string + _type: 'reference' + _weak?: boolean + [internalGroqTypeReferenceTo]?: 'assist.instruction.context' +} export type SanityAssistInstructionContext = { - _type: "sanity.assist.instruction.context"; - reference?: AssistInstructionContextReference; -}; + _type: 'sanity.assist.instruction.context' + reference?: AssistInstructionContextReference +} export type AssistInstructionContext = { - _id: string; - _type: "assist.instruction.context"; - _createdAt: string; - _updatedAt: string; - _rev: string; - title?: string; + _id: string + _type: 'assist.instruction.context' + _createdAt: string + _updatedAt: string + _rev: string + title?: string context?: Array<{ children?: Array<{ - marks?: Array; - text?: string; - _type: "span"; - _key: string; - }>; - style?: "normal"; - listItem?: never; - markDefs?: null; - level?: number; - _type: "block"; - _key: string; - }>; -}; + marks?: Array + text?: string + _type: 'span' + _key: string + }> + style?: 'normal' + listItem?: never + markDefs?: null + level?: number + _type: 'block' + _key: string + }> +} export type SanityAssistInstructionUserInput = { - _type: "sanity.assist.instruction.userInput"; - message?: string; - description?: string; -}; + _type: 'sanity.assist.instruction.userInput' + message?: string + description?: string +} export type SanityAssistInstructionPrompt = Array<{ children?: Array< | { - marks?: Array; - text?: string; - _type: "span"; - _key: string; + marks?: Array + text?: string + _type: 'span' + _key: string } | ({ - _key: string; + _key: string } & SanityAssistInstructionFieldRef) | ({ - _key: string; + _key: string } & SanityAssistInstructionContext) | ({ - _key: string; + _key: string } & SanityAssistInstructionUserInput) - >; - style?: "normal"; - listItem?: never; - markDefs?: null; - level?: number; - _type: "block"; - _key: string; -}>; + > + style?: 'normal' + listItem?: never + markDefs?: null + level?: number + _type: 'block' + _key: string +}> export type SanityAssistInstructionFieldRef = { - _type: "sanity.assist.instruction.fieldRef"; - path?: string; -}; + _type: 'sanity.assist.instruction.fieldRef' + path?: string +} export type SanityAssistInstruction = { - _type: "sanity.assist.instruction"; - prompt?: SanityAssistInstructionPrompt; - icon?: string; - title?: string; - userId?: string; - createdById?: string; + _type: 'sanity.assist.instruction' + prompt?: SanityAssistInstructionPrompt + icon?: string + title?: string + userId?: string + createdById?: string output?: Array< | ({ - _key: string; + _key: string } & SanityAssistOutputField) | ({ - _key: string; + _key: string } & SanityAssistOutputType) - >; -}; + > +} export type SanityAssistSchemaTypeField = { - _type: "sanity.assist.schemaType.field"; - path?: string; + _type: 'sanity.assist.schemaType.field' + path?: string instructions?: Array< { - _key: string; + _key: string } & SanityAssistInstruction - >; -}; + > +} export type SanityImagePaletteSwatch = { - _type: "sanity.imagePaletteSwatch"; - background?: string; - foreground?: string; - population?: number; - title?: string; -}; + _type: 'sanity.imagePaletteSwatch' + background?: string + foreground?: string + population?: number + title?: string +} export type SanityImagePalette = { - _type: "sanity.imagePalette"; - darkMuted?: SanityImagePaletteSwatch; - lightVibrant?: SanityImagePaletteSwatch; - darkVibrant?: SanityImagePaletteSwatch; - vibrant?: SanityImagePaletteSwatch; - dominant?: SanityImagePaletteSwatch; - lightMuted?: SanityImagePaletteSwatch; - muted?: SanityImagePaletteSwatch; -}; + _type: 'sanity.imagePalette' + darkMuted?: SanityImagePaletteSwatch + lightVibrant?: SanityImagePaletteSwatch + darkVibrant?: SanityImagePaletteSwatch + vibrant?: SanityImagePaletteSwatch + dominant?: SanityImagePaletteSwatch + lightMuted?: SanityImagePaletteSwatch + muted?: SanityImagePaletteSwatch +} export type SanityImageDimensions = { - _type: "sanity.imageDimensions"; - height?: number; - width?: number; - aspectRatio?: number; -}; + _type: 'sanity.imageDimensions' + height?: number + width?: number + aspectRatio?: number +} export type SanityImageMetadata = { - _type: "sanity.imageMetadata"; - location?: Geopoint; - dimensions?: SanityImageDimensions; - palette?: SanityImagePalette; - lqip?: string; - blurHash?: string; - hasAlpha?: boolean; - isOpaque?: boolean; -}; + _type: 'sanity.imageMetadata' + location?: Geopoint + dimensions?: SanityImageDimensions + palette?: SanityImagePalette + lqip?: string + blurHash?: string + hasAlpha?: boolean + isOpaque?: boolean +} export type SanityFileAsset = { - _id: string; - _type: "sanity.fileAsset"; - _createdAt: string; - _updatedAt: string; - _rev: string; - originalFilename?: string; - label?: string; - title?: string; - description?: string; - altText?: string; - sha1hash?: string; - extension?: string; - mimeType?: string; - size?: number; - assetId?: string; - uploadId?: string; - path?: string; - url?: string; - source?: SanityAssetSourceData; -}; + _id: string + _type: 'sanity.fileAsset' + _createdAt: string + _updatedAt: string + _rev: string + originalFilename?: string + label?: string + title?: string + description?: string + altText?: string + sha1hash?: string + extension?: string + mimeType?: string + size?: number + assetId?: string + uploadId?: string + path?: string + url?: string + source?: SanityAssetSourceData +} export type SanityAssetSourceData = { - _type: "sanity.assetSourceData"; - name?: string; - id?: string; - url?: string; -}; + _type: 'sanity.assetSourceData' + name?: string + id?: string + url?: string +} export type SanityImageAsset = { - _id: string; - _type: "sanity.imageAsset"; - _createdAt: string; - _updatedAt: string; - _rev: string; - originalFilename?: string; - label?: string; - title?: string; - description?: string; - altText?: string; - sha1hash?: string; - extension?: string; - mimeType?: string; - size?: number; - assetId?: string; - uploadId?: string; - path?: string; - url?: string; - metadata?: SanityImageMetadata; - source?: SanityAssetSourceData; -}; + _id: string + _type: 'sanity.imageAsset' + _createdAt: string + _updatedAt: string + _rev: string + originalFilename?: string + label?: string + title?: string + description?: string + altText?: string + sha1hash?: string + extension?: string + mimeType?: string + size?: number + assetId?: string + uploadId?: string + path?: string + url?: string + metadata?: SanityImageMetadata + source?: SanityAssetSourceData +} export type Geopoint = { - _type: "geopoint"; - lat?: number; - lng?: number; - alt?: number; -}; + _type: 'geopoint' + lat?: number + lng?: number + alt?: number +} export type AllSanitySchemaTypes = | SanityImageAssetReference @@ -421,6 +421,6 @@ export type AllSanitySchemaTypes = | SanityFileAsset | SanityAssetSourceData | SanityImageAsset - | Geopoint; + | Geopoint -export declare const internalGroqTypeReferenceTo: unique symbol; +export declare const internalGroqTypeReferenceTo: unique symbol diff --git a/packages/next-sanity/src/experimental/client-components/live.tsx b/packages/next-sanity/src/experimental/client-components/live.tsx index 12891922343..b4f50d30787 100644 --- a/packages/next-sanity/src/experimental/client-components/live.tsx +++ b/packages/next-sanity/src/experimental/client-components/live.tsx @@ -11,14 +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 {PUBLISHED_SYNC_TAG_PREFIX, type DRAFT_SYNC_TAG_PREFIX} from '../constants' -import { expireTags } from 'next-sanity/live/server-actions' +import {PUBLISHED_SYNC_TAG_PREFIX} from '../constants' const PresentationComlink = dynamic(() => import('./PresentationComlink'), {ssr: false}) const RefreshOnMount = dynamic(() => import('../../live/client-components/live/RefreshOnMount'), { @@ -49,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}${string}`[], - ) => Promise + revalidateSyncTags?: (tags: string[]) => Promise resolveDraftModePerspective: () => Promise } diff --git a/packages/next-sanity/src/experimental/live.tsx b/packages/next-sanity/src/experimental/live.tsx index f9778a437cb..3d6d8e9dbe2 100644 --- a/packages/next-sanity/src/experimental/live.tsx +++ b/packages/next-sanity/src/experimental/live.tsx @@ -2,7 +2,7 @@ import type {DefinedFetchType, DefinedLiveProps, LiveOptions, PerspectiveType} f import {resolvePerspectiveFromCookies} from '#live/resolvePerspectiveFromCookies' import SanityLiveClientComponent from 'next-sanity/experimental/client-components/live' -import {cacheTag, updateTag} from 'next/cache' +import {cacheTag} from 'next/cache' import {draftMode, cookies} from 'next/headers' import {Suspense} from 'react' import {preconnect} from 'react-dom' @@ -126,7 +126,6 @@ export function defineLive(config: LiveOptions): { intervalOnGoAway, } = props - if (onChangeIncludingDrafts) { console.warn('`onChangeIncludingDrafts` is not implemented yet') } @@ -187,8 +186,6 @@ export function defineLive(config: LiveOptions): { } } - - async function resolveDraftModePerspective(): Promise { 'use server' if ((await draftMode()).isEnabled) { diff --git a/packages/next-sanity/src/live/server-actions/index.ts b/packages/next-sanity/src/live/server-actions/index.ts index 8668201fd29..2e069686a64 100644 --- a/packages/next-sanity/src/live/server-actions/index.ts +++ b/packages/next-sanity/src/live/server-actions/index.ts @@ -6,7 +6,8 @@ import {sanitizePerspective} from '#live/sanitizePerspective' import {perspectiveCookieName} from '@sanity/preview-url-secret/constants' import {revalidateTag, updateTag} from 'next/cache' import {cookies, draftMode} from 'next/headers' -import { PUBLISHED_SYNC_TAG_PREFIX } from '../../experimental/constants' + +import {PUBLISHED_SYNC_TAG_PREFIX} from '../../experimental/constants' export async function revalidateSyncTags(tags: SyncTag[]): Promise { revalidateTag('sanity:fetch-sync-tags', 'max') @@ -41,12 +42,10 @@ 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}`) @@ -66,4 +65,4 @@ export async function expireTags(_tags: unknown): Promise { } // oxlint-disable-next-line no-console console.log(` updated tags: ${tags.join(', ')}`) -} \ No newline at end of file +} From 0b2314c2ae5241689e4dd9f18061ec22f346e8b0 Mon Sep 17 00:00:00 2001 From: "squiggler[bot]" <128108030+squiggler[bot]@users.noreply.github.com> Date: Fri, 19 Dec 2025 18:38:37 +0100 Subject: [PATCH 12/12] Version Packages (cache-components) (#3123) Co-authored-by: squiggler[bot] <128108030+squiggler[bot]@users.noreply.github.com> --- .changeset/pre.json | 1 + packages/next-sanity/CHANGELOG.md | 6 ++++++ packages/next-sanity/package.json | 2 +- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.changeset/pre.json b/.changeset/pre.json index 904e6f2ba95..1faa183a046 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -9,6 +9,7 @@ "bright-jokes-stay", "fair-rats-sell", "four-jobs-type", + "free-hoops-marry", "heavy-doors-ask", "moody-news-stay", "some-towns-rush", diff --git a/packages/next-sanity/CHANGELOG.md b/packages/next-sanity/CHANGELOG.md index 3d8470e9c0d..d902e9a86b0 100644 --- a/packages/next-sanity/CHANGELOG.md +++ b/packages/next-sanity/CHANGELOG.md @@ -1,5 +1,11 @@ # 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 diff --git a/packages/next-sanity/package.json b/packages/next-sanity/package.json index 40fad2a0240..da7b92aa95b 100644 --- a/packages/next-sanity/package.json +++ b/packages/next-sanity/package.json @@ -1,6 +1,6 @@ { "name": "next-sanity", - "version": "13.0.0-cache-components.7", + "version": "13.0.0-cache-components.8", "description": "Sanity.io toolkit for Next.js", "keywords": [ "live",