next-sanity 11.5.7 → 11.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,7 +8,7 @@ import { ClientPerspective, ClientReturn, ContentSourceMap, LiveEventGoAway, Que
8
8
  /**
9
9
  * @alpha CAUTION: This API does not follow semver and could have breaking changes in future minor releases.
10
10
  */
11
- declare function resolvePerspectiveFromCookie({
11
+ declare function resolvePerspectiveFromCookies({
12
12
  cookies: jar
13
13
  }: {
14
14
  cookies: Awaited<ReturnType<typeof cookies>>;
@@ -16,14 +16,16 @@ declare function resolvePerspectiveFromCookie({
16
16
  /**
17
17
  * @alpha CAUTION: This API does not follow semver and could have breaking changes in future minor releases.
18
18
  */
19
- type DefinedSanityFetchType = <const QueryString extends string>(options: {
20
- query: QueryString;
19
+ interface SanityFetchOptions<QueryString$1 extends string> {
20
+ query: QueryString$1;
21
21
  params?: QueryParams | Promise<QueryParams>;
22
+ /**
23
+ * @defaultValue 'published'
24
+ */
22
25
  perspective?: Exclude<ClientPerspective, "raw">;
23
26
  /**
24
- * Enables stega encoding of the data, this is typically only used in draft mode.
25
- * If `defineLive({..., stega: true})` is provided, then it defaults to `true` in Draft Mode.
26
- * If `defineLive({..., stega: false})` then it defaults to `false`.
27
+ * 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.
28
+ * @defaultValue `false`
27
29
  */
28
30
  stega?: boolean;
29
31
  /**
@@ -33,10 +35,14 @@ type DefinedSanityFetchType = <const QueryString extends string>(options: {
33
35
  */
34
36
  requestTag?: string;
35
37
  /**
36
- * Custom cache tags that can be used with next's `revalidateTag` function for custom webhook on-demand revalidation.
38
+ * Custom cache tags that can be used with next's `revalidateTag` and `updateTag` functions for custom webhook on-demand revalidation.
37
39
  */
38
40
  tags?: string[];
39
- }) => Promise<{
41
+ }
42
+ /**
43
+ * @alpha CAUTION: This API does not follow semver and could have breaking changes in future minor releases.
44
+ */
45
+ type DefinedSanityFetchType = <const QueryString extends string>(options: SanityFetchOptions<QueryString>) => Promise<{
40
46
  data: ClientReturn<QueryString, unknown>;
41
47
  /**
42
48
  * 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.
@@ -44,10 +50,6 @@ type DefinedSanityFetchType = <const QueryString extends string>(options: {
44
50
  */
45
51
  sourceMap: ContentSourceMap | null;
46
52
  /**
47
- * The perspective used to fetch the data, useful for debugging.
48
- */
49
- perspective: Exclude<ClientPerspective, "raw">;
50
- /**
51
53
  * The cache tags used with `next/cache`, useful for debugging.
52
54
  */
53
55
  tags: string[];
@@ -125,11 +127,6 @@ interface DefineSanityLiveOptions {
125
127
  * It is used to setup a `Live Draft Content` EventSource connection, and enables live previewing drafts stand-alone, outside of Presentation Tool.
126
128
  */
127
129
  browserToken?: string | false;
128
- /**
129
- * Optional. Include stega encoding when draft mode is enabled.
130
- * @defaultValue `true` if the client configuration has the `stega.studioUrl` property set, otherwise `false`
131
- */
132
- stega?: boolean;
133
130
  }
134
131
  /**
135
132
  * @alpha CAUTION: This API does not follow semver and could have breaking changes in future minor releases.
@@ -145,5 +142,5 @@ declare function defineLive(config: DefineSanityLiveOptions): {
145
142
  SanityLive: React.ComponentType<DefinedSanityLiveProps>;
146
143
  };
147
144
  //#endregion
148
- export { DefineSanityLiveOptions, DefinedSanityFetchType, DefinedSanityLiveProps, defineLive, resolvePerspectiveFromCookie };
145
+ export { DefineSanityLiveOptions, DefinedSanityFetchType, DefinedSanityLiveProps, SanityFetchOptions, defineLive, resolvePerspectiveFromCookies };
149
146
  //# sourceMappingURL=live.d.ts.map
@@ -8,29 +8,31 @@ import { jsx } from "react/jsx-runtime";
8
8
  import "server-only";
9
9
  import { createClient } from "next-sanity";
10
10
  import SanityLiveClientComponent from "next-sanity/experimental/client-components/live";
11
- import { cacheTag, updateTag } from "next/cache";
12
- async function resolvePerspectiveFromCookie({ cookies: jar }) {
11
+ import { cacheLife, cacheTag, updateTag } from "next/cache";
12
+ async function resolvePerspectiveFromCookies({ cookies: jar }) {
13
13
  return jar.has(perspectiveCookieName) ? sanitizePerspective(jar.get(perspectiveCookieName)?.value, "drafts") : "drafts";
14
14
  }
15
15
  async function sanityCachedFetch(config, { query, params = {}, perspective, stega, requestTag, draftToken, customCacheTags = [] }) {
16
- "use cache";
16
+ "use cache: remote";
17
17
  const client = createClient({
18
18
  ...config,
19
19
  useCdn: true
20
20
  });
21
21
  const useCdn = perspective === "published";
22
- const { result, resultSourceMap, syncTags } = await client.fetch(query, params, {
22
+ const { result, resultSourceMap, syncTags } = await client.fetch(query, await params, {
23
23
  filterResponse: false,
24
24
  returnQuery: false,
25
25
  perspective,
26
26
  useCdn,
27
27
  resultSourceMap: stega ? "withKeyArraySelector" : void 0,
28
+ stega: false,
28
29
  cacheMode: useCdn ? "noStale" : void 0,
29
30
  tag: requestTag,
30
31
  token: perspective === "published" ? config.token : draftToken || config.token
31
32
  });
32
33
  const tags = [...customCacheTags, ...(syncTags || []).map((tag) => `${perspective === "published" ? PUBLISHED_SYNC_TAG_PREFIX : DRAFT_SYNC_TAG_PREFIX}${tag}`)];
33
34
  cacheTag(...tags);
35
+ cacheLife({ revalidate: 3600 * 24 * 90 });
34
36
  return {
35
37
  data: result,
36
38
  sourceMap: resultSourceMap || null,
@@ -46,15 +48,10 @@ function defineLive(config) {
46
48
  allowReconfigure: false,
47
49
  useCdn: false
48
50
  });
49
- const { token: originalToken, stega: stegaConfig } = client.config();
50
- const studioUrlDefined = typeof client.config().stega.studioUrl !== "undefined";
51
- const { stega: stegaEnabled = typeof client.config().stega.studioUrl !== "undefined" } = config;
51
+ const { token: originalToken, apiHost, apiVersion, useProjectHostname, dataset, projectId, requestTagPrefix, stega: stegaConfig } = client.config();
52
52
  return {
53
- sanityFetch: async function sanityFetch({ query, params = {}, stega: _stega, tags: customCacheTags = [], perspective: _perspective, requestTag = "next-loader.fetch" }) {
54
- const stega = _stega ?? (stegaEnabled && studioUrlDefined && (await draftMode()).isEnabled);
55
- const perspective = _perspective ?? ((await draftMode()).isEnabled ? "drafts" : "published");
56
- const { apiHost, apiVersion, useProjectHostname, dataset, projectId, requestTagPrefix } = client.config();
57
- const { data: _data, sourceMap, tags } = await sanityCachedFetch({
53
+ sanityFetch: function sanityFetch({ query, params = {}, stega = false, perspective = "published", tags: customCacheTags = [], requestTag = "next-loader.fetch" }) {
54
+ return sanityCachedFetch({
58
55
  apiHost,
59
56
  apiVersion,
60
57
  useProjectHostname,
@@ -64,36 +61,34 @@ function defineLive(config) {
64
61
  token: originalToken
65
62
  }, {
66
63
  query,
67
- params: await params,
64
+ params,
68
65
  perspective,
69
66
  stega,
70
67
  requestTag,
71
68
  draftToken: serverToken,
72
69
  customCacheTags
73
- });
74
- return {
75
- data: stega && sourceMap ? stegaEncodeSourceMap(_data, sourceMap, {
70
+ }).then(({ data, sourceMap, tags }) => ({
71
+ data: stega && sourceMap ? stegaEncodeSourceMap(data, sourceMap, {
76
72
  ...stegaConfig,
77
73
  enabled: true
78
- }) : _data,
74
+ }) : data,
79
75
  sourceMap,
80
- tags,
81
- perspective
82
- };
76
+ tags
77
+ }));
83
78
  },
84
79
  SanityLive: function SanityLive(props) {
85
80
  const { refreshOnMount = false, refreshOnFocus = false, refreshOnReconnect = false, requestTag, onError, onGoAway, intervalOnGoAway, revalidateSyncTags = expireTags } = props;
86
- const { projectId, dataset, apiHost, apiVersion, useProjectHostname, requestTagPrefix } = client.config();
81
+ const { projectId: projectId$1, dataset: dataset$1, apiHost: apiHost$1, apiVersion: apiVersion$1, useProjectHostname: useProjectHostname$1, requestTagPrefix: requestTagPrefix$1 } = client.config();
87
82
  const { origin } = new URL(client.getUrl("", false));
88
83
  preconnect(origin);
89
84
  return /* @__PURE__ */ jsx(SanityLiveServerComponent, {
90
85
  config: {
91
- projectId,
92
- dataset,
93
- apiHost,
94
- apiVersion,
95
- useProjectHostname,
96
- requestTagPrefix
86
+ projectId: projectId$1,
87
+ dataset: dataset$1,
88
+ apiHost: apiHost$1,
89
+ apiVersion: apiVersion$1,
90
+ useProjectHostname: useProjectHostname$1,
91
+ requestTagPrefix: requestTagPrefix$1
97
92
  },
98
93
  requestTag,
99
94
  browserToken,
@@ -110,7 +105,6 @@ function defineLive(config) {
110
105
  };
111
106
  }
112
107
  const SanityLiveServerComponent = async function SanityLiveServerComponent$1(props) {
113
- "use cache";
114
108
  const { config, requestTag, intervalOnGoAway, onError, onGoAway, refreshOnFocus, refreshOnMount, refreshOnReconnect, revalidateSyncTags, browserToken, resolveDraftModePerspective: resolveDraftModePerspective$1 } = props;
115
109
  const { isEnabled: isDraftModeEnabled } = await draftMode();
116
110
  return /* @__PURE__ */ jsx(SanityLiveClientComponent, {
@@ -146,9 +140,9 @@ async function expireTags(_tags) {
146
140
  }
147
141
  async function resolveDraftModePerspective() {
148
142
  "use server";
149
- if ((await draftMode()).isEnabled) return resolvePerspectiveFromCookie({ cookies: await cookies() });
143
+ if ((await draftMode()).isEnabled) return resolvePerspectiveFromCookies({ cookies: await cookies() });
150
144
  return "published";
151
145
  }
152
- export { defineLive, resolvePerspectiveFromCookie };
146
+ export { defineLive, resolvePerspectiveFromCookies };
153
147
 
154
148
  //# sourceMappingURL=live.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"live.js","names":["SanityLiveServerComponent: React.ComponentType<SanityLiveServerComponentProps>","SanityLiveServerComponent","resolveDraftModePerspective"],"sources":["../../src/experimental/live.tsx"],"sourcesContent":["// oxlint-disable-next-line no-unassigned-import\nimport 'server-only'\nimport {\n createClient,\n type ClientPerspective,\n type ClientReturn,\n type ContentSourceMap,\n type LiveEventGoAway,\n type QueryParams,\n type SanityClient,\n type SyncTag,\n} from 'next-sanity'\nimport {stegaEncodeSourceMap} from '@sanity/client/stega'\nimport SanityLiveClientComponent, {\n type SanityLiveProps,\n} from 'next-sanity/experimental/client-components/live'\nimport {cacheTag, updateTag} from 'next/cache'\nimport {draftMode, cookies} from 'next/headers'\nimport {preconnect} from 'react-dom'\nimport {perspectiveCookieName} from '@sanity/preview-url-secret/constants'\nimport {sanitizePerspective} from '../live/utils'\nimport type {SanityClientConfig} from './types'\nimport {DRAFT_SYNC_TAG_PREFIX, PUBLISHED_SYNC_TAG_PREFIX} from './constants'\n\n/**\n * @alpha CAUTION: This API does not follow semver and could have breaking changes in future minor releases.\n */\nexport async function resolvePerspectiveFromCookie({\n cookies: jar,\n}: {\n cookies: Awaited<ReturnType<typeof cookies>>\n}): Promise<Exclude<ClientPerspective, 'raw'>> {\n return jar.has(perspectiveCookieName)\n ? sanitizePerspective(jar.get(perspectiveCookieName)?.value, 'drafts')\n : 'drafts'\n}\n\nasync function sanityCachedFetch<const QueryString extends string>(\n config: SanityClientConfig,\n {\n query,\n params = {},\n perspective,\n stega,\n requestTag,\n draftToken,\n customCacheTags = [],\n }: {\n query: QueryString\n params?: QueryParams\n perspective: Exclude<ClientPerspective, 'raw'>\n stega: boolean\n requestTag: string\n draftToken?: string | false | undefined\n customCacheTags?: string[]\n },\n): Promise<{\n data: ClientReturn<QueryString, unknown>\n sourceMap: ContentSourceMap | null\n tags: string[]\n}> {\n 'use cache'\n\n const client = createClient({...config, useCdn: true})\n const useCdn = perspective === 'published'\n /**\n * The default cache profile isn't ideal for live content, as it has unnecessary time based background validation, as well as a too lazy client stale value\n * https://github.com/vercel/next.js/blob/8dd358002baf4244c0b2e38b5bda496daf60dacb/packages/next/cache.d.ts#L14-L26\n */\n // cacheLife({\n // stale: Infinity,\n // revalidate: Infinity,\n // expire: Infinity,\n // })\n\n const {result, resultSourceMap, syncTags} = await client.fetch(query, params, {\n filterResponse: false,\n returnQuery: false,\n perspective,\n useCdn,\n resultSourceMap: stega ? 'withKeyArraySelector' : undefined, // @TODO allow passing csm for non-stega use\n cacheMode: useCdn ? 'noStale' : undefined,\n tag: requestTag,\n token: perspective === 'published' ? config.token : draftToken || config.token, // @TODO can pass undefined instead of config.token here?\n })\n const tags = [\n ...customCacheTags,\n ...(syncTags || []).map(\n (tag) =>\n `${perspective === 'published' ? PUBLISHED_SYNC_TAG_PREFIX : DRAFT_SYNC_TAG_PREFIX}${tag}`,\n ),\n ]\n /**\n * The tags used here, are expired later on in the `expireTags` Server Action with the `expireTag` function from `next/cache`\n */\n cacheTag(...tags)\n\n return {data: result, sourceMap: resultSourceMap || null, tags}\n}\n\n/**\n * @alpha CAUTION: This API does not follow semver and could have breaking changes in future minor releases.\n */\nexport type DefinedSanityFetchType = <const QueryString extends string>(options: {\n query: QueryString\n params?: QueryParams | Promise<QueryParams>\n perspective?: Exclude<ClientPerspective, 'raw'>\n /**\n * Enables stega encoding of the data, this is typically only used in draft mode.\n * If `defineLive({..., stega: true})` is provided, then it defaults to `true` in Draft Mode.\n * If `defineLive({..., stega: false})` then it defaults to `false`.\n */\n stega?: boolean\n /**\n * This request tag is used to identify the request when viewing request logs from your Sanity Content Lake.\n * @see https://www.sanity.io/docs/reference-api-request-tags\n * @defaultValue 'next-loader.fetch'\n */\n requestTag?: string\n /**\n * Custom cache tags that can be used with next's `revalidateTag` function for custom webhook on-demand revalidation.\n */\n tags?: string[]\n}) => Promise<{\n data: ClientReturn<QueryString, unknown>\n /**\n * 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.\n * 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`\n */\n sourceMap: ContentSourceMap | null\n /**\n * The perspective used to fetch the data, useful for debugging.\n */\n perspective: Exclude<ClientPerspective, 'raw'>\n /**\n * The cache tags used with `next/cache`, useful for debugging.\n */\n tags: string[]\n}>\n\n/**\n * @alpha CAUTION: This API does not follow semver and could have breaking changes in future minor releases.\n */\nexport interface DefinedSanityLiveProps {\n /**\n * Automatic refresh of RSC when the component <SanityLive /> is mounted.\n * @defaultValue `false`\n */\n refreshOnMount?: boolean\n /**\n * Automatically refresh when window gets focused\n * @defaultValue `false`\n */\n refreshOnFocus?: boolean\n /**\n * Automatically refresh when the browser regains a network connection (via navigator.onLine)\n * @defaultValue `false`\n */\n refreshOnReconnect?: boolean\n /**\n * Automatically refresh on an interval when the Live Event API emits a `goaway` event, which indicates that the connection is rejected or closed.\n * This typically happens if the connection limit is reached, or if the connection is idle for too long.\n * To disable this long polling fallback behavior set `intervalOnGoAway` to `false` or `0`.\n * You can also use `onGoAway` to handle the `goaway` event in your own way, and read the reason why the event was emitted.\n * @defaultValue `30_000` 30 seconds interval\n */\n intervalOnGoAway?: number | false\n\n /**\n * This request tag is used to identify the request when viewing request logs from your Sanity Content Lake.\n * @see https://www.sanity.io/docs/reference-api-request-tags\n * @defaultValue 'next-loader.live'\n */\n requestTag?: string\n\n /**\n * Handle errors from the Live Events subscription.\n * By default it's reported using `console.error`, you can override this prop to handle it in your own way.\n */\n onError?: (error: unknown) => void\n\n /**\n * Handle the `goaway` event if the connection is rejected/closed.\n * `event.reason` will be a string of why the event was emitted, for example `'connection limit reached'`.\n * When this happens the `<SanityLive />` will fallback to long polling with a default interval of 30 seconds, providing your own `onGoAway` handler does not change this behavior.\n * If you want to disable long polling set `intervalOnGoAway` to `false` or `0`.\n */\n onGoAway?: (event: LiveEventGoAway, intervalOnGoAway: number | false) => void\n\n /**\n * Override how cache tags are invalidated, you need to pass a server action here.\n * You can also pass a `use client` function here, and have `router.refresh()` be called if the promise resolves to `'refresh'`.\n */\n // @TODO remove, replace with onLiveEvent\n revalidateSyncTags?: (\n tags: `${typeof PUBLISHED_SYNC_TAG_PREFIX | typeof DRAFT_SYNC_TAG_PREFIX}${SyncTag}`[],\n ) => Promise<void | 'refresh'>\n\n // @TODO add\n // decide how to handle a live event coming in\n // onLiveEvent?: (event: LiveEvent, mode: 'production' | 'preview) => void\n\n /**\n * Control how the draft mode perspective is resolved, by default it resolves from the `sanity-preview-perspective` cookie.\n */\n resolveDraftModePerspective?: () => Promise<ClientPerspective>\n}\n\n/**\n * @alpha CAUTION: This API does not follow semver and could have breaking changes in future minor releases.\n */\nexport interface DefineSanityLiveOptions {\n /**\n * Required for `sanityFetch` and `SanityLive` to work\n */\n client: SanityClient\n /**\n * Optional. If provided then the token needs to have permissions to query documents with `drafts.` prefixes in order for `perspective: 'drafts'` to work.\n * This token is not shared with the browser.\n */\n serverToken?: string | false\n /**\n * Optional. This token is shared with the browser, and should only have access to query published documents.\n * It is used to setup a `Live Draft Content` EventSource connection, and enables live previewing drafts stand-alone, outside of Presentation Tool.\n */\n browserToken?: string | false\n /**\n * Optional. Include stega encoding when draft mode is enabled.\n * @defaultValue `true` if the client configuration has the `stega.studioUrl` property set, otherwise `false`\n */\n stega?: boolean\n}\n\n/**\n * @alpha CAUTION: This API does not follow semver and could have breaking changes in future minor releases.\n */\nexport function defineLive(config: DefineSanityLiveOptions): {\n /**\n * Use this function to fetch data from Sanity in your React Server Components.\n */\n sanityFetch: DefinedSanityFetchType\n /**\n * Render this in your root layout.tsx to make your page revalidate on new content live, automatically.\n */\n SanityLive: React.ComponentType<DefinedSanityLiveProps>\n} {\n const {client: _client, serverToken, browserToken} = config\n\n if (!_client) {\n throw new Error('`client` is required for `defineLive` to function')\n }\n\n if (process.env.NODE_ENV !== 'production' && !serverToken && serverToken !== false) {\n // eslint-disable-next-line no-console\n console.warn(\n 'No `serverToken` provided to `defineLive`. This means that only published content will be fetched and respond to live events. You can silence this warning by setting `serverToken: false`.',\n )\n }\n\n if (process.env.NODE_ENV !== 'production' && !browserToken && browserToken !== false) {\n // eslint-disable-next-line no-console\n console.warn(\n 'No `browserToken` provided to `defineLive`. This means that live previewing drafts will only work when using the Presentation Tool in your Sanity Studio. To support live previewing drafts stand-alone, provide a `browserToken`. It is shared with the browser so it should only have Viewer rights or lower. You can silence this warning by setting `browserToken: false`.',\n )\n }\n\n const client = _client.withConfig({allowReconfigure: false, useCdn: false})\n const {token: originalToken, stega: stegaConfig} = client.config()\n const studioUrlDefined = typeof client.config().stega.studioUrl !== 'undefined'\n const {stega: stegaEnabled = typeof client.config().stega.studioUrl !== 'undefined'} = config\n\n const sanityFetch: DefinedSanityFetchType = async function sanityFetch<\n const QueryString extends string,\n >({\n query,\n params = {},\n stega: _stega,\n tags: customCacheTags = [],\n perspective: _perspective,\n requestTag = 'next-loader.fetch',\n }: {\n query: QueryString\n params?: QueryParams | Promise<QueryParams>\n stega?: boolean\n tags?: string[]\n perspective?: Exclude<ClientPerspective, 'raw'>\n requestTag?: string\n }) {\n const stega = _stega ?? (stegaEnabled && studioUrlDefined && (await draftMode()).isEnabled)\n const perspective = _perspective ?? ((await draftMode()).isEnabled ? 'drafts' : 'published')\n\n const {apiHost, apiVersion, useProjectHostname, dataset, projectId, requestTagPrefix} =\n client.config()\n const {\n data: _data,\n sourceMap,\n tags,\n } = await sanityCachedFetch(\n {\n apiHost,\n apiVersion,\n useProjectHostname,\n dataset,\n projectId,\n requestTagPrefix,\n token: originalToken,\n },\n {\n query,\n params: await params,\n perspective,\n stega,\n requestTag,\n draftToken: serverToken,\n customCacheTags,\n },\n )\n\n const data =\n stega && sourceMap\n ? stegaEncodeSourceMap(_data, sourceMap, {...stegaConfig, enabled: true})\n : _data\n\n return {data, sourceMap, tags, perspective}\n }\n\n const SanityLive: React.ComponentType<DefinedSanityLiveProps> = function SanityLive(props) {\n const {\n // perspective,\n refreshOnMount = false,\n refreshOnFocus = false,\n refreshOnReconnect = false,\n requestTag,\n onError,\n onGoAway,\n intervalOnGoAway,\n revalidateSyncTags = expireTags,\n } = props\n\n const {projectId, dataset, apiHost, apiVersion, useProjectHostname, requestTagPrefix} =\n client.config()\n const {origin} = new URL(client.getUrl('', false))\n\n // Preconnect to the Live Event API origin early, as the Sanity API is almost always on a different origin than the app\n preconnect(origin)\n\n return (\n <SanityLiveServerComponent\n config={{projectId, dataset, apiHost, apiVersion, useProjectHostname, requestTagPrefix}}\n requestTag={requestTag}\n browserToken={browserToken}\n // origin={origin}\n refreshOnMount={refreshOnMount}\n refreshOnFocus={refreshOnFocus}\n refreshOnReconnect={refreshOnReconnect}\n onError={onError}\n onGoAway={onGoAway}\n intervalOnGoAway={intervalOnGoAway}\n revalidateSyncTags={revalidateSyncTags}\n resolveDraftModePerspective={\n props.resolveDraftModePerspective ?? resolveDraftModePerspective\n }\n />\n )\n }\n\n return {sanityFetch, SanityLive}\n}\n\ninterface SanityLiveServerComponentProps\n extends Omit<SanityLiveProps, 'draftModeEnabled' | 'token' | 'draftModePerspective'> {\n browserToken: string | false | undefined\n // origin: string\n // perspective?: Exclude<ClientPerspective, 'raw'>\n}\n\nconst SanityLiveServerComponent: React.ComponentType<SanityLiveServerComponentProps> =\n async function SanityLiveServerComponent(props) {\n 'use cache'\n // @TODO should this be 'max' instead?, or configured by changing the default cache profile?\n // cacheLife({\n // stale: Infinity,\n // revalidate: Infinity,\n // expire: Infinity,\n // })\n const {\n config,\n requestTag,\n intervalOnGoAway,\n onError,\n onGoAway,\n refreshOnFocus,\n refreshOnMount,\n refreshOnReconnect,\n revalidateSyncTags,\n browserToken,\n // origin,\n // perspective,\n resolveDraftModePerspective,\n } = props\n\n const {isEnabled: isDraftModeEnabled} = await draftMode()\n\n // // Preconnect to the Live Event API origin early, as the Sanity API is almost always on a different origin than the app\n // preconnect(origin)\n\n return (\n <SanityLiveClientComponent\n config={{\n ...config,\n token: typeof browserToken === 'string' && isDraftModeEnabled ? browserToken : undefined,\n }}\n requestTag={requestTag}\n draftModeEnabled={isDraftModeEnabled}\n refreshOnMount={refreshOnMount}\n refreshOnFocus={refreshOnFocus}\n refreshOnReconnect={refreshOnReconnect}\n onError={onError}\n onGoAway={onGoAway}\n intervalOnGoAway={intervalOnGoAway}\n revalidateSyncTags={revalidateSyncTags}\n resolveDraftModePerspective={resolveDraftModePerspective}\n />\n )\n }\n\n// @TODO expose parseTags function that returns the correct array of tags\n// we already have s1: prefixes, but they could change\n// use sp: for prod, sd: for draft, keep em short\nasync function expireTags(_tags: unknown): Promise<void> {\n 'use server'\n // @TODO Draft Mode bypasses cache anyway so we don't bother with expiring tags for draft content\n // const isDraftMode = (await draftMode()).isEnabled\n // const tags = _tags.map((tag) => `${isDraftMode ? 'drafts' : 'sanity'}:${tag}`)\n if (!Array.isArray(_tags)) {\n console.warn('<SanityLive /> `expireTags` called with non-array tags', _tags)\n return undefined\n }\n const tags = _tags.filter(\n (tag) => typeof tag === 'string' && tag.startsWith(PUBLISHED_SYNC_TAG_PREFIX),\n )\n if (!tags.length) {\n console.warn('<SanityLive /> `expireTags` called with no valid tags', _tags)\n return undefined\n }\n for (const tag of tags) {\n updateTag(tag)\n }\n console.log(`<SanityLive /> updated tags: ${tags.join(', ')}`)\n}\n\nasync function resolveDraftModePerspective(): Promise<ClientPerspective> {\n 'use server'\n if ((await draftMode()).isEnabled) {\n const jar = await cookies()\n return resolvePerspectiveFromCookie({cookies: jar})\n }\n return 'published'\n}\n\n/**\n * Add more stuff:\n * - sanityFetchMetadata: sanityFetch({query, params, stega: false, perspective: 'auto'})\n * - sanityFetchStaticParams: sanityFetch({query, params, stega: false, perspective: 'published', cacheMode: undefined})\n * - sanityFetchCached: sanityFetch({query, params, stega: 'opt-in',perspective: 'opt-in'}) useful for 'use cache' components, no unexpected magic, maybe this will be `sanityFetch` instead\n * - sanityFetchDynamic: sanityFetch({query, params, stega: 'auto', perspective: 'auto'}) just like sanityFetch of old, since `sanityFetch` will likely become opt-in\n */\n"],"mappings":";;;;;;;;;;;AA2BA,eAAsB,6BAA6B,EACjD,SAAS,OAGoC;AAC7C,QAAO,IAAI,IAAI,sBAAsB,GACjC,oBAAoB,IAAI,IAAI,sBAAsB,EAAE,OAAO,SAAS,GACpE;;AAGN,eAAe,kBACb,QACA,EACE,OACA,SAAS,EAAE,EACX,aACA,OACA,YACA,YACA,kBAAkB,EAAE,IAcrB;AACD;CAEA,MAAM,SAAS,aAAa;EAAC,GAAG;EAAQ,QAAQ;EAAK,CAAC;CACtD,MAAM,SAAS,gBAAgB;CAW/B,MAAM,EAAC,QAAQ,iBAAiB,aAAY,MAAM,OAAO,MAAM,OAAO,QAAQ;EAC5E,gBAAgB;EAChB,aAAa;EACb;EACA;EACA,iBAAiB,QAAQ,yBAAyB,KAAA;EAClD,WAAW,SAAS,YAAY,KAAA;EAChC,KAAK;EACL,OAAO,gBAAgB,cAAc,OAAO,QAAQ,cAAc,OAAO;EAC1E,CAAC;CACF,MAAM,OAAO,CACX,GAAG,iBACH,IAAI,YAAY,EAAE,EAAE,KACjB,QACC,GAAG,gBAAgB,cAAc,4BAA4B,wBAAwB,MACxF,CACF;AAID,UAAS,GAAG,KAAK;AAEjB,QAAO;EAAC,MAAM;EAAQ,WAAW,mBAAmB;EAAM;EAAK;;AA2IjE,SAAgB,WAAW,QASzB;CACA,MAAM,EAAC,QAAQ,SAAS,aAAa,iBAAgB;AAErD,KAAI,CAAC,QACH,OAAM,IAAI,MAAM,oDAAoD;AAGtE,KAAI,QAAQ,IAAI,aAAa,gBAAgB,CAAC,eAAe,gBAAgB,MAE3E,SAAQ,KACN,8LACD;AAGH,KAAI,QAAQ,IAAI,aAAa,gBAAgB,CAAC,gBAAgB,iBAAiB,MAE7E,SAAQ,KACN,iXACD;CAGH,MAAM,SAAS,QAAQ,WAAW;EAAC,kBAAkB;EAAO,QAAQ;EAAM,CAAC;CAC3E,MAAM,EAAC,OAAO,eAAe,OAAO,gBAAe,OAAO,QAAQ;CAClE,MAAM,mBAAmB,OAAO,OAAO,QAAQ,CAAC,MAAM,cAAc;CACpE,MAAM,EAAC,OAAO,eAAe,OAAO,OAAO,QAAQ,CAAC,MAAM,cAAc,gBAAe;AAiGvF,QAAO;EAAC,aA/FoC,eAAe,YAEzD,EACA,OACA,SAAS,EAAE,EACX,OAAO,QACP,MAAM,kBAAkB,EAAE,EAC1B,aAAa,cACb,aAAa,uBAQZ;GACD,MAAM,QAAQ,WAAW,gBAAgB,qBAAqB,MAAM,WAAW,EAAE;GACjF,MAAM,cAAc,kBAAkB,MAAM,WAAW,EAAE,YAAY,WAAW;GAEhF,MAAM,EAAC,SAAS,YAAY,oBAAoB,SAAS,WAAW,qBAClE,OAAO,QAAQ;GACjB,MAAM,EACJ,MAAM,OACN,WACA,SACE,MAAM,kBACR;IACE;IACA;IACA;IACA;IACA;IACA;IACA,OAAO;IACR,EACD;IACE;IACA,QAAQ,MAAM;IACd;IACA;IACA;IACA,YAAY;IACZ;IACD,CACF;AAOD,UAAO;IAAC,MAJN,SAAS,YACL,qBAAqB,OAAO,WAAW;KAAC,GAAG;KAAa,SAAS;KAAK,CAAC,GACvE;IAEQ;IAAW;IAAM;IAAY;;EA2CxB,YAxC2C,SAAS,WAAW,OAAO;GACzF,MAAM,EAEJ,iBAAiB,OACjB,iBAAiB,OACjB,qBAAqB,OACrB,YACA,SACA,UACA,kBACA,qBAAqB,eACnB;GAEJ,MAAM,EAAC,WAAW,SAAS,SAAS,YAAY,oBAAoB,qBAClE,OAAO,QAAQ;GACjB,MAAM,EAAC,WAAU,IAAI,IAAI,OAAO,OAAO,IAAI,MAAM,CAAC;AAGlD,cAAW,OAAO;AAElB,UACE,oBAAC,2BAAA;IACC,QAAQ;KAAC;KAAW;KAAS;KAAS;KAAY;KAAoB;KAAiB;IAC3E;IACE;IAEE;IACA;IACI;IACX;IACC;IACQ;IACE;IACpB,6BACE,MAAM,+BAA+B;KAEvC;;EAI0B;;AAUlC,MAAMA,4BACJ,eAAeC,4BAA0B,OAAO;AAC9C;CAOA,MAAM,EACJ,QACA,YACA,kBACA,SACA,UACA,gBACA,gBACA,oBACA,oBACA,cAGA,6BAAA,kCACE;CAEJ,MAAM,EAAC,WAAW,uBAAsB,MAAM,WAAW;AAKzD,QACE,oBAAC,2BAAA;EACC,QAAQ;GACN,GAAG;GACH,OAAO,OAAO,iBAAiB,YAAY,qBAAqB,eAAe,KAAA;GAChF;EACW;EACZ,kBAAkB;EACF;EACA;EACI;EACX;EACC;EACQ;EACE;EACpB,6BAA6BC;GAC7B;;AAOR,eAAe,WAAW,OAA+B;AACvD;AAIA,KAAI,CAAC,MAAM,QAAQ,MAAM,EAAE;AACzB,UAAQ,KAAK,0DAA0D,MAAM;AAC7E;;CAEF,MAAM,OAAO,MAAM,QAChB,QAAQ,OAAO,QAAQ,YAAY,IAAI,WAAW,0BAA0B,CAC9E;AACD,KAAI,CAAC,KAAK,QAAQ;AAChB,UAAQ,KAAK,yDAAyD,MAAM;AAC5E;;AAEF,MAAK,MAAM,OAAO,KAChB,WAAU,IAAI;AAEhB,SAAQ,IAAI,gCAAgC,KAAK,KAAK,KAAK,GAAG;;AAGhE,eAAe,8BAA0D;AACvE;AACA,MAAK,MAAM,WAAW,EAAE,UAEtB,QAAO,6BAA6B,EAAC,SADzB,MAAM,SAAS,EACuB,CAAC;AAErD,QAAO"}
1
+ {"version":3,"file":"live.js","names":["SanityLiveServerComponent: React.ComponentType<SanityLiveServerComponentProps>","SanityLiveServerComponent","resolveDraftModePerspective"],"sources":["../../src/experimental/live.tsx"],"sourcesContent":["// oxlint-disable-next-line no-unassigned-import\nimport 'server-only'\nimport {\n createClient,\n type ClientPerspective,\n type ClientReturn,\n type ContentSourceMap,\n type LiveEventGoAway,\n type QueryParams,\n type SanityClient,\n type SyncTag,\n} from 'next-sanity'\nimport {stegaEncodeSourceMap} from '@sanity/client/stega'\nimport SanityLiveClientComponent, {\n type SanityLiveProps,\n} from 'next-sanity/experimental/client-components/live'\nimport {cacheTag, cacheLife, updateTag} from 'next/cache'\nimport {draftMode, cookies} from 'next/headers'\nimport {preconnect} from 'react-dom'\nimport {perspectiveCookieName} from '@sanity/preview-url-secret/constants'\nimport {sanitizePerspective} from '../live/utils'\nimport type {SanityClientConfig} from './types'\nimport {DRAFT_SYNC_TAG_PREFIX, PUBLISHED_SYNC_TAG_PREFIX} from './constants'\n\n/**\n * @alpha CAUTION: This API does not follow semver and could have breaking changes in future minor releases.\n */\nexport async function resolvePerspectiveFromCookies({\n cookies: jar,\n}: {\n cookies: Awaited<ReturnType<typeof cookies>>\n}): Promise<Exclude<ClientPerspective, 'raw'>> {\n return jar.has(perspectiveCookieName)\n ? sanitizePerspective(jar.get(perspectiveCookieName)?.value, 'drafts')\n : 'drafts'\n}\n\nasync function sanityCachedFetch<const QueryString extends string>(\n config: SanityClientConfig,\n {\n query,\n params = {},\n perspective,\n stega,\n requestTag,\n draftToken,\n customCacheTags = [],\n }: {\n query: QueryString\n params?: QueryParams | Promise<QueryParams>\n perspective: Exclude<ClientPerspective, 'raw'>\n stega: boolean\n requestTag: string\n draftToken?: string | false | undefined\n customCacheTags?: string[]\n },\n): Promise<{\n data: ClientReturn<QueryString, unknown>\n sourceMap: ContentSourceMap | null\n tags: string[]\n}> {\n 'use cache: remote'\n\n const client = createClient({...config, useCdn: true})\n const useCdn = perspective === 'published'\n\n const {result, resultSourceMap, syncTags} = await client.fetch(query, await params, {\n filterResponse: false,\n returnQuery: false,\n perspective,\n useCdn,\n resultSourceMap: stega ? 'withKeyArraySelector' : undefined, // @TODO allow passing csm for non-stega use\n stega: false,\n cacheMode: useCdn ? 'noStale' : undefined,\n tag: requestTag,\n token: perspective === 'published' ? config.token : draftToken || config.token, // @TODO can pass undefined instead of config.token here?\n })\n const tags = [\n ...customCacheTags,\n ...(syncTags || []).map(\n (tag) =>\n `${perspective === 'published' ? PUBLISHED_SYNC_TAG_PREFIX : DRAFT_SYNC_TAG_PREFIX}${tag}`,\n ),\n ]\n /**\n * The tags used here, are expired later on in the `expireTags` Server Action with the `expireTag` function from `next/cache`\n */\n cacheTag(...tags)\n /**\n * Sanity Live handles on-demand revalidation, so the default 15min time based revalidation is too short\n */\n cacheLife({revalidate: 60 * 60 * 24 * 90})\n\n return {data: result, sourceMap: resultSourceMap || null, tags}\n}\n\n/**\n * @alpha CAUTION: This API does not follow semver and could have breaking changes in future minor releases.\n */\nexport interface SanityFetchOptions<QueryString extends string> {\n query: QueryString\n params?: QueryParams | Promise<QueryParams>\n /**\n * @defaultValue 'published'\n */\n perspective?: Exclude<ClientPerspective, 'raw'>\n /**\n * 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.\n * @defaultValue `false`\n */\n stega?: boolean\n /**\n * This request tag is used to identify the request when viewing request logs from your Sanity Content Lake.\n * @see https://www.sanity.io/docs/reference-api-request-tags\n * @defaultValue 'next-loader.fetch'\n */\n requestTag?: string\n /**\n * Custom cache tags that can be used with next's `revalidateTag` and `updateTag` functions for custom webhook on-demand revalidation.\n */\n tags?: string[]\n}\n\n/**\n * @alpha CAUTION: This API does not follow semver and could have breaking changes in future minor releases.\n */\nexport type DefinedSanityFetchType = <const QueryString extends string>(\n options: SanityFetchOptions<QueryString>,\n) => Promise<{\n data: ClientReturn<QueryString, unknown>\n /**\n * 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.\n * 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`\n */\n sourceMap: ContentSourceMap | null\n /**\n * The cache tags used with `next/cache`, useful for debugging.\n */\n tags: string[]\n}>\n\n/**\n * @alpha CAUTION: This API does not follow semver and could have breaking changes in future minor releases.\n */\nexport interface DefinedSanityLiveProps {\n /**\n * Automatic refresh of RSC when the component <SanityLive /> is mounted.\n * @defaultValue `false`\n */\n refreshOnMount?: boolean\n /**\n * Automatically refresh when window gets focused\n * @defaultValue `false`\n */\n refreshOnFocus?: boolean\n /**\n * Automatically refresh when the browser regains a network connection (via navigator.onLine)\n * @defaultValue `false`\n */\n refreshOnReconnect?: boolean\n /**\n * Automatically refresh on an interval when the Live Event API emits a `goaway` event, which indicates that the connection is rejected or closed.\n * This typically happens if the connection limit is reached, or if the connection is idle for too long.\n * To disable this long polling fallback behavior set `intervalOnGoAway` to `false` or `0`.\n * You can also use `onGoAway` to handle the `goaway` event in your own way, and read the reason why the event was emitted.\n * @defaultValue `30_000` 30 seconds interval\n */\n intervalOnGoAway?: number | false\n\n /**\n * This request tag is used to identify the request when viewing request logs from your Sanity Content Lake.\n * @see https://www.sanity.io/docs/reference-api-request-tags\n * @defaultValue 'next-loader.live'\n */\n requestTag?: string\n\n /**\n * Handle errors from the Live Events subscription.\n * By default it's reported using `console.error`, you can override this prop to handle it in your own way.\n */\n onError?: (error: unknown) => void\n\n /**\n * Handle the `goaway` event if the connection is rejected/closed.\n * `event.reason` will be a string of why the event was emitted, for example `'connection limit reached'`.\n * When this happens the `<SanityLive />` will fallback to long polling with a default interval of 30 seconds, providing your own `onGoAway` handler does not change this behavior.\n * If you want to disable long polling set `intervalOnGoAway` to `false` or `0`.\n */\n onGoAway?: (event: LiveEventGoAway, intervalOnGoAway: number | false) => void\n\n /**\n * Override how cache tags are invalidated, you need to pass a server action here.\n * You can also pass a `use client` function here, and have `router.refresh()` be called if the promise resolves to `'refresh'`.\n */\n // @TODO remove, replace with onLiveEvent\n revalidateSyncTags?: (\n tags: `${typeof PUBLISHED_SYNC_TAG_PREFIX | typeof DRAFT_SYNC_TAG_PREFIX}${SyncTag}`[],\n ) => Promise<void | 'refresh'>\n\n // @TODO add\n // decide how to handle a live event coming in\n // onLiveEvent?: (event: LiveEvent, mode: 'production' | 'preview) => void\n\n /**\n * Control how the draft mode perspective is resolved, by default it resolves from the `sanity-preview-perspective` cookie.\n */\n resolveDraftModePerspective?: () => Promise<ClientPerspective>\n}\n\n/**\n * @alpha CAUTION: This API does not follow semver and could have breaking changes in future minor releases.\n */\nexport interface DefineSanityLiveOptions {\n /**\n * Required for `sanityFetch` and `SanityLive` to work\n */\n client: SanityClient\n /**\n * Optional. If provided then the token needs to have permissions to query documents with `drafts.` prefixes in order for `perspective: 'drafts'` to work.\n * This token is not shared with the browser.\n */\n serverToken?: string | false\n /**\n * Optional. This token is shared with the browser, and should only have access to query published documents.\n * It is used to setup a `Live Draft Content` EventSource connection, and enables live previewing drafts stand-alone, outside of Presentation Tool.\n */\n browserToken?: string | false\n}\n\n/**\n * @alpha CAUTION: This API does not follow semver and could have breaking changes in future minor releases.\n */\nexport function defineLive(config: DefineSanityLiveOptions): {\n /**\n * Use this function to fetch data from Sanity in your React Server Components.\n */\n sanityFetch: DefinedSanityFetchType\n /**\n * Render this in your root layout.tsx to make your page revalidate on new content live, automatically.\n */\n SanityLive: React.ComponentType<DefinedSanityLiveProps>\n} {\n const {client: _client, serverToken, browserToken} = config\n\n if (!_client) {\n throw new Error('`client` is required for `defineLive` to function')\n }\n\n if (process.env.NODE_ENV !== 'production' && !serverToken && serverToken !== false) {\n // eslint-disable-next-line no-console\n console.warn(\n 'No `serverToken` provided to `defineLive`. This means that only published content will be fetched and respond to live events. You can silence this warning by setting `serverToken: false`.',\n )\n }\n\n if (process.env.NODE_ENV !== 'production' && !browserToken && browserToken !== false) {\n // eslint-disable-next-line no-console\n console.warn(\n 'No `browserToken` provided to `defineLive`. This means that live previewing drafts will only work when using the Presentation Tool in your Sanity Studio. To support live previewing drafts stand-alone, provide a `browserToken`. It is shared with the browser so it should only have Viewer rights or lower. You can silence this warning by setting `browserToken: false`.',\n )\n }\n\n const client = _client.withConfig({allowReconfigure: false, useCdn: false})\n const {\n token: originalToken,\n apiHost,\n apiVersion,\n useProjectHostname,\n dataset,\n projectId,\n requestTagPrefix,\n stega: stegaConfig,\n } = client.config()\n\n const sanityFetch: DefinedSanityFetchType = function sanityFetch<\n const QueryString extends string,\n >({\n query,\n params = {},\n stega = false,\n perspective = 'published',\n tags: customCacheTags = [],\n requestTag = 'next-loader.fetch',\n }: {\n query: QueryString\n params?: QueryParams | Promise<QueryParams>\n stega?: boolean\n tags?: string[]\n perspective?: Exclude<ClientPerspective, 'raw'>\n requestTag?: string\n }) {\n return sanityCachedFetch(\n {\n apiHost,\n apiVersion,\n useProjectHostname,\n dataset,\n projectId,\n requestTagPrefix,\n token: originalToken,\n },\n {\n query,\n params,\n perspective,\n stega,\n requestTag,\n draftToken: serverToken,\n customCacheTags,\n },\n ).then(({data, sourceMap, tags}) => ({\n data:\n stega && sourceMap\n ? stegaEncodeSourceMap(data, sourceMap, {...stegaConfig, enabled: true})\n : data,\n sourceMap,\n tags,\n }))\n }\n\n const SanityLive: React.ComponentType<DefinedSanityLiveProps> = function SanityLive(props) {\n const {\n // perspective,\n refreshOnMount = false,\n refreshOnFocus = false,\n refreshOnReconnect = false,\n requestTag,\n onError,\n onGoAway,\n intervalOnGoAway,\n revalidateSyncTags = expireTags,\n } = props\n\n const {projectId, dataset, apiHost, apiVersion, useProjectHostname, requestTagPrefix} =\n client.config()\n const {origin} = new URL(client.getUrl('', false))\n\n // Preconnect to the Live Event API origin early, as the Sanity API is almost always on a different origin than the app\n preconnect(origin)\n\n return (\n <SanityLiveServerComponent\n config={{projectId, dataset, apiHost, apiVersion, useProjectHostname, requestTagPrefix}}\n requestTag={requestTag}\n browserToken={browserToken}\n // origin={origin}\n refreshOnMount={refreshOnMount}\n refreshOnFocus={refreshOnFocus}\n refreshOnReconnect={refreshOnReconnect}\n onError={onError}\n onGoAway={onGoAway}\n intervalOnGoAway={intervalOnGoAway}\n revalidateSyncTags={revalidateSyncTags}\n resolveDraftModePerspective={\n props.resolveDraftModePerspective ?? resolveDraftModePerspective\n }\n />\n )\n }\n\n return {sanityFetch, SanityLive}\n}\n\ninterface SanityLiveServerComponentProps\n extends Omit<SanityLiveProps, 'draftModeEnabled' | 'token' | 'draftModePerspective'> {\n browserToken: string | false | undefined\n // origin: string\n // perspective?: Exclude<ClientPerspective, 'raw'>\n}\n\nconst SanityLiveServerComponent: React.ComponentType<SanityLiveServerComponentProps> =\n async function SanityLiveServerComponent(props) {\n // 'use cache'\n // @TODO should this be 'max' instead?, or configured by changing the default cache profile?\n // cacheLife({\n // stale: Infinity,\n // revalidate: Infinity,\n // expire: Infinity,\n // })\n const {\n config,\n requestTag,\n intervalOnGoAway,\n onError,\n onGoAway,\n refreshOnFocus,\n refreshOnMount,\n refreshOnReconnect,\n revalidateSyncTags,\n browserToken,\n // origin,\n // perspective,\n resolveDraftModePerspective,\n } = props\n\n const {isEnabled: isDraftModeEnabled} = await draftMode()\n\n // // Preconnect to the Live Event API origin early, as the Sanity API is almost always on a different origin than the app\n // preconnect(origin)\n\n return (\n <SanityLiveClientComponent\n config={{\n ...config,\n token: typeof browserToken === 'string' && isDraftModeEnabled ? browserToken : undefined,\n }}\n requestTag={requestTag}\n draftModeEnabled={isDraftModeEnabled}\n refreshOnMount={refreshOnMount}\n refreshOnFocus={refreshOnFocus}\n refreshOnReconnect={refreshOnReconnect}\n onError={onError}\n onGoAway={onGoAway}\n intervalOnGoAway={intervalOnGoAway}\n revalidateSyncTags={revalidateSyncTags}\n resolveDraftModePerspective={resolveDraftModePerspective}\n />\n )\n }\n\n// @TODO expose parseTags function that returns the correct array of tags\n// we already have s1: prefixes, but they could change\n// use sp: for prod, sd: for draft, keep em short\nasync function expireTags(_tags: unknown): Promise<void> {\n 'use server'\n // @TODO Draft Mode bypasses cache anyway so we don't bother with expiring tags for draft content\n // const isDraftMode = (await draftMode()).isEnabled\n // const tags = _tags.map((tag) => `${isDraftMode ? 'drafts' : 'sanity'}:${tag}`)\n if (!Array.isArray(_tags)) {\n console.warn('<SanityLive /> `expireTags` called with non-array tags', _tags)\n return undefined\n }\n const tags = _tags.filter(\n (tag) => typeof tag === 'string' && tag.startsWith(PUBLISHED_SYNC_TAG_PREFIX),\n )\n if (!tags.length) {\n console.warn('<SanityLive /> `expireTags` called with no valid tags', _tags)\n return undefined\n }\n for (const tag of tags) {\n updateTag(tag)\n }\n console.log(`<SanityLive /> updated tags: ${tags.join(', ')}`)\n}\n\nasync function resolveDraftModePerspective(): Promise<ClientPerspective> {\n 'use server'\n if ((await draftMode()).isEnabled) {\n const jar = await cookies()\n return resolvePerspectiveFromCookies({cookies: jar})\n }\n return 'published'\n}\n\n// revalidateSyncTags => actionUpdateTags\n// router.refresh() => actionRefresh\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "next-sanity",
3
- "version": "11.5.7",
3
+ "version": "11.6.0",
4
4
  "description": "Sanity.io toolkit for Next.js",
5
5
  "keywords": [
6
6
  "sanity",
@@ -91,7 +91,11 @@
91
91
  },
92
92
  "files": [
93
93
  "dist",
94
- "src"
94
+ "src",
95
+ "!src/**/test/**",
96
+ "!src/**/*.test.ts",
97
+ "!src/**/*.test.tsx",
98
+ "!src/**/*.test-d.ts"
95
99
  ],
96
100
  "browserslist": "extends @sanity/browserslist-config",
97
101
  "dependencies": {
@@ -109,21 +113,21 @@
109
113
  },
110
114
  "devDependencies": {
111
115
  "@sanity/browserslist-config": "^1.0.5",
112
- "@sanity/pkg-utils": "^8.1.24",
116
+ "@sanity/pkg-utils": "^8.1.25",
113
117
  "@sanity/types": "^4.11.0",
114
118
  "@sanity/webhook": "4.0.4",
115
119
  "@types/react": "^19.1.8",
116
120
  "@types/react-dom": "^19.1.6",
117
- "@vitest/coverage-v8": "^3.2.4",
118
- "next": "^15.3.5",
119
- "publint": "^0.3.14",
120
- "react": "^19.1.0",
121
- "react-dom": "^19.1.0",
121
+ "@vitest/coverage-v8": "^4.0.1",
122
+ "next": "16.0.1-canary.1",
123
+ "publint": "^0.3.15",
124
+ "react": "^19.2.0",
125
+ "react-dom": "^19.2.0",
122
126
  "styled-components": "^6.1.19",
123
- "tsdown": "0.15.7",
127
+ "tsdown": "0.15.9",
124
128
  "typescript": "5.9.3",
125
129
  "vite-tsconfig-paths": "^5.1.4",
126
- "vitest": "^3.2.4"
130
+ "vitest": "^4.0.1"
127
131
  },
128
132
  "peerDependencies": {
129
133
  "@sanity/client": "^7.12.0",
@@ -14,7 +14,7 @@ import {stegaEncodeSourceMap} from '@sanity/client/stega'
14
14
  import SanityLiveClientComponent, {
15
15
  type SanityLiveProps,
16
16
  } from 'next-sanity/experimental/client-components/live'
17
- import {cacheTag, updateTag} from 'next/cache'
17
+ import {cacheTag, cacheLife, updateTag} from 'next/cache'
18
18
  import {draftMode, cookies} from 'next/headers'
19
19
  import {preconnect} from 'react-dom'
20
20
  import {perspectiveCookieName} from '@sanity/preview-url-secret/constants'
@@ -25,7 +25,7 @@ import {DRAFT_SYNC_TAG_PREFIX, PUBLISHED_SYNC_TAG_PREFIX} from './constants'
25
25
  /**
26
26
  * @alpha CAUTION: This API does not follow semver and could have breaking changes in future minor releases.
27
27
  */
28
- export async function resolvePerspectiveFromCookie({
28
+ export async function resolvePerspectiveFromCookies({
29
29
  cookies: jar,
30
30
  }: {
31
31
  cookies: Awaited<ReturnType<typeof cookies>>
@@ -47,7 +47,7 @@ async function sanityCachedFetch<const QueryString extends string>(
47
47
  customCacheTags = [],
48
48
  }: {
49
49
  query: QueryString
50
- params?: QueryParams
50
+ params?: QueryParams | Promise<QueryParams>
51
51
  perspective: Exclude<ClientPerspective, 'raw'>
52
52
  stega: boolean
53
53
  requestTag: string
@@ -59,26 +59,18 @@ async function sanityCachedFetch<const QueryString extends string>(
59
59
  sourceMap: ContentSourceMap | null
60
60
  tags: string[]
61
61
  }> {
62
- 'use cache'
62
+ 'use cache: remote'
63
63
 
64
64
  const client = createClient({...config, useCdn: true})
65
65
  const useCdn = perspective === 'published'
66
- /**
67
- * The default cache profile isn't ideal for live content, as it has unnecessary time based background validation, as well as a too lazy client stale value
68
- * https://github.com/vercel/next.js/blob/8dd358002baf4244c0b2e38b5bda496daf60dacb/packages/next/cache.d.ts#L14-L26
69
- */
70
- // cacheLife({
71
- // stale: Infinity,
72
- // revalidate: Infinity,
73
- // expire: Infinity,
74
- // })
75
66
 
76
- const {result, resultSourceMap, syncTags} = await client.fetch(query, params, {
67
+ const {result, resultSourceMap, syncTags} = await client.fetch(query, await params, {
77
68
  filterResponse: false,
78
69
  returnQuery: false,
79
70
  perspective,
80
71
  useCdn,
81
72
  resultSourceMap: stega ? 'withKeyArraySelector' : undefined, // @TODO allow passing csm for non-stega use
73
+ stega: false,
82
74
  cacheMode: useCdn ? 'noStale' : undefined,
83
75
  tag: requestTag,
84
76
  token: perspective === 'published' ? config.token : draftToken || config.token, // @TODO can pass undefined instead of config.token here?
@@ -94,6 +86,10 @@ async function sanityCachedFetch<const QueryString extends string>(
94
86
  * The tags used here, are expired later on in the `expireTags` Server Action with the `expireTag` function from `next/cache`
95
87
  */
96
88
  cacheTag(...tags)
89
+ /**
90
+ * Sanity Live handles on-demand revalidation, so the default 15min time based revalidation is too short
91
+ */
92
+ cacheLife({revalidate: 60 * 60 * 24 * 90})
97
93
 
98
94
  return {data: result, sourceMap: resultSourceMap || null, tags}
99
95
  }
@@ -101,14 +97,16 @@ async function sanityCachedFetch<const QueryString extends string>(
101
97
  /**
102
98
  * @alpha CAUTION: This API does not follow semver and could have breaking changes in future minor releases.
103
99
  */
104
- export type DefinedSanityFetchType = <const QueryString extends string>(options: {
100
+ export interface SanityFetchOptions<QueryString extends string> {
105
101
  query: QueryString
106
102
  params?: QueryParams | Promise<QueryParams>
103
+ /**
104
+ * @defaultValue 'published'
105
+ */
107
106
  perspective?: Exclude<ClientPerspective, 'raw'>
108
107
  /**
109
- * Enables stega encoding of the data, this is typically only used in draft mode.
110
- * If `defineLive({..., stega: true})` is provided, then it defaults to `true` in Draft Mode.
111
- * If `defineLive({..., stega: false})` then it defaults to `false`.
108
+ * 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.
109
+ * @defaultValue `false`
112
110
  */
113
111
  stega?: boolean
114
112
  /**
@@ -118,20 +116,23 @@ export type DefinedSanityFetchType = <const QueryString extends string>(options:
118
116
  */
119
117
  requestTag?: string
120
118
  /**
121
- * Custom cache tags that can be used with next's `revalidateTag` function for custom webhook on-demand revalidation.
119
+ * Custom cache tags that can be used with next's `revalidateTag` and `updateTag` functions for custom webhook on-demand revalidation.
122
120
  */
123
121
  tags?: string[]
124
- }) => Promise<{
122
+ }
123
+
124
+ /**
125
+ * @alpha CAUTION: This API does not follow semver and could have breaking changes in future minor releases.
126
+ */
127
+ export type DefinedSanityFetchType = <const QueryString extends string>(
128
+ options: SanityFetchOptions<QueryString>,
129
+ ) => Promise<{
125
130
  data: ClientReturn<QueryString, unknown>
126
131
  /**
127
132
  * 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.
128
133
  * 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`
129
134
  */
130
135
  sourceMap: ContentSourceMap | null
131
- /**
132
- * The perspective used to fetch the data, useful for debugging.
133
- */
134
- perspective: Exclude<ClientPerspective, 'raw'>
135
136
  /**
136
137
  * The cache tags used with `next/cache`, useful for debugging.
137
138
  */
@@ -224,11 +225,6 @@ export interface DefineSanityLiveOptions {
224
225
  * It is used to setup a `Live Draft Content` EventSource connection, and enables live previewing drafts stand-alone, outside of Presentation Tool.
225
226
  */
226
227
  browserToken?: string | false
227
- /**
228
- * Optional. Include stega encoding when draft mode is enabled.
229
- * @defaultValue `true` if the client configuration has the `stega.studioUrl` property set, otherwise `false`
230
- */
231
- stega?: boolean
232
228
  }
233
229
 
234
230
  /**
@@ -265,18 +261,25 @@ export function defineLive(config: DefineSanityLiveOptions): {
265
261
  }
266
262
 
267
263
  const client = _client.withConfig({allowReconfigure: false, useCdn: false})
268
- const {token: originalToken, stega: stegaConfig} = client.config()
269
- const studioUrlDefined = typeof client.config().stega.studioUrl !== 'undefined'
270
- const {stega: stegaEnabled = typeof client.config().stega.studioUrl !== 'undefined'} = config
271
-
272
- const sanityFetch: DefinedSanityFetchType = async function sanityFetch<
264
+ const {
265
+ token: originalToken,
266
+ apiHost,
267
+ apiVersion,
268
+ useProjectHostname,
269
+ dataset,
270
+ projectId,
271
+ requestTagPrefix,
272
+ stega: stegaConfig,
273
+ } = client.config()
274
+
275
+ const sanityFetch: DefinedSanityFetchType = function sanityFetch<
273
276
  const QueryString extends string,
274
277
  >({
275
278
  query,
276
279
  params = {},
277
- stega: _stega,
280
+ stega = false,
281
+ perspective = 'published',
278
282
  tags: customCacheTags = [],
279
- perspective: _perspective,
280
283
  requestTag = 'next-loader.fetch',
281
284
  }: {
282
285
  query: QueryString
@@ -286,16 +289,7 @@ export function defineLive(config: DefineSanityLiveOptions): {
286
289
  perspective?: Exclude<ClientPerspective, 'raw'>
287
290
  requestTag?: string
288
291
  }) {
289
- const stega = _stega ?? (stegaEnabled && studioUrlDefined && (await draftMode()).isEnabled)
290
- const perspective = _perspective ?? ((await draftMode()).isEnabled ? 'drafts' : 'published')
291
-
292
- const {apiHost, apiVersion, useProjectHostname, dataset, projectId, requestTagPrefix} =
293
- client.config()
294
- const {
295
- data: _data,
296
- sourceMap,
297
- tags,
298
- } = await sanityCachedFetch(
292
+ return sanityCachedFetch(
299
293
  {
300
294
  apiHost,
301
295
  apiVersion,
@@ -307,21 +301,21 @@ export function defineLive(config: DefineSanityLiveOptions): {
307
301
  },
308
302
  {
309
303
  query,
310
- params: await params,
304
+ params,
311
305
  perspective,
312
306
  stega,
313
307
  requestTag,
314
308
  draftToken: serverToken,
315
309
  customCacheTags,
316
310
  },
317
- )
318
-
319
- const data =
320
- stega && sourceMap
321
- ? stegaEncodeSourceMap(_data, sourceMap, {...stegaConfig, enabled: true})
322
- : _data
323
-
324
- return {data, sourceMap, tags, perspective}
311
+ ).then(({data, sourceMap, tags}) => ({
312
+ data:
313
+ stega && sourceMap
314
+ ? stegaEncodeSourceMap(data, sourceMap, {...stegaConfig, enabled: true})
315
+ : data,
316
+ sourceMap,
317
+ tags,
318
+ }))
325
319
  }
326
320
 
327
321
  const SanityLive: React.ComponentType<DefinedSanityLiveProps> = function SanityLive(props) {
@@ -376,7 +370,7 @@ interface SanityLiveServerComponentProps
376
370
 
377
371
  const SanityLiveServerComponent: React.ComponentType<SanityLiveServerComponentProps> =
378
372
  async function SanityLiveServerComponent(props) {
379
- 'use cache'
373
+ // 'use cache'
380
374
  // @TODO should this be 'max' instead?, or configured by changing the default cache profile?
381
375
  // cacheLife({
382
376
  // stale: Infinity,
@@ -453,15 +447,10 @@ async function resolveDraftModePerspective(): Promise<ClientPerspective> {
453
447
  'use server'
454
448
  if ((await draftMode()).isEnabled) {
455
449
  const jar = await cookies()
456
- return resolvePerspectiveFromCookie({cookies: jar})
450
+ return resolvePerspectiveFromCookies({cookies: jar})
457
451
  }
458
452
  return 'published'
459
453
  }
460
454
 
461
- /**
462
- * Add more stuff:
463
- * - sanityFetchMetadata: sanityFetch({query, params, stega: false, perspective: 'auto'})
464
- * - sanityFetchStaticParams: sanityFetch({query, params, stega: false, perspective: 'published', cacheMode: undefined})
465
- * - sanityFetchCached: sanityFetch({query, params, stega: 'opt-in',perspective: 'opt-in'}) useful for 'use cache' components, no unexpected magic, maybe this will be `sanityFetch` instead
466
- * - sanityFetchDynamic: sanityFetch({query, params, stega: 'auto', perspective: 'auto'}) just like sanityFetch of old, since `sanityFetch` will likely become opt-in
467
- */
455
+ // revalidateSyncTags => actionUpdateTags
456
+ // router.refresh() => actionRefresh