hydrogen-sanity 6.1.1 → 6.2.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.
- package/dist/_chunks-es/utils.js +12 -1
- package/dist/_chunks-es/utils.js.map +1 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.js +10 -10
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/context.test.ts +133 -15
- package/src/context.ts +19 -10
- package/src/index.ts +1 -0
- package/src/utils.test.ts +42 -1
- package/src/utils.ts +16 -0
package/dist/_chunks-es/utils.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { validateApiPerspective } from '@sanity/client';
|
|
2
|
+
import { urlSearchParamPreviewPerspective } from '@sanity/preview-url-secret/constants';
|
|
2
3
|
|
|
3
4
|
async function sha256(message) {
|
|
4
5
|
const messageBuffer = await new TextEncoder().encode(message);
|
|
@@ -36,6 +37,16 @@ function getPerspective(session) {
|
|
|
36
37
|
validateApiPerspective(perspective);
|
|
37
38
|
return perspective;
|
|
38
39
|
}
|
|
40
|
+
function getPerspectiveFromUrl(url) {
|
|
41
|
+
try {
|
|
42
|
+
const parsed = typeof url === "string" ? new URL(url) : url;
|
|
43
|
+
const param = parsed.searchParams.get(urlSearchParamPreviewPerspective);
|
|
44
|
+
if (!param) return void 0;
|
|
45
|
+
return sanitizePerspective(param);
|
|
46
|
+
} catch {
|
|
47
|
+
return void 0;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
39
50
|
function isSanityPreviewSession(session) {
|
|
40
51
|
return isHydrogenSession(session) && "has" in session && typeof session.has === "function" && "destroy" in session && typeof session.destroy === "function";
|
|
41
52
|
}
|
|
@@ -46,5 +57,5 @@ function isServer() {
|
|
|
46
57
|
return typeof document === "undefined";
|
|
47
58
|
}
|
|
48
59
|
|
|
49
|
-
export { getPerspective, hashQuery, isHydrogenSession, isSanityPreviewSession, isServer, sanitizePerspective, supportsPerspectiveStack };
|
|
60
|
+
export { getPerspective, getPerspectiveFromUrl, hashQuery, isHydrogenSession, isSanityPreviewSession, isServer, sanitizePerspective, supportsPerspectiveStack };
|
|
50
61
|
//# sourceMappingURL=utils.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sources":["../../src/utils.ts"],"sourcesContent":["import {\n type ClientPerspective,\n type QueryParams,\n type QueryWithoutParams,\n validateApiPerspective,\n} from '@sanity/client'\nimport type {HydrogenSession} from '@shopify/hydrogen'\n\nimport type {SanityPreviewSession} from './preview/session'\n\n/**\n * Create an SHA-256 hash as a hex string\n * @see https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest#converting_a_digest_to_a_hex_string\n */\nexport async function sha256(message: string): Promise<string> {\n // encode as UTF-8\n const messageBuffer = await new TextEncoder().encode(message)\n // hash the message\n const hashBuffer = await crypto.subtle.digest('SHA-256', messageBuffer)\n // convert bytes to hex string\n return Array.from(new Uint8Array(hashBuffer))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('')\n}\n\n/**\n * Hash query and its parameters for use as cache key.\n * NOTE: Oxygen deployment will break if the cache key is long or contains `\\n`\n */\nexport function hashQuery(\n query: string,\n params: QueryParams | QueryWithoutParams,\n): Promise<string> {\n let hash = query\n\n if (params) {\n hash += JSON.stringify(params)\n }\n\n return sha256(hash)\n}\n\n/**\n * Sanitizes and validates a perspective value.\n * Handles both string (comma-separated) and array formats.\n */\nexport function sanitizePerspective(perspective: unknown): Exclude<ClientPerspective, 'raw'> {\n let sanitizedPerspective =\n typeof perspective === 'string' && perspective.includes(',')\n ? perspective.split(',')\n : perspective\n\n // Filter out empty strings and undefined values from perspective array\n if (Array.isArray(sanitizedPerspective)) {\n sanitizedPerspective = sanitizedPerspective.filter(\n (p): p is string => typeof p === 'string' && p.length > 0,\n )\n }\n\n validateApiPerspective(sanitizedPerspective)\n\n return sanitizedPerspective === 'raw' ? 'drafts' : sanitizedPerspective\n}\n\n/**\n * Check if API version supports perspective stack (v2025-02-19 or later)\n * Special versions: '1' doesn't support perspectives, 'X' does support perspectives\n */\nexport function supportsPerspectiveStack(apiVersion: string): boolean {\n // Special cases\n if (apiVersion === '1') return false\n if (apiVersion === 'X') return true\n\n // Normalize version by removing 'v' prefix if present\n const normalizedVersion = `${apiVersion}`.replace(/^v/, '')\n\n // Parse date format: 2025-02-19\n if (!/^\\d{4}-\\d{2}-\\d{2}$/.test(normalizedVersion)) return false\n\n const versionDate = new Date(normalizedVersion)\n const cutoffDate = new Date('2025-02-19')\n\n return versionDate >= cutoffDate\n}\n\n/**\n * Extracts and validates the perspective from a session.\n */\nexport function getPerspective(session: SanityPreviewSession | HydrogenSession): ClientPerspective {\n const perspective = session\n .get('perspective')\n ?.split(',')\n .filter((p: string) => p.length > 0)\n validateApiPerspective(perspective)\n return perspective\n}\n\n/**\n * Type guard that checks if a session object is a SanityPreviewSession.\n * Validates presence of required methods: has, destroy (in addition to Hydrogen session methods).\n */\nexport function isSanityPreviewSession(session: unknown): session is SanityPreviewSession {\n return (\n isHydrogenSession(session) &&\n 'has' in session &&\n typeof session.has === 'function' &&\n 'destroy' in session &&\n typeof session.destroy === 'function'\n )\n}\n\n/**\n * Type guard that checks if a session object is a valid Hydrogen session.\n * Validates presence of required methods: get, set, unset, commit.\n */\nexport function isHydrogenSession(session: unknown): session is HydrogenSession {\n return (\n !!session &&\n typeof session === 'object' &&\n 'get' in session &&\n typeof session.get === 'function' &&\n 'set' in session &&\n typeof session.set === 'function' &&\n 'unset' in session &&\n typeof session.unset === 'function' &&\n 'commit' in session &&\n typeof session.commit === 'function'\n )\n}\n\n/**\n * Utility function that detects if code is running on the server.\n * Used for SSR safety and preventing client-only code from running on server.\n */\nexport function isServer(): boolean {\n return typeof document === 'undefined'\n}\n"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"utils.js","sources":["../../src/utils.ts"],"sourcesContent":["import {\n type ClientPerspective,\n type QueryParams,\n type QueryWithoutParams,\n validateApiPerspective,\n} from '@sanity/client'\nimport {urlSearchParamPreviewPerspective} from '@sanity/preview-url-secret/constants'\nimport type {HydrogenSession} from '@shopify/hydrogen'\n\nimport type {SanityPreviewSession} from './preview/session'\n\n/**\n * Create an SHA-256 hash as a hex string\n * @see https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest#converting_a_digest_to_a_hex_string\n */\nexport async function sha256(message: string): Promise<string> {\n // encode as UTF-8\n const messageBuffer = await new TextEncoder().encode(message)\n // hash the message\n const hashBuffer = await crypto.subtle.digest('SHA-256', messageBuffer)\n // convert bytes to hex string\n return Array.from(new Uint8Array(hashBuffer))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('')\n}\n\n/**\n * Hash query and its parameters for use as cache key.\n * NOTE: Oxygen deployment will break if the cache key is long or contains `\\n`\n */\nexport function hashQuery(\n query: string,\n params: QueryParams | QueryWithoutParams,\n): Promise<string> {\n let hash = query\n\n if (params) {\n hash += JSON.stringify(params)\n }\n\n return sha256(hash)\n}\n\n/**\n * Sanitizes and validates a perspective value.\n * Handles both string (comma-separated) and array formats.\n */\nexport function sanitizePerspective(perspective: unknown): Exclude<ClientPerspective, 'raw'> {\n let sanitizedPerspective =\n typeof perspective === 'string' && perspective.includes(',')\n ? perspective.split(',')\n : perspective\n\n // Filter out empty strings and undefined values from perspective array\n if (Array.isArray(sanitizedPerspective)) {\n sanitizedPerspective = sanitizedPerspective.filter(\n (p): p is string => typeof p === 'string' && p.length > 0,\n )\n }\n\n validateApiPerspective(sanitizedPerspective)\n\n return sanitizedPerspective === 'raw' ? 'drafts' : sanitizedPerspective\n}\n\n/**\n * Check if API version supports perspective stack (v2025-02-19 or later)\n * Special versions: '1' doesn't support perspectives, 'X' does support perspectives\n */\nexport function supportsPerspectiveStack(apiVersion: string): boolean {\n // Special cases\n if (apiVersion === '1') return false\n if (apiVersion === 'X') return true\n\n // Normalize version by removing 'v' prefix if present\n const normalizedVersion = `${apiVersion}`.replace(/^v/, '')\n\n // Parse date format: 2025-02-19\n if (!/^\\d{4}-\\d{2}-\\d{2}$/.test(normalizedVersion)) return false\n\n const versionDate = new Date(normalizedVersion)\n const cutoffDate = new Date('2025-02-19')\n\n return versionDate >= cutoffDate\n}\n\n/**\n * Extracts and validates the perspective from a session.\n */\nexport function getPerspective(session: SanityPreviewSession | HydrogenSession): ClientPerspective {\n const perspective = session\n .get('perspective')\n ?.split(',')\n .filter((p: string) => p.length > 0)\n validateApiPerspective(perspective)\n return perspective\n}\n\n/**\n * Reads the `sanity-preview-perspective` URL search param and validates it.\n * Returns `undefined` if absent or invalid, so callers can fall back to the session.\n */\nexport function getPerspectiveFromUrl(url: URL | string): ClientPerspective | undefined {\n try {\n const parsed = typeof url === 'string' ? new URL(url) : url\n const param = parsed.searchParams.get(urlSearchParamPreviewPerspective)\n if (!param) return undefined\n return sanitizePerspective(param)\n } catch {\n return undefined\n }\n}\n\n/**\n * Type guard that checks if a session object is a SanityPreviewSession.\n * Validates presence of required methods: has, destroy (in addition to Hydrogen session methods).\n */\nexport function isSanityPreviewSession(session: unknown): session is SanityPreviewSession {\n return (\n isHydrogenSession(session) &&\n 'has' in session &&\n typeof session.has === 'function' &&\n 'destroy' in session &&\n typeof session.destroy === 'function'\n )\n}\n\n/**\n * Type guard that checks if a session object is a valid Hydrogen session.\n * Validates presence of required methods: get, set, unset, commit.\n */\nexport function isHydrogenSession(session: unknown): session is HydrogenSession {\n return (\n !!session &&\n typeof session === 'object' &&\n 'get' in session &&\n typeof session.get === 'function' &&\n 'set' in session &&\n typeof session.set === 'function' &&\n 'unset' in session &&\n typeof session.unset === 'function' &&\n 'commit' in session &&\n typeof session.commit === 'function'\n )\n}\n\n/**\n * Utility function that detects if code is running on the server.\n * Used for SSR safety and preventing client-only code from running on server.\n */\nexport function isServer(): boolean {\n return typeof document === 'undefined'\n}\n"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {Any} from '@sanity/client'
|
|
2
2
|
import {CachingStrategy} from '@shopify/hydrogen'
|
|
3
3
|
import {ClientConfig} from '@sanity/client'
|
|
4
|
+
import {ClientPerspective} from '@sanity/client'
|
|
4
5
|
import {ClientReturn} from '@sanity/client'
|
|
5
6
|
import {createDataAttribute} from '@sanity/core-loader/create-data-attribute'
|
|
6
7
|
import {EncodeDataAttributeFunction} from '@sanity/core-loader/encode-data-attribute'
|
|
@@ -86,6 +87,12 @@ declare type FetchOptions<T> = HydrogenResponseQueryOptions & {
|
|
|
86
87
|
}
|
|
87
88
|
}
|
|
88
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Reads the `sanity-preview-perspective` URL search param and validates it.
|
|
92
|
+
* Returns `undefined` if absent or invalid, so callers can fall back to the session.
|
|
93
|
+
*/
|
|
94
|
+
export declare function getPerspectiveFromUrl(url: URL | string): ClientPerspective | undefined
|
|
95
|
+
|
|
89
96
|
declare type HydrogenResponseQueryOptions = Omit<ResponseQueryOptions, 'next' | 'cache'> & {
|
|
90
97
|
hydrogen?: 'hydrogen' extends keyof RequestInit_2 ? RequestInit_2['hydrogen'] : never
|
|
91
98
|
}
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import { createElement, useMemo, lazy, Suspense, useSyncExternalStore, useId, us
|
|
|
4
4
|
import { isPreviewEnabled, usePreviewMode } from './preview/index.js';
|
|
5
5
|
import { SanityProvider, useSanityProviderValue } from './_chunks-es/provider.js';
|
|
6
6
|
export { Sanity } from './_chunks-es/provider.js';
|
|
7
|
-
import { supportsPerspectiveStack, getPerspective, hashQuery, isServer } from './_chunks-es/utils.js';
|
|
7
|
+
import { getPerspectiveFromUrl, supportsPerspectiveStack, getPerspective, hashQuery, isServer } from './_chunks-es/utils.js';
|
|
8
8
|
import { createImageUrlBuilder } from '@sanity/image-url';
|
|
9
9
|
import { jsx } from 'react/jsx-runtime';
|
|
10
10
|
import { useQuery as useQuery$1 } from '@sanity/react-loader';
|
|
@@ -18,7 +18,6 @@ const DEFAULT_CACHE_STRATEGY = CacheLong();
|
|
|
18
18
|
let didWarnAboutNoApiVersion = false;
|
|
19
19
|
let didWarnAboutNoPerspectiveSupport = false;
|
|
20
20
|
let didWarnAboutLoadQuery = false;
|
|
21
|
-
let didInitializeLoader = false;
|
|
22
21
|
async function createSanityContext(options) {
|
|
23
22
|
const { cache, waitUntil = () => Promise.resolve(), request, preview, defaultStrategy } = options;
|
|
24
23
|
const withCache = cache ? createWithCache({ cache, waitUntil, request }) : null;
|
|
@@ -44,7 +43,10 @@ You can find the latest version in the Sanity changelog: https://www.sanity.io/c
|
|
|
44
43
|
if (previewEnabled) {
|
|
45
44
|
const apiVersion2 = client.config().apiVersion;
|
|
46
45
|
let perspective;
|
|
47
|
-
|
|
46
|
+
const urlPerspective = getPerspectiveFromUrl(request.url);
|
|
47
|
+
if (urlPerspective !== void 0 && !(Array.isArray(urlPerspective) && !supportsPerspectiveStack(apiVersion2))) {
|
|
48
|
+
perspective = urlPerspective;
|
|
49
|
+
} else if (supportsPerspectiveStack(apiVersion2)) {
|
|
48
50
|
perspective = getPerspective(preview.session);
|
|
49
51
|
} else {
|
|
50
52
|
if (process.env.NODE_ENV === "development" && !didWarnAboutNoPerspectiveSupport) {
|
|
@@ -78,11 +80,8 @@ You can find the latest version in the Sanity changelog: https://www.sanity.io/c
|
|
|
78
80
|
* Bypasses Hydrogen cache in preview mode.
|
|
79
81
|
*/
|
|
80
82
|
async loadQuery(query, params, loaderOptions) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
setServerClient(client);
|
|
84
|
-
didInitializeLoader = true;
|
|
85
|
-
}
|
|
83
|
+
const { setServerClient } = await import('@sanity/react-loader');
|
|
84
|
+
setServerClient(client);
|
|
86
85
|
if (!previewEnabled && process.env.NODE_ENV === "development" && !didWarnAboutLoadQuery) {
|
|
87
86
|
console.warn(
|
|
88
87
|
`\`loadQuery\` is being called outside of preview mode. Consider using \`query\` instead, which automatically handles both preview and production modes efficiently, or use \`fetch\`. \`loadQuery\` is intended to be called conditionally in preview and visual editing contexts.`
|
|
@@ -91,7 +90,8 @@ You can find the latest version in the Sanity changelog: https://www.sanity.io/c
|
|
|
91
90
|
}
|
|
92
91
|
if (!withCache || previewEnabled) {
|
|
93
92
|
const { loadQuery } = await import('@sanity/react-loader');
|
|
94
|
-
|
|
93
|
+
const resolvedOptions = previewEnabled && !loaderOptions?.perspective ? { ...loaderOptions, perspective: client.config().perspective } : loaderOptions;
|
|
94
|
+
return await loadQuery(query, params, resolvedOptions);
|
|
95
95
|
}
|
|
96
96
|
const cacheStrategy = loaderOptions?.hydrogen?.cache || defaultStrategy || DEFAULT_CACHE_STRATEGY;
|
|
97
97
|
const queryHash = await hashQuery(query, params);
|
|
@@ -227,5 +227,5 @@ function useQuery(query, params, options) {
|
|
|
227
227
|
return useQuery$1(query, params, options);
|
|
228
228
|
}
|
|
229
229
|
|
|
230
|
-
export { DEFAULT_API_VERSION, DEFAULT_CACHE_STRATEGY, Query, createSanityContext, useImageUrl, useImageUrlBuilder, useQuery, useSanityProviderValue };
|
|
230
|
+
export { DEFAULT_API_VERSION, DEFAULT_CACHE_STRATEGY, Query, createSanityContext, getPerspectiveFromUrl, useImageUrl, useImageUrlBuilder, useQuery, useSanityProviderValue };
|
|
231
231
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/constants.ts","../src/context.ts","../src/image.ts","../src/Query.tsx","../src/visual-editing/useQuery.tsx"],"sourcesContent":["import {CacheLong, type CachingStrategy} from '@shopify/hydrogen'\n\n/** Default Sanity API version with perspective stack support */\nexport const DEFAULT_API_VERSION = 'v2025-02-19'\n\n/** Default Hydrogen caching strategy for Sanity queries */\nexport const DEFAULT_CACHE_STRATEGY: CachingStrategy = CacheLong()\n","import {\n type Any,\n type ClientConfig,\n type ClientPerspective,\n type ClientReturn,\n createClient,\n type QueryParams,\n type QueryWithoutParams,\n type ResponseQueryOptions,\n SanityClient,\n} from '@sanity/client'\nimport type {QueryResponseInitial} from '@sanity/react-loader'\nimport {type CachingStrategy, createWithCache, type HydrogenSession} from '@shopify/hydrogen'\nimport {createElement, type PropsWithChildren, type ReactNode} from 'react'\n\nimport {DEFAULT_API_VERSION, DEFAULT_CACHE_STRATEGY} from './constants'\nimport type {SanityPreviewSession} from './preview/session'\nimport {isPreviewEnabled} from './preview/utils'\nimport {SanityProvider, type SanityProviderValue} from './provider'\nimport type {CacheActionFunctionParam, WaitUntil} from './types'\nimport {getPerspective} from './utils'\nimport {hashQuery, supportsPerspectiveStack} from './utils'\n\nlet didWarnAboutNoApiVersion = false\nlet didWarnAboutNoPerspectiveSupport = false\nlet didWarnAboutLoadQuery = false\nlet didInitializeLoader = false\n\nexport type CreateSanityContextOptions = {\n request: Request\n\n cache?: Cache | undefined\n waitUntil?: WaitUntil | undefined\n\n /**\n * Sanity client or configuration to use.\n */\n client: SanityClient | ClientConfig\n\n /**\n * The default caching strategy to use for `loadQuery` subrequests.\n * @see https://shopify.dev/docs/custom-storefronts/hydrogen/caching#caching-strategies\n *\n * Defaults to `CacheLong`\n */\n defaultStrategy?: CachingStrategy | null\n\n /**\n * Configuration for enabling preview mode.\n */\n preview?: {\n token: string\n session: SanityPreviewSession | HydrogenSession\n }\n}\n\ninterface RequestInit {\n hydrogen?: {\n /**\n * The caching strategy to use for the subrequest.\n * @see https://shopify.dev/docs/custom-storefronts/hydrogen/caching#caching-strategies\n */\n cache?: CachingStrategy\n\n /**\n * Optional debugging information to be displayed in the subrequest profiler.\n * @see https://shopify.dev/docs/custom-storefronts/hydrogen/debugging/subrequest-profiler#how-to-provide-more-debug-information-for-a-request\n */\n debug?: {\n displayName: string\n }\n }\n}\n\ntype HydrogenResponseQueryOptions = Omit<ResponseQueryOptions, 'next' | 'cache'> & {\n hydrogen?: 'hydrogen' extends keyof RequestInit ? RequestInit['hydrogen'] : never\n}\n\nexport type LoadQueryOptions<T> = Pick<\n HydrogenResponseQueryOptions,\n 'perspective' | 'hydrogen' | 'useCdn' | 'stega' | 'headers' | 'tag'\n> & {\n hydrogen?: {\n /**\n * The caching strategy to use for the subrequest.\n * @see https://shopify.dev/docs/custom-storefronts/hydrogen/caching#caching-strategies\n */\n cache?: CachingStrategy\n\n /**\n * Optional debugging information to be displayed in the subrequest profiler.\n * @see https://shopify.dev/docs/custom-storefronts/hydrogen/debugging/subrequest-profiler#how-to-provide-more-debug-information-for-a-request\n */\n debug?: {\n displayName: string\n }\n\n /**\n * Whether to cache the result of the query or not.\n * @defaultValue () => true\n */\n shouldCacheResult?: (value: QueryResponseInitial<T>) => boolean\n }\n}\n\nexport type FetchOptions<T> = HydrogenResponseQueryOptions & {\n hydrogen?: {\n /**\n * The caching strategy to use for the subrequest.\n * @see https://shopify.dev/docs/custom-storefronts/hydrogen/caching#caching-strategies\n */\n cache?: CachingStrategy\n\n /**\n * Optional debugging information to be displayed in the subrequest profiler.\n * @see https://shopify.dev/docs/custom-storefronts/hydrogen/debugging/subrequest-profiler#how-to-provide-more-debug-information-for-a-request\n */\n debug?: {\n displayName: string\n }\n\n /**\n * Whether to cache the result of the query or not.\n * @defaultValue () => true\n */\n shouldCacheResult?: (value: QueryResponseInitial<T>) => boolean\n }\n}\n\nexport interface SanityContext {\n /**\n * Query Sanity using the loader.\n * @see https://www.sanity.io/docs/loaders\n */\n loadQuery<Result = Any, Query extends string = string>(\n query: Query,\n params?: QueryParams | QueryWithoutParams,\n options?: LoadQueryOptions<ClientReturn<Query, Result>>,\n ): Promise<QueryResponseInitial<ClientReturn<Query, Result>>>\n\n /**\n * Query Sanity using direct client fetch with Hydrogen caching.\n * Use this when you need direct client results without react-loader integration.\n * Automatically disables caching in preview mode for real-time updates.\n */\n fetch<Result = Any, Query extends string = string>(\n query: Query,\n params?: QueryParams | QueryWithoutParams,\n options?: FetchOptions<Result>,\n ): Promise<ClientReturn<Query, Result>>\n\n /**\n * Conditionally query Sanity using either loadQuery (for preview mode) or fetch (for static mode).\n * This optimizes bundle size by only loading @sanity/react-loader dependencies when in preview mode.\n */\n query<Result = Any, Query extends string = string>(\n query: Query,\n params?: QueryParams | QueryWithoutParams,\n options?: LoadQueryOptions<ClientReturn<Query, Result>> & FetchOptions<Result>,\n ): Promise<QueryResponseInitial<ClientReturn<Query, Result>> | ClientReturn<Query, Result>>\n\n /**\n * The Sanity client, automatically configured for preview mode when enabled.\n * Uses preview token, perspective, and CDN settings based on session state.\n */\n client: SanityClient\n\n preview?: CreateSanityContextOptions['preview'] & {\n /**\n * Whether preview mode is currently enabled based on session detection\n */\n enabled: boolean\n }\n\n SanityProvider: (props: PropsWithChildren<object>) => ReactNode\n}\n\n/**\n * @public\n */\nexport async function createSanityContext(\n options: CreateSanityContextOptions,\n): Promise<SanityContext> {\n const {cache, waitUntil = () => Promise.resolve(), request, preview, defaultStrategy} = options\n const withCache = cache ? createWithCache({cache, waitUntil, request}) : null\n let client =\n options.client instanceof SanityClient ? options.client : createClient(options.client)\n\n if (client.config().apiVersion === '1') {\n if (process.env.NODE_ENV === 'development' && !didWarnAboutNoApiVersion) {\n console.warn(\n `\nNo API version specified, defaulting to \\`${DEFAULT_API_VERSION}\\` which supports perspectives and Content Releases.\nYou can find the latest version in the Sanity changelog: https://www.sanity.io/changelog.\n `.trim(),\n )\n\n didWarnAboutNoApiVersion = true\n }\n\n client = client.withConfig({apiVersion: DEFAULT_API_VERSION})\n }\n\n // Determine if preview is enabled and configure the client accordingly\n let previewEnabled = false\n if (preview) {\n if (!preview.token) {\n throw new Error('Enabling preview mode requires a token.')\n }\n\n previewEnabled = isPreviewEnabled(client.config().projectId!, preview.session)\n\n if (previewEnabled) {\n const apiVersion = client.config().apiVersion\n let perspective: ClientPerspective\n if (supportsPerspectiveStack(apiVersion)) {\n perspective = getPerspective(preview.session)\n } else {\n if (process.env.NODE_ENV === 'development' && !didWarnAboutNoPerspectiveSupport) {\n console.warn(\n `API version \\`${apiVersion}\\` does not support perspective stacks. Using \\`previewDrafts\\` perspective. Consider upgrading to \\`v2025-02-19\\` or later for full perspective support.`,\n )\n\n didWarnAboutNoPerspectiveSupport = true\n }\n perspective = 'previewDrafts'\n }\n\n client = client.withConfig({\n useCdn: false,\n token: preview.token,\n perspective,\n })\n }\n }\n\n // Server client will be initialized lazily on first loadQuery call\n const {apiHost, projectId, dataset, apiVersion} = client.config()\n const providerValue: SanityProviderValue = {\n projectId: projectId!,\n dataset: dataset!,\n apiHost,\n apiVersion: apiVersion!,\n previewEnabled,\n perspective: client.config().perspective || 'published',\n stegaEnabled: client.config().stega?.enabled ?? false,\n }\n\n return {\n /**\n * Loads a Sanity query with client-side loader support and Hydrogen cache integration.\n * Bypasses Hydrogen cache in preview mode.\n */\n async loadQuery<Result = Any, Query extends string = string>(\n query: Query,\n params: QueryParams | QueryWithoutParams,\n loaderOptions?: LoadQueryOptions<ClientReturn<Query, Result>>,\n ): Promise<QueryResponseInitial<ClientReturn<Query, Result>>> {\n // Lazy initialize the loader on first call with the configured client\n if (!didInitializeLoader) {\n const {setServerClient} = await import('@sanity/react-loader')\n setServerClient(client)\n didInitializeLoader = true\n }\n\n // Warn users to migrate to `query` method when using loadQuery outside preview mode\n if (!previewEnabled && process.env.NODE_ENV === 'development' && !didWarnAboutLoadQuery) {\n console.warn(\n `\\`loadQuery\\` is being called outside of preview mode. Consider using \\`query\\` instead, which automatically handles both preview and production modes efficiently, or use \\`fetch\\`. \\`loadQuery\\` is intended to be called conditionally in preview and visual editing contexts.`,\n )\n didWarnAboutLoadQuery = true\n }\n\n if (!withCache || previewEnabled) {\n const {loadQuery} = await import('@sanity/react-loader')\n return await loadQuery<ClientReturn<Query, Result>>(query, params, loaderOptions)\n }\n\n const cacheStrategy =\n loaderOptions?.hydrogen?.cache || defaultStrategy || DEFAULT_CACHE_STRATEGY\n const queryHash = await hashQuery(query, params)\n const shouldCacheResult = loaderOptions?.hydrogen?.shouldCacheResult ?? (() => true)\n\n return await withCache.run(\n {cacheKey: queryHash, cacheStrategy, shouldCacheResult},\n async ({\n addDebugData,\n }: CacheActionFunctionParam): Promise<\n QueryResponseInitial<ClientReturn<Query, Result>>\n > => {\n // Name displayed in the subrequest profiler\n const displayName = loaderOptions?.hydrogen?.debug?.displayName || 'query Sanity'\n\n addDebugData({\n displayName,\n })\n\n const {loadQuery} = await import('@sanity/react-loader')\n return await loadQuery<ClientReturn<Query, Result>>(query, params, loaderOptions)\n },\n )\n },\n\n /**\n * Executes a Sanity query with Hydrogen cache integration.\n * Direct client fetch without loader integration. Bypasses cache in preview mode.\n */\n async fetch<Result = Any, Query extends string = string>(\n query: Query,\n params: QueryParams | QueryWithoutParams = {},\n fetchOptions?: Pick<\n LoadQueryOptions<Result>,\n 'perspective' | 'hydrogen' | 'useCdn' | 'headers' | 'tag'\n >,\n ): Promise<ClientReturn<Query, Result>> {\n if (!withCache || previewEnabled) {\n return await client.fetch<ClientReturn<Query, Result>>(query, params, fetchOptions)\n }\n\n const cacheStrategy =\n fetchOptions?.hydrogen?.cache || defaultStrategy || DEFAULT_CACHE_STRATEGY\n const queryHash = await hashQuery(query, params)\n\n return await withCache.run(\n {cacheKey: queryHash, cacheStrategy, shouldCacheResult: () => true},\n async ({addDebugData}: CacheActionFunctionParam): Promise<ClientReturn<Query, Result>> => {\n // Name displayed in the subrequest profiler\n const displayName = fetchOptions?.hydrogen?.debug?.displayName || 'fetch Sanity'\n\n addDebugData({\n displayName,\n })\n\n return await client.fetch<ClientReturn<Query, Result>>(query, params, fetchOptions)\n },\n )\n },\n\n /**\n * Automatic query method that automatically adapts based on preview mode state.\n * Uses `loadQuery` (with client-side loader integration) when preview is enabled, `fetch` otherwise.\n * Bypasses cache in preview mode.\n */\n async query<Result = Any, Query extends string = string>(\n query: Query,\n params?: QueryParams | QueryWithoutParams,\n queryOptions?: LoadQueryOptions<ClientReturn<Query, Result>> & FetchOptions<Result>,\n ): Promise<QueryResponseInitial<ClientReturn<Query, Result>> | ClientReturn<Query, Result>> {\n return await (previewEnabled ? this.loadQuery : this.fetch)(query, params, queryOptions)\n },\n\n /** The configured Sanity client instance */\n client,\n\n /** Preview configuration with session-based state, undefined when preview is not configured */\n preview: preview ? {...preview, enabled: previewEnabled} : undefined,\n\n /**\n * React Provider component that serializes Sanity configuration across server-client boundary.\n */\n SanityProvider({children}: PropsWithChildren<object>) {\n return createElement(\n SanityProvider,\n {\n value: Object.freeze(providerValue),\n },\n children,\n )\n },\n } satisfies SanityContext\n}\n","import type {ImageUrlBuilder, SanityImageSource, SanityModernClientLike} from '@sanity/image-url'\nimport {createImageUrlBuilder} from '@sanity/image-url'\nimport {useMemo} from 'react'\n\nimport {useSanityProviderValue} from './provider'\n\n/**\n * Hook that returns a Sanity image URL builder configured with current provider settings.\n * Use this to create custom image transformations beyond `useImageUrl`.\n */\nexport function useImageUrlBuilder(): ImageUrlBuilder {\n const {projectId, dataset, apiHost} = useSanityProviderValue()\n return useMemo(() => {\n return createImageUrlBuilder({\n config: () => ({projectId, dataset, apiHost}),\n } as SanityModernClientLike)\n }, [apiHost, dataset, projectId])\n}\n\n/**\n * Hook that generates image URLs from Sanity image assets.\n * Returns a configured image URL builder for the given source.\n */\nexport function useImageUrl(source: SanityImageSource): ImageUrlBuilder {\n const builder = useImageUrlBuilder()\n return builder.image(source)\n}\n\nexport type * from '@sanity/image-url'\n","import type {Any, ClientReturn, QueryParams, QueryWithoutParams} from '@sanity/client'\nimport type {EncodeDataAttributeFunction} from '@sanity/core-loader/encode-data-attribute'\nimport type {QueryResponseInitial} from '@sanity/react-loader'\nimport {lazy, type ReactNode, Suspense, type SuspenseProps, useSyncExternalStore} from 'react'\n\nimport type {LoadQueryOptions} from './context'\nimport {usePreviewMode} from './preview/hooks'\nimport type {QueryClientProps} from './Query.client'\nimport {isServer} from './utils'\n\n/**\n * Fallback component that renders nothing, preventing hydration mismatches.\n */\nfunction SanityQueryFallback(): ReactNode {\n return null\n}\n\n/**\n * Simple hydration store to avoid hydration mismatches.\n * Returns false on server, true on client after hydration.\n */\nfunction useIsHydrated(): boolean {\n return useSyncExternalStore(\n // eslint-disable-next-line no-empty-function\n () => () => {},\n () => true,\n () => false,\n )\n}\n\nconst QueryClient = isServer()\n ? SanityQueryFallback\n : (lazy(\n () =>\n /**\n * `lazy` expects the component as the default export\n * @see https://react.dev/reference/react/lazy\n */\n import('./Query.client'),\n ) as <Result = Any, Query extends string = string>(\n props: QueryClientProps<Result, Query>,\n ) => ReactNode)\n\nconst noopEncodeDataAttribute: EncodeDataAttributeFunction = Object.assign(() => undefined, {\n scope: () => noopEncodeDataAttribute,\n})\n\nexport interface QueryProps<Result = Any, Query extends string = string> extends Omit<\n QueryClientProps<Result, Query>,\n 'options'\n> {\n query: Query\n params?: QueryParams | QueryWithoutParams\n options: {\n initial: ClientReturn<Query, Result> | QueryResponseInitial<ClientReturn<Query, Result>>\n } & LoadQueryOptions<ClientReturn<Query, Result>>\n children: (\n data: ClientReturn<Query, Result>,\n encodeDataAttribute: EncodeDataAttributeFunction,\n ) => ReactNode\n}\n\n/**\n * Query component that provides live updates in preview mode and static data otherwise.\n *\n * @public\n */\nexport function Query<Result = Any, Query extends string = string>({\n query,\n params,\n options,\n children,\n ...suspenseProps\n}: QueryProps<Result, Query> & Omit<SuspenseProps, 'children'>): ReactNode {\n const isPreviewMode = usePreviewMode()\n const isHydrated = useIsHydrated()\n\n // If in preview mode and hydrated, render the client component\n if (isPreviewMode && isHydrated) {\n return (\n <Suspense {...suspenseProps} fallback={suspenseProps.fallback ?? <SanityQueryFallback />}>\n <QueryClient<Result, Query>\n query={query}\n params={params}\n options={options as QueryClientProps<Result, Query>['options']}\n >\n {children}\n </QueryClient>\n </Suspense>\n )\n }\n\n // Render static data in non-preview mode or during hydration\n return children(options.initial as ClientReturn<Query, Result>, noopEncodeDataAttribute)\n}\n","import {useQuery as _useQuery, type UseQueryOptionsDefinedInitial} from '@sanity/react-loader'\nimport {useEffect, useId} from 'react'\n\nimport {registerQuery} from './registry'\n\n/**\n * Automatically registers with the query detection system.\n * This enables automatic live mode detection in `VisualEditing` components.\n */\nexport function useQuery<QueryResponseResult = unknown>(\n query: string,\n params?: Record<string, unknown>,\n options?: UseQueryOptionsDefinedInitial<QueryResponseResult>,\n): ReturnType<typeof _useQuery<QueryResponseResult>> {\n // Generate stable ID for this `useQuery` instance\n const id = useId()\n\n // Register this `useQuery` instance with the detection system\n useEffect(() => {\n const unregister = registerQuery(id)\n return unregister\n }, [id])\n\n // Call the original `useQuery` with all the same arguments\n return _useQuery<QueryResponseResult>(query, params, options)\n}\n"],"names":["apiVersion","_useQuery"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/constants.ts","../src/context.ts","../src/image.ts","../src/Query.tsx","../src/visual-editing/useQuery.tsx"],"sourcesContent":["import {CacheLong, type CachingStrategy} from '@shopify/hydrogen'\n\n/** Default Sanity API version with perspective stack support */\nexport const DEFAULT_API_VERSION = 'v2025-02-19'\n\n/** Default Hydrogen caching strategy for Sanity queries */\nexport const DEFAULT_CACHE_STRATEGY: CachingStrategy = CacheLong()\n","import {\n type Any,\n type ClientConfig,\n type ClientPerspective,\n type ClientReturn,\n createClient,\n type QueryParams,\n type QueryWithoutParams,\n type ResponseQueryOptions,\n SanityClient,\n} from '@sanity/client'\nimport type {QueryResponseInitial} from '@sanity/react-loader'\nimport {type CachingStrategy, createWithCache, type HydrogenSession} from '@shopify/hydrogen'\nimport {createElement, type PropsWithChildren, type ReactNode} from 'react'\n\nimport {DEFAULT_API_VERSION, DEFAULT_CACHE_STRATEGY} from './constants'\nimport type {SanityPreviewSession} from './preview/session'\nimport {isPreviewEnabled} from './preview/utils'\nimport {SanityProvider, type SanityProviderValue} from './provider'\nimport type {CacheActionFunctionParam, WaitUntil} from './types'\nimport {getPerspective, getPerspectiveFromUrl} from './utils'\nimport {hashQuery, supportsPerspectiveStack} from './utils'\n\nlet didWarnAboutNoApiVersion = false\nlet didWarnAboutNoPerspectiveSupport = false\nlet didWarnAboutLoadQuery = false\n\nexport type CreateSanityContextOptions = {\n request: Request\n\n cache?: Cache | undefined\n waitUntil?: WaitUntil | undefined\n\n /**\n * Sanity client or configuration to use.\n */\n client: SanityClient | ClientConfig\n\n /**\n * The default caching strategy to use for `loadQuery` subrequests.\n * @see https://shopify.dev/docs/custom-storefronts/hydrogen/caching#caching-strategies\n *\n * Defaults to `CacheLong`\n */\n defaultStrategy?: CachingStrategy | null\n\n /**\n * Configuration for enabling preview mode.\n */\n preview?: {\n token: string\n session: SanityPreviewSession | HydrogenSession\n }\n}\n\ninterface RequestInit {\n hydrogen?: {\n /**\n * The caching strategy to use for the subrequest.\n * @see https://shopify.dev/docs/custom-storefronts/hydrogen/caching#caching-strategies\n */\n cache?: CachingStrategy\n\n /**\n * Optional debugging information to be displayed in the subrequest profiler.\n * @see https://shopify.dev/docs/custom-storefronts/hydrogen/debugging/subrequest-profiler#how-to-provide-more-debug-information-for-a-request\n */\n debug?: {\n displayName: string\n }\n }\n}\n\ntype HydrogenResponseQueryOptions = Omit<ResponseQueryOptions, 'next' | 'cache'> & {\n hydrogen?: 'hydrogen' extends keyof RequestInit ? RequestInit['hydrogen'] : never\n}\n\nexport type LoadQueryOptions<T> = Pick<\n HydrogenResponseQueryOptions,\n 'perspective' | 'hydrogen' | 'useCdn' | 'stega' | 'headers' | 'tag'\n> & {\n hydrogen?: {\n /**\n * The caching strategy to use for the subrequest.\n * @see https://shopify.dev/docs/custom-storefronts/hydrogen/caching#caching-strategies\n */\n cache?: CachingStrategy\n\n /**\n * Optional debugging information to be displayed in the subrequest profiler.\n * @see https://shopify.dev/docs/custom-storefronts/hydrogen/debugging/subrequest-profiler#how-to-provide-more-debug-information-for-a-request\n */\n debug?: {\n displayName: string\n }\n\n /**\n * Whether to cache the result of the query or not.\n * @defaultValue () => true\n */\n shouldCacheResult?: (value: QueryResponseInitial<T>) => boolean\n }\n}\n\nexport type FetchOptions<T> = HydrogenResponseQueryOptions & {\n hydrogen?: {\n /**\n * The caching strategy to use for the subrequest.\n * @see https://shopify.dev/docs/custom-storefronts/hydrogen/caching#caching-strategies\n */\n cache?: CachingStrategy\n\n /**\n * Optional debugging information to be displayed in the subrequest profiler.\n * @see https://shopify.dev/docs/custom-storefronts/hydrogen/debugging/subrequest-profiler#how-to-provide-more-debug-information-for-a-request\n */\n debug?: {\n displayName: string\n }\n\n /**\n * Whether to cache the result of the query or not.\n * @defaultValue () => true\n */\n shouldCacheResult?: (value: QueryResponseInitial<T>) => boolean\n }\n}\n\nexport interface SanityContext {\n /**\n * Query Sanity using the loader.\n * @see https://www.sanity.io/docs/loaders\n */\n loadQuery<Result = Any, Query extends string = string>(\n query: Query,\n params?: QueryParams | QueryWithoutParams,\n options?: LoadQueryOptions<ClientReturn<Query, Result>>,\n ): Promise<QueryResponseInitial<ClientReturn<Query, Result>>>\n\n /**\n * Query Sanity using direct client fetch with Hydrogen caching.\n * Use this when you need direct client results without react-loader integration.\n * Automatically disables caching in preview mode for real-time updates.\n */\n fetch<Result = Any, Query extends string = string>(\n query: Query,\n params?: QueryParams | QueryWithoutParams,\n options?: FetchOptions<Result>,\n ): Promise<ClientReturn<Query, Result>>\n\n /**\n * Conditionally query Sanity using either loadQuery (for preview mode) or fetch (for static mode).\n * This optimizes bundle size by only loading @sanity/react-loader dependencies when in preview mode.\n */\n query<Result = Any, Query extends string = string>(\n query: Query,\n params?: QueryParams | QueryWithoutParams,\n options?: LoadQueryOptions<ClientReturn<Query, Result>> & FetchOptions<Result>,\n ): Promise<QueryResponseInitial<ClientReturn<Query, Result>> | ClientReturn<Query, Result>>\n\n /**\n * The Sanity client, automatically configured for preview mode when enabled.\n * Uses preview token, perspective, and CDN settings based on session state.\n */\n client: SanityClient\n\n preview?: CreateSanityContextOptions['preview'] & {\n /**\n * Whether preview mode is currently enabled based on session detection\n */\n enabled: boolean\n }\n\n SanityProvider: (props: PropsWithChildren<object>) => ReactNode\n}\n\n/**\n * @public\n */\nexport async function createSanityContext(\n options: CreateSanityContextOptions,\n): Promise<SanityContext> {\n const {cache, waitUntil = () => Promise.resolve(), request, preview, defaultStrategy} = options\n const withCache = cache ? createWithCache({cache, waitUntil, request}) : null\n let client =\n options.client instanceof SanityClient ? options.client : createClient(options.client)\n\n if (client.config().apiVersion === '1') {\n if (process.env.NODE_ENV === 'development' && !didWarnAboutNoApiVersion) {\n console.warn(\n `\nNo API version specified, defaulting to \\`${DEFAULT_API_VERSION}\\` which supports perspectives and Content Releases.\nYou can find the latest version in the Sanity changelog: https://www.sanity.io/changelog.\n `.trim(),\n )\n\n didWarnAboutNoApiVersion = true\n }\n\n client = client.withConfig({apiVersion: DEFAULT_API_VERSION})\n }\n\n // Determine if preview is enabled and configure the client accordingly\n let previewEnabled = false\n if (preview) {\n if (!preview.token) {\n throw new Error('Enabling preview mode requires a token.')\n }\n\n previewEnabled = isPreviewEnabled(client.config().projectId!, preview.session)\n\n if (previewEnabled) {\n const apiVersion = client.config().apiVersion\n let perspective: ClientPerspective\n\n // Prefer URL param over session — the cookie may lag behind the iframe reload.\n const urlPerspective = getPerspectiveFromUrl(request.url)\n\n if (\n urlPerspective !== undefined &&\n !(Array.isArray(urlPerspective) && !supportsPerspectiveStack(apiVersion))\n ) {\n perspective = urlPerspective\n } else if (supportsPerspectiveStack(apiVersion)) {\n perspective = getPerspective(preview.session)\n } else {\n if (process.env.NODE_ENV === 'development' && !didWarnAboutNoPerspectiveSupport) {\n console.warn(\n `API version \\`${apiVersion}\\` does not support perspective stacks. Using \\`previewDrafts\\` perspective. Consider upgrading to \\`v2025-02-19\\` or later for full perspective support.`,\n )\n\n didWarnAboutNoPerspectiveSupport = true\n }\n perspective = 'previewDrafts'\n }\n\n client = client.withConfig({\n useCdn: false,\n token: preview.token,\n perspective,\n })\n }\n }\n\n // Server client will be initialized lazily on first loadQuery call\n const {apiHost, projectId, dataset, apiVersion} = client.config()\n const providerValue: SanityProviderValue = {\n projectId: projectId!,\n dataset: dataset!,\n apiHost,\n apiVersion: apiVersion!,\n previewEnabled,\n perspective: client.config().perspective || 'published',\n stegaEnabled: client.config().stega?.enabled ?? false,\n }\n\n return {\n /**\n * Loads a Sanity query with client-side loader support and Hydrogen cache integration.\n * Bypasses Hydrogen cache in preview mode.\n */\n async loadQuery<Result = Any, Query extends string = string>(\n query: Query,\n params: QueryParams | QueryWithoutParams,\n loaderOptions?: LoadQueryOptions<ClientReturn<Query, Result>>,\n ): Promise<QueryResponseInitial<ClientReturn<Query, Result>>> {\n const {setServerClient} = await import('@sanity/react-loader')\n setServerClient(client)\n\n // Warn users to migrate to `query` method when using loadQuery outside preview mode\n if (!previewEnabled && process.env.NODE_ENV === 'development' && !didWarnAboutLoadQuery) {\n console.warn(\n `\\`loadQuery\\` is being called outside of preview mode. Consider using \\`query\\` instead, which automatically handles both preview and production modes efficiently, or use \\`fetch\\`. \\`loadQuery\\` is intended to be called conditionally in preview and visual editing contexts.`,\n )\n didWarnAboutLoadQuery = true\n }\n\n if (!withCache || previewEnabled) {\n const {loadQuery} = await import('@sanity/react-loader')\n // Override the singleton's possibly-stale perspective with the per-request value.\n const resolvedOptions =\n previewEnabled && !loaderOptions?.perspective\n ? {...loaderOptions, perspective: client.config().perspective as ClientPerspective}\n : loaderOptions\n return await loadQuery<ClientReturn<Query, Result>>(query, params, resolvedOptions)\n }\n\n const cacheStrategy =\n loaderOptions?.hydrogen?.cache || defaultStrategy || DEFAULT_CACHE_STRATEGY\n const queryHash = await hashQuery(query, params)\n const shouldCacheResult = loaderOptions?.hydrogen?.shouldCacheResult ?? (() => true)\n\n return await withCache.run(\n {cacheKey: queryHash, cacheStrategy, shouldCacheResult},\n async ({\n addDebugData,\n }: CacheActionFunctionParam): Promise<\n QueryResponseInitial<ClientReturn<Query, Result>>\n > => {\n // Name displayed in the subrequest profiler\n const displayName = loaderOptions?.hydrogen?.debug?.displayName || 'query Sanity'\n\n addDebugData({\n displayName,\n })\n\n const {loadQuery} = await import('@sanity/react-loader')\n return await loadQuery<ClientReturn<Query, Result>>(query, params, loaderOptions)\n },\n )\n },\n\n /**\n * Executes a Sanity query with Hydrogen cache integration.\n * Direct client fetch without loader integration. Bypasses cache in preview mode.\n */\n async fetch<Result = Any, Query extends string = string>(\n query: Query,\n params: QueryParams | QueryWithoutParams = {},\n fetchOptions?: Pick<\n LoadQueryOptions<Result>,\n 'perspective' | 'hydrogen' | 'useCdn' | 'headers' | 'tag'\n >,\n ): Promise<ClientReturn<Query, Result>> {\n if (!withCache || previewEnabled) {\n return await client.fetch<ClientReturn<Query, Result>>(query, params, fetchOptions)\n }\n\n const cacheStrategy =\n fetchOptions?.hydrogen?.cache || defaultStrategy || DEFAULT_CACHE_STRATEGY\n const queryHash = await hashQuery(query, params)\n\n return await withCache.run(\n {cacheKey: queryHash, cacheStrategy, shouldCacheResult: () => true},\n async ({addDebugData}: CacheActionFunctionParam): Promise<ClientReturn<Query, Result>> => {\n // Name displayed in the subrequest profiler\n const displayName = fetchOptions?.hydrogen?.debug?.displayName || 'fetch Sanity'\n\n addDebugData({\n displayName,\n })\n\n return await client.fetch<ClientReturn<Query, Result>>(query, params, fetchOptions)\n },\n )\n },\n\n /**\n * Automatic query method that automatically adapts based on preview mode state.\n * Uses `loadQuery` (with client-side loader integration) when preview is enabled, `fetch` otherwise.\n * Bypasses cache in preview mode.\n */\n async query<Result = Any, Query extends string = string>(\n query: Query,\n params?: QueryParams | QueryWithoutParams,\n queryOptions?: LoadQueryOptions<ClientReturn<Query, Result>> & FetchOptions<Result>,\n ): Promise<QueryResponseInitial<ClientReturn<Query, Result>> | ClientReturn<Query, Result>> {\n return await (previewEnabled ? this.loadQuery : this.fetch)(query, params, queryOptions)\n },\n\n /** The configured Sanity client instance */\n client,\n\n /** Preview configuration with session-based state, undefined when preview is not configured */\n preview: preview ? {...preview, enabled: previewEnabled} : undefined,\n\n /**\n * React Provider component that serializes Sanity configuration across server-client boundary.\n */\n SanityProvider({children}: PropsWithChildren<object>) {\n return createElement(\n SanityProvider,\n {\n value: Object.freeze(providerValue),\n },\n children,\n )\n },\n } satisfies SanityContext\n}\n","import type {ImageUrlBuilder, SanityImageSource, SanityModernClientLike} from '@sanity/image-url'\nimport {createImageUrlBuilder} from '@sanity/image-url'\nimport {useMemo} from 'react'\n\nimport {useSanityProviderValue} from './provider'\n\n/**\n * Hook that returns a Sanity image URL builder configured with current provider settings.\n * Use this to create custom image transformations beyond `useImageUrl`.\n */\nexport function useImageUrlBuilder(): ImageUrlBuilder {\n const {projectId, dataset, apiHost} = useSanityProviderValue()\n return useMemo(() => {\n return createImageUrlBuilder({\n config: () => ({projectId, dataset, apiHost}),\n } as SanityModernClientLike)\n }, [apiHost, dataset, projectId])\n}\n\n/**\n * Hook that generates image URLs from Sanity image assets.\n * Returns a configured image URL builder for the given source.\n */\nexport function useImageUrl(source: SanityImageSource): ImageUrlBuilder {\n const builder = useImageUrlBuilder()\n return builder.image(source)\n}\n\nexport type * from '@sanity/image-url'\n","import type {Any, ClientReturn, QueryParams, QueryWithoutParams} from '@sanity/client'\nimport type {EncodeDataAttributeFunction} from '@sanity/core-loader/encode-data-attribute'\nimport type {QueryResponseInitial} from '@sanity/react-loader'\nimport {lazy, type ReactNode, Suspense, type SuspenseProps, useSyncExternalStore} from 'react'\n\nimport type {LoadQueryOptions} from './context'\nimport {usePreviewMode} from './preview/hooks'\nimport type {QueryClientProps} from './Query.client'\nimport {isServer} from './utils'\n\n/**\n * Fallback component that renders nothing, preventing hydration mismatches.\n */\nfunction SanityQueryFallback(): ReactNode {\n return null\n}\n\n/**\n * Simple hydration store to avoid hydration mismatches.\n * Returns false on server, true on client after hydration.\n */\nfunction useIsHydrated(): boolean {\n return useSyncExternalStore(\n // eslint-disable-next-line no-empty-function\n () => () => {},\n () => true,\n () => false,\n )\n}\n\nconst QueryClient = isServer()\n ? SanityQueryFallback\n : (lazy(\n () =>\n /**\n * `lazy` expects the component as the default export\n * @see https://react.dev/reference/react/lazy\n */\n import('./Query.client'),\n ) as <Result = Any, Query extends string = string>(\n props: QueryClientProps<Result, Query>,\n ) => ReactNode)\n\nconst noopEncodeDataAttribute: EncodeDataAttributeFunction = Object.assign(() => undefined, {\n scope: () => noopEncodeDataAttribute,\n})\n\nexport interface QueryProps<Result = Any, Query extends string = string> extends Omit<\n QueryClientProps<Result, Query>,\n 'options'\n> {\n query: Query\n params?: QueryParams | QueryWithoutParams\n options: {\n initial: ClientReturn<Query, Result> | QueryResponseInitial<ClientReturn<Query, Result>>\n } & LoadQueryOptions<ClientReturn<Query, Result>>\n children: (\n data: ClientReturn<Query, Result>,\n encodeDataAttribute: EncodeDataAttributeFunction,\n ) => ReactNode\n}\n\n/**\n * Query component that provides live updates in preview mode and static data otherwise.\n *\n * @public\n */\nexport function Query<Result = Any, Query extends string = string>({\n query,\n params,\n options,\n children,\n ...suspenseProps\n}: QueryProps<Result, Query> & Omit<SuspenseProps, 'children'>): ReactNode {\n const isPreviewMode = usePreviewMode()\n const isHydrated = useIsHydrated()\n\n // If in preview mode and hydrated, render the client component\n if (isPreviewMode && isHydrated) {\n return (\n <Suspense {...suspenseProps} fallback={suspenseProps.fallback ?? <SanityQueryFallback />}>\n <QueryClient<Result, Query>\n query={query}\n params={params}\n options={options as QueryClientProps<Result, Query>['options']}\n >\n {children}\n </QueryClient>\n </Suspense>\n )\n }\n\n // Render static data in non-preview mode or during hydration\n return children(options.initial as ClientReturn<Query, Result>, noopEncodeDataAttribute)\n}\n","import {useQuery as _useQuery, type UseQueryOptionsDefinedInitial} from '@sanity/react-loader'\nimport {useEffect, useId} from 'react'\n\nimport {registerQuery} from './registry'\n\n/**\n * Automatically registers with the query detection system.\n * This enables automatic live mode detection in `VisualEditing` components.\n */\nexport function useQuery<QueryResponseResult = unknown>(\n query: string,\n params?: Record<string, unknown>,\n options?: UseQueryOptionsDefinedInitial<QueryResponseResult>,\n): ReturnType<typeof _useQuery<QueryResponseResult>> {\n // Generate stable ID for this `useQuery` instance\n const id = useId()\n\n // Register this `useQuery` instance with the detection system\n useEffect(() => {\n const unregister = registerQuery(id)\n return unregister\n }, [id])\n\n // Call the original `useQuery` with all the same arguments\n return _useQuery<QueryResponseResult>(query, params, options)\n}\n"],"names":["apiVersion","_useQuery"],"mappings":"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"}
|
package/package.json
CHANGED
package/src/context.test.ts
CHANGED
|
@@ -439,6 +439,131 @@ describe('stegaEnabled serialization', () => {
|
|
|
439
439
|
})
|
|
440
440
|
})
|
|
441
441
|
|
|
442
|
+
describe('perspective resolution priority', () => {
|
|
443
|
+
beforeEach(() => {
|
|
444
|
+
vi.clearAllMocks()
|
|
445
|
+
})
|
|
446
|
+
|
|
447
|
+
it('should use URL param perspective over session value', async () => {
|
|
448
|
+
const previewSession = new PreviewSession()
|
|
449
|
+
previewSession.set('projectId', projectId)
|
|
450
|
+
previewSession.set('perspective', 'drafts')
|
|
451
|
+
|
|
452
|
+
const request = new Request('https://example.com/?sanity-preview-perspective=releaseId,drafts')
|
|
453
|
+
|
|
454
|
+
const context = await createSanityContext({
|
|
455
|
+
request,
|
|
456
|
+
cache,
|
|
457
|
+
client,
|
|
458
|
+
preview: {
|
|
459
|
+
token: 'my-token',
|
|
460
|
+
session: previewSession,
|
|
461
|
+
},
|
|
462
|
+
})
|
|
463
|
+
|
|
464
|
+
expect(context.client.config().perspective).toEqual(['releaseId', 'drafts'])
|
|
465
|
+
})
|
|
466
|
+
|
|
467
|
+
it('should fall back to session perspective when URL param is absent', async () => {
|
|
468
|
+
const previewSession = new PreviewSession()
|
|
469
|
+
previewSession.set('projectId', projectId)
|
|
470
|
+
previewSession.set('perspective', 'drafts')
|
|
471
|
+
|
|
472
|
+
const request = new Request('https://example.com/')
|
|
473
|
+
|
|
474
|
+
const context = await createSanityContext({
|
|
475
|
+
request,
|
|
476
|
+
cache,
|
|
477
|
+
client,
|
|
478
|
+
preview: {
|
|
479
|
+
token: 'my-token',
|
|
480
|
+
session: previewSession,
|
|
481
|
+
},
|
|
482
|
+
})
|
|
483
|
+
|
|
484
|
+
expect(context.client.config().perspective).toEqual(['drafts'])
|
|
485
|
+
})
|
|
486
|
+
|
|
487
|
+
it('should pass perspective explicitly to loadQuery in preview mode', async () => {
|
|
488
|
+
const previewSession = new PreviewSession()
|
|
489
|
+
previewSession.set('projectId', projectId)
|
|
490
|
+
previewSession.set('perspective', 'drafts')
|
|
491
|
+
|
|
492
|
+
const request = new Request('https://example.com/?sanity-preview-perspective=releaseId,drafts')
|
|
493
|
+
|
|
494
|
+
const context = await createSanityContext({
|
|
495
|
+
request,
|
|
496
|
+
cache,
|
|
497
|
+
client,
|
|
498
|
+
preview: {
|
|
499
|
+
token: 'my-token',
|
|
500
|
+
session: previewSession,
|
|
501
|
+
},
|
|
502
|
+
})
|
|
503
|
+
|
|
504
|
+
await context.loadQuery<boolean>(query, params)
|
|
505
|
+
|
|
506
|
+
expect(loadQuery).toHaveBeenCalledWith(
|
|
507
|
+
query,
|
|
508
|
+
params,
|
|
509
|
+
expect.objectContaining({
|
|
510
|
+
perspective: ['releaseId', 'drafts'],
|
|
511
|
+
}),
|
|
512
|
+
)
|
|
513
|
+
})
|
|
514
|
+
|
|
515
|
+
it('should ignore URL param perspective stack when API version is too old', async () => {
|
|
516
|
+
const previewSession = new PreviewSession()
|
|
517
|
+
previewSession.set('projectId', projectId)
|
|
518
|
+
|
|
519
|
+
const oldClient = createClient({
|
|
520
|
+
projectId,
|
|
521
|
+
dataset: 'my-dataset',
|
|
522
|
+
apiVersion: '2024-01-01',
|
|
523
|
+
})
|
|
524
|
+
|
|
525
|
+
const request = new Request('https://example.com/?sanity-preview-perspective=releaseId,drafts')
|
|
526
|
+
|
|
527
|
+
const context = await createSanityContext({
|
|
528
|
+
request,
|
|
529
|
+
cache,
|
|
530
|
+
client: oldClient,
|
|
531
|
+
preview: {
|
|
532
|
+
token: 'my-token',
|
|
533
|
+
session: previewSession,
|
|
534
|
+
},
|
|
535
|
+
})
|
|
536
|
+
|
|
537
|
+
// Should fall back to previewDrafts since API version doesn't support stacks
|
|
538
|
+
expect(context.client.config().perspective).toBe('previewDrafts')
|
|
539
|
+
})
|
|
540
|
+
|
|
541
|
+
it('should accept single URL param perspective even with old API version', async () => {
|
|
542
|
+
const previewSession = new PreviewSession()
|
|
543
|
+
previewSession.set('projectId', projectId)
|
|
544
|
+
|
|
545
|
+
const oldClient = createClient({
|
|
546
|
+
projectId,
|
|
547
|
+
dataset: 'my-dataset',
|
|
548
|
+
apiVersion: '2024-01-01',
|
|
549
|
+
})
|
|
550
|
+
|
|
551
|
+
const request = new Request('https://example.com/?sanity-preview-perspective=drafts')
|
|
552
|
+
|
|
553
|
+
const context = await createSanityContext({
|
|
554
|
+
request,
|
|
555
|
+
cache,
|
|
556
|
+
client: oldClient,
|
|
557
|
+
preview: {
|
|
558
|
+
token: 'my-token',
|
|
559
|
+
session: previewSession,
|
|
560
|
+
},
|
|
561
|
+
})
|
|
562
|
+
|
|
563
|
+
expect(context.client.config().perspective).toBe('drafts')
|
|
564
|
+
})
|
|
565
|
+
})
|
|
566
|
+
|
|
442
567
|
describe('lazy-initialize loaders', () => {
|
|
443
568
|
const request = new Request('https://example.com')
|
|
444
569
|
|
|
@@ -456,22 +581,21 @@ describe('lazy-initialize loaders', () => {
|
|
|
456
581
|
expect(setServerClient).not.toHaveBeenCalled()
|
|
457
582
|
})
|
|
458
583
|
|
|
459
|
-
it('should
|
|
584
|
+
it('should call `setServerClient` on every `loadQuery` invocation', async () => {
|
|
460
585
|
const context = await createSanityContext({
|
|
461
586
|
request,
|
|
462
587
|
cache,
|
|
463
588
|
client,
|
|
464
589
|
})
|
|
465
590
|
|
|
466
|
-
// loadQuery should work in non-preview mode (backwards compatibility)
|
|
467
|
-
// The actual loadQuery function will be called from the mocked module
|
|
468
591
|
await context.loadQuery<boolean>(query, params)
|
|
592
|
+
expect(setServerClient).toHaveBeenCalledTimes(1)
|
|
469
593
|
|
|
470
|
-
|
|
471
|
-
expect(
|
|
594
|
+
await context.loadQuery<boolean>(query, params)
|
|
595
|
+
expect(setServerClient).toHaveBeenCalledTimes(2)
|
|
472
596
|
})
|
|
473
597
|
|
|
474
|
-
it('should call `setServerClient`
|
|
598
|
+
it('should call `setServerClient` with the preview-configured client', async () => {
|
|
475
599
|
const previewSession = new PreviewSession()
|
|
476
600
|
previewSession.set('projectId', projectId)
|
|
477
601
|
|
|
@@ -485,17 +609,11 @@ describe('lazy-initialize loaders', () => {
|
|
|
485
609
|
},
|
|
486
610
|
})
|
|
487
611
|
|
|
488
|
-
// First call to `loadQuery`
|
|
489
612
|
await context.loadQuery<boolean>(query, params)
|
|
490
613
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
if (latestCall) {
|
|
495
|
-
const calledWithClient = latestCall[0]
|
|
496
|
-
expect(calledWithClient.config().useCdn).toBe(false)
|
|
497
|
-
expect(calledWithClient.config().token).toBe('my-token')
|
|
498
|
-
}
|
|
614
|
+
const calledWithClient = setServerClient.mock.calls[0][0]
|
|
615
|
+
expect(calledWithClient.config().useCdn).toBe(false)
|
|
616
|
+
expect(calledWithClient.config().token).toBe('my-token')
|
|
499
617
|
})
|
|
500
618
|
|
|
501
619
|
it('should display warning when `loadQuery` called outside preview mode in development', async () => {
|
package/src/context.ts
CHANGED
|
@@ -18,13 +18,12 @@ import type {SanityPreviewSession} from './preview/session'
|
|
|
18
18
|
import {isPreviewEnabled} from './preview/utils'
|
|
19
19
|
import {SanityProvider, type SanityProviderValue} from './provider'
|
|
20
20
|
import type {CacheActionFunctionParam, WaitUntil} from './types'
|
|
21
|
-
import {getPerspective} from './utils'
|
|
21
|
+
import {getPerspective, getPerspectiveFromUrl} from './utils'
|
|
22
22
|
import {hashQuery, supportsPerspectiveStack} from './utils'
|
|
23
23
|
|
|
24
24
|
let didWarnAboutNoApiVersion = false
|
|
25
25
|
let didWarnAboutNoPerspectiveSupport = false
|
|
26
26
|
let didWarnAboutLoadQuery = false
|
|
27
|
-
let didInitializeLoader = false
|
|
28
27
|
|
|
29
28
|
export type CreateSanityContextOptions = {
|
|
30
29
|
request: Request
|
|
@@ -213,7 +212,16 @@ You can find the latest version in the Sanity changelog: https://www.sanity.io/c
|
|
|
213
212
|
if (previewEnabled) {
|
|
214
213
|
const apiVersion = client.config().apiVersion
|
|
215
214
|
let perspective: ClientPerspective
|
|
216
|
-
|
|
215
|
+
|
|
216
|
+
// Prefer URL param over session — the cookie may lag behind the iframe reload.
|
|
217
|
+
const urlPerspective = getPerspectiveFromUrl(request.url)
|
|
218
|
+
|
|
219
|
+
if (
|
|
220
|
+
urlPerspective !== undefined &&
|
|
221
|
+
!(Array.isArray(urlPerspective) && !supportsPerspectiveStack(apiVersion))
|
|
222
|
+
) {
|
|
223
|
+
perspective = urlPerspective
|
|
224
|
+
} else if (supportsPerspectiveStack(apiVersion)) {
|
|
217
225
|
perspective = getPerspective(preview.session)
|
|
218
226
|
} else {
|
|
219
227
|
if (process.env.NODE_ENV === 'development' && !didWarnAboutNoPerspectiveSupport) {
|
|
@@ -256,12 +264,8 @@ You can find the latest version in the Sanity changelog: https://www.sanity.io/c
|
|
|
256
264
|
params: QueryParams | QueryWithoutParams,
|
|
257
265
|
loaderOptions?: LoadQueryOptions<ClientReturn<Query, Result>>,
|
|
258
266
|
): Promise<QueryResponseInitial<ClientReturn<Query, Result>>> {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
const {setServerClient} = await import('@sanity/react-loader')
|
|
262
|
-
setServerClient(client)
|
|
263
|
-
didInitializeLoader = true
|
|
264
|
-
}
|
|
267
|
+
const {setServerClient} = await import('@sanity/react-loader')
|
|
268
|
+
setServerClient(client)
|
|
265
269
|
|
|
266
270
|
// Warn users to migrate to `query` method when using loadQuery outside preview mode
|
|
267
271
|
if (!previewEnabled && process.env.NODE_ENV === 'development' && !didWarnAboutLoadQuery) {
|
|
@@ -273,7 +277,12 @@ You can find the latest version in the Sanity changelog: https://www.sanity.io/c
|
|
|
273
277
|
|
|
274
278
|
if (!withCache || previewEnabled) {
|
|
275
279
|
const {loadQuery} = await import('@sanity/react-loader')
|
|
276
|
-
|
|
280
|
+
// Override the singleton's possibly-stale perspective with the per-request value.
|
|
281
|
+
const resolvedOptions =
|
|
282
|
+
previewEnabled && !loaderOptions?.perspective
|
|
283
|
+
? {...loaderOptions, perspective: client.config().perspective as ClientPerspective}
|
|
284
|
+
: loaderOptions
|
|
285
|
+
return await loadQuery<ClientReturn<Query, Result>>(query, params, resolvedOptions)
|
|
277
286
|
}
|
|
278
287
|
|
|
279
288
|
const cacheStrategy =
|
package/src/index.ts
CHANGED
|
@@ -3,6 +3,7 @@ export {createSanityContext, type SanityContext} from './context'
|
|
|
3
3
|
export {useImageUrl, useImageUrlBuilder} from './image'
|
|
4
4
|
export {Sanity, useSanityProviderValue} from './provider'
|
|
5
5
|
export {Query, type QueryProps} from './Query'
|
|
6
|
+
export {getPerspectiveFromUrl} from './utils'
|
|
6
7
|
export {useQuery} from './visual-editing/useQuery'
|
|
7
8
|
export {createDataAttribute} from '@sanity/core-loader/create-data-attribute'
|
|
8
9
|
export type {EncodeDataAttributeFunction} from '@sanity/core-loader/encode-data-attribute'
|
package/src/utils.test.ts
CHANGED
|
@@ -2,7 +2,7 @@ import {beforeEach, describe, expect, it, vi} from 'vitest'
|
|
|
2
2
|
|
|
3
3
|
import {PreviewSession} from './fixtures'
|
|
4
4
|
import {isPreviewEnabled} from './preview/utils'
|
|
5
|
-
import {getPerspective} from './utils'
|
|
5
|
+
import {getPerspective, getPerspectiveFromUrl} from './utils'
|
|
6
6
|
import {sanitizePerspective, supportsPerspectiveStack} from './utils'
|
|
7
7
|
|
|
8
8
|
describe('sanitizePerspective', () => {
|
|
@@ -105,6 +105,47 @@ describe('getPerspective', () => {
|
|
|
105
105
|
})
|
|
106
106
|
})
|
|
107
107
|
|
|
108
|
+
describe('getPerspectiveFromUrl', () => {
|
|
109
|
+
it('should return parsed perspective when URL param is present and valid', () => {
|
|
110
|
+
expect(getPerspectiveFromUrl('https://example.com/?sanity-preview-perspective=drafts')).toBe(
|
|
111
|
+
'drafts',
|
|
112
|
+
)
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
it('should return undefined when param is absent', () => {
|
|
116
|
+
expect(getPerspectiveFromUrl('https://example.com/')).toBeUndefined()
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
it('should return undefined when param is empty', () => {
|
|
120
|
+
expect(
|
|
121
|
+
getPerspectiveFromUrl('https://example.com/?sanity-preview-perspective='),
|
|
122
|
+
).toBeUndefined()
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
it('should handle comma-separated perspective stacks', () => {
|
|
126
|
+
expect(
|
|
127
|
+
getPerspectiveFromUrl('https://example.com/?sanity-preview-perspective=releaseId,drafts'),
|
|
128
|
+
).toEqual(['releaseId', 'drafts'])
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
it('should filter empty segments from perspective stacks', () => {
|
|
132
|
+
expect(
|
|
133
|
+
getPerspectiveFromUrl('https://example.com/?sanity-preview-perspective=,releaseId,drafts'),
|
|
134
|
+
).toEqual(['releaseId', 'drafts'])
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
it('should convert raw to drafts', () => {
|
|
138
|
+
expect(getPerspectiveFromUrl('https://example.com/?sanity-preview-perspective=raw')).toBe(
|
|
139
|
+
'drafts',
|
|
140
|
+
)
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
it('should accept a URL object', () => {
|
|
144
|
+
const url = new URL('https://example.com/?sanity-preview-perspective=drafts')
|
|
145
|
+
expect(getPerspectiveFromUrl(url)).toBe('drafts')
|
|
146
|
+
})
|
|
147
|
+
})
|
|
148
|
+
|
|
108
149
|
describe('isPreviewEnabled', () => {
|
|
109
150
|
const projectId = 'test-project-id'
|
|
110
151
|
|
package/src/utils.ts
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
type QueryWithoutParams,
|
|
5
5
|
validateApiPerspective,
|
|
6
6
|
} from '@sanity/client'
|
|
7
|
+
import {urlSearchParamPreviewPerspective} from '@sanity/preview-url-secret/constants'
|
|
7
8
|
import type {HydrogenSession} from '@shopify/hydrogen'
|
|
8
9
|
|
|
9
10
|
import type {SanityPreviewSession} from './preview/session'
|
|
@@ -95,6 +96,21 @@ export function getPerspective(session: SanityPreviewSession | HydrogenSession):
|
|
|
95
96
|
return perspective
|
|
96
97
|
}
|
|
97
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Reads the `sanity-preview-perspective` URL search param and validates it.
|
|
101
|
+
* Returns `undefined` if absent or invalid, so callers can fall back to the session.
|
|
102
|
+
*/
|
|
103
|
+
export function getPerspectiveFromUrl(url: URL | string): ClientPerspective | undefined {
|
|
104
|
+
try {
|
|
105
|
+
const parsed = typeof url === 'string' ? new URL(url) : url
|
|
106
|
+
const param = parsed.searchParams.get(urlSearchParamPreviewPerspective)
|
|
107
|
+
if (!param) return undefined
|
|
108
|
+
return sanitizePerspective(param)
|
|
109
|
+
} catch {
|
|
110
|
+
return undefined
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
98
114
|
/**
|
|
99
115
|
* Type guard that checks if a session object is a SanityPreviewSession.
|
|
100
116
|
* Validates presence of required methods: has, destroy (in addition to Hydrogen session methods).
|