hydrogen-sanity 5.0.0 → 5.1.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/README.md +175 -37
- package/dist/_chunks-es/provider.js +2 -5
- package/dist/_chunks-es/provider.js.map +1 -1
- package/dist/index.js +11 -7
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
- package/src/Query.tsx +15 -17
- package/src/provider.tsx +6 -14
package/README.md
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
# hydrogen-sanity
|
|
2
2
|
|
|
3
3
|
> [!TIP]
|
|
4
|
-
> **Upgrading from v4?** See the [migration guide](https://github.com/sanity-io/hydrogen-sanity/blob/main/
|
|
4
|
+
> **Upgrading from v4?** See the [migration guide](https://github.com/sanity-io/hydrogen-sanity/blob/main/packages/hydrogen-sanity/MIGRATE-v4-to-v5.md) for breaking changes and new features. 🎉
|
|
5
5
|
|
|
6
|
-
[Sanity.io](https://www.sanity.io) toolkit for [Hydrogen](https://hydrogen.shopify.dev/). Requires `@shopify/hydrogen >= 2025.5.
|
|
6
|
+
[Sanity.io](https://www.sanity.io) toolkit for [Hydrogen](https://hydrogen.shopify.dev/). Requires `@shopify/hydrogen >= 2025.5.0`.
|
|
7
7
|
|
|
8
8
|
Learn more about [getting started with Sanity](https://www.sanity.io/docs/getting-started).
|
|
9
9
|
|
|
10
10
|
**Features:**
|
|
11
11
|
|
|
12
|
-
-
|
|
12
|
+
- Drop-in preview mode handling with pre-built route and session management.
|
|
13
13
|
- Opinionated data fetching that automatically adapts to preview mode.
|
|
14
14
|
- Interactive live preview with automatic loader detection for [Visual Editing](https://www.sanity.io/docs/loaders-and-overlays) in Sanity's Presentation tool.
|
|
15
|
+
- Optimized image URL generation hooks with Sanity's image URL builder.
|
|
16
|
+
- TypeScript support with [Sanity TypeGen](https://www.sanity.io/docs/sanity-typegen).
|
|
15
17
|
|
|
16
18
|
> [!TIP]
|
|
17
19
|
>
|
|
@@ -21,6 +23,7 @@ Learn more about [getting started with Sanity](https://www.sanity.io/docs/gettin
|
|
|
21
23
|
- [Add Vite plugin](#add-vite-plugin)
|
|
22
24
|
- [Usage](#usage)
|
|
23
25
|
- [Satisfy TypeScript](#satisfy-typescript)
|
|
26
|
+
- [Set up the Sanity provider](#set-up-the-sanity-provider)
|
|
24
27
|
- [Interacting with Sanity data](#interacting-with-sanity-data)
|
|
25
28
|
- [Recommended: Using `query` and `Query` together](#recommended-using-query-and-query-together)
|
|
26
29
|
- [Alternative: Cached queries using `loadQuery`](#alternative-cached-queries-using-loadquery)
|
|
@@ -84,27 +87,47 @@ export default defineConfig({
|
|
|
84
87
|
Create the Sanity context and pass it through to your application, and optionally, configure the preview mode if you plan to setup Visual Editing
|
|
85
88
|
|
|
86
89
|
> [!NOTE]
|
|
87
|
-
> The examples below are up-to-date as of `npm create @shopify/hydrogen@2025.
|
|
90
|
+
> The examples below are up-to-date as of `npm create @shopify/hydrogen@2025.7.0`
|
|
88
91
|
|
|
89
92
|
```diff
|
|
90
93
|
// ./lib/context.ts
|
|
91
94
|
|
|
92
95
|
// ...all other imports
|
|
93
|
-
+ import {createSanityContext} from 'hydrogen-sanity'
|
|
96
|
+
+ import {createSanityContext, type SanityContext} from 'hydrogen-sanity'
|
|
97
|
+
|
|
98
|
+
// Define the additional context object
|
|
99
|
+
const additionalContext = {
|
|
100
|
+
// Additional context for custom properties, CMS clients, 3P SDKs, etc.
|
|
101
|
+
// These will be available as both context.propertyName and context.get(propertyContext)
|
|
102
|
+
// Example of complex objects that could be added:
|
|
103
|
+
// cms: await createCMSClient(env),
|
|
104
|
+
// reviews: await createReviewsClient(env),
|
|
105
|
+
} as const;
|
|
106
|
+
|
|
107
|
+
// Automatically augment HydrogenAdditionalContext with the additional context type
|
|
108
|
+
type AdditionalContextType = typeof additionalContext;
|
|
109
|
+
|
|
110
|
+
declare global {
|
|
111
|
+
interface HydrogenAdditionalContext extends AdditionalContextType {
|
|
112
|
+
+
|
|
113
|
+
+ // Augment `HydrogenAdditionalContext` with the Sanity context
|
|
114
|
+
+ sanity: SanityContext;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
94
117
|
|
|
95
|
-
export async function
|
|
118
|
+
export async function createHydrogenRouterContext(
|
|
96
119
|
request: Request,
|
|
97
120
|
env: Env,
|
|
98
121
|
executionContext: ExecutionContext,
|
|
99
122
|
) {
|
|
100
|
-
// ... Leave all other functions
|
|
123
|
+
// ... Leave all other functions as-is
|
|
101
124
|
const waitUntil = executionContext.waitUntil.bind(executionContext)
|
|
102
125
|
const [cache, session] = await Promise.all([
|
|
103
126
|
caches.open('hydrogen'),
|
|
104
127
|
AppSession.init(request, [env.SESSION_SECRET]),
|
|
105
128
|
])
|
|
106
129
|
|
|
107
|
-
+ //
|
|
130
|
+
+ // Initialize the Sanity context
|
|
108
131
|
+ const sanity = await createSanityContext({
|
|
109
132
|
+ request,
|
|
110
133
|
+
|
|
@@ -132,12 +155,29 @@ export async function createAppLoadContext(
|
|
|
132
155
|
+ // strategy: CacheShort() | null,
|
|
133
156
|
+ })
|
|
134
157
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
158
|
+
|
|
159
|
+
+ // Make `sanity` available to loaders and actions in the request context
|
|
160
|
+
const hydrogenContext = createHydrogenContext(
|
|
161
|
+
{
|
|
162
|
+
env,
|
|
163
|
+
request,
|
|
164
|
+
cache,
|
|
165
|
+
waitUntil,
|
|
166
|
+
session,
|
|
167
|
+
i18n: {language: 'EN', country: 'US'},
|
|
168
|
+
cart: {
|
|
169
|
+
queryFragment: CART_QUERY_FRAGMENT,
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
+ {
|
|
173
|
+
+ ...additionalContext
|
|
174
|
+
+ sanity,
|
|
175
|
+
+ } as const,
|
|
176
|
+
+ )
|
|
177
|
+
+
|
|
178
|
+
return hydrogenContext
|
|
140
179
|
}
|
|
180
|
+
|
|
141
181
|
```
|
|
142
182
|
|
|
143
183
|
Learn more about [Sanity's JavaScript client configuration](https://www.sanity.io/docs/js-client).
|
|
@@ -184,6 +224,73 @@ declare global {
|
|
|
184
224
|
}
|
|
185
225
|
```
|
|
186
226
|
|
|
227
|
+
### Set up the Sanity provider
|
|
228
|
+
|
|
229
|
+
You now need to wrap your app with the Sanity provider to make Sanity context available to client-side hooks and components like `useImageUrl` and `Query`.
|
|
230
|
+
|
|
231
|
+
**Update entry.server.tsx**
|
|
232
|
+
|
|
233
|
+
Wrap your app with the Sanity provider in your server entry point:
|
|
234
|
+
|
|
235
|
+
```diff
|
|
236
|
+
// ./app/entry.server.tsx
|
|
237
|
+
|
|
238
|
+
export default async function handleRequest(
|
|
239
|
+
request: Request,
|
|
240
|
+
responseStatusCode: number,
|
|
241
|
+
responseHeaders: Headers,
|
|
242
|
+
reactRouterContext: EntryContext,
|
|
243
|
+
- context: AppLoadContext,
|
|
244
|
+
+ context: HydrogenRouterContextProvider,
|
|
245
|
+
) {
|
|
246
|
+
+ const {SanityProvider} = context.sanity
|
|
247
|
+
|
|
248
|
+
// ... CSP setup etc ...
|
|
249
|
+
|
|
250
|
+
const body = await renderToReadableStream(
|
|
251
|
+
<NonceProvider>
|
|
252
|
+
+ <SanityProvider>
|
|
253
|
+
<ServerRouter context={reactRouterContext} url={request.url} nonce={nonce} />
|
|
254
|
+
+ </SanityProvider>
|
|
255
|
+
</NonceProvider>,
|
|
256
|
+
// ... render options
|
|
257
|
+
)
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
**Update root.tsx**
|
|
262
|
+
|
|
263
|
+
Add the `Sanity` component to your root layout:
|
|
264
|
+
|
|
265
|
+
```diff
|
|
266
|
+
// ./app/root.tsx
|
|
267
|
+
|
|
268
|
+
+ import {Sanity} from 'hydrogen-sanity'
|
|
269
|
+
|
|
270
|
+
export function Layout({children}: {children?: React.ReactNode}) {
|
|
271
|
+
const nonce = useNonce()
|
|
272
|
+
|
|
273
|
+
return (
|
|
274
|
+
<html lang="en">
|
|
275
|
+
<head>
|
|
276
|
+
<meta charSet="utf-8" />
|
|
277
|
+
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
278
|
+
<Meta />
|
|
279
|
+
<Links />
|
|
280
|
+
</head>
|
|
281
|
+
<body>
|
|
282
|
+
{children}
|
|
283
|
+
|
|
284
|
+
+ {/* Add Sanity client-side script */}
|
|
285
|
+
+ <Sanity nonce={nonce} />
|
|
286
|
+
<ScrollRestoration nonce={nonce} />
|
|
287
|
+
<Scripts nonce={nonce} />
|
|
288
|
+
</body>
|
|
289
|
+
</html>
|
|
290
|
+
)
|
|
291
|
+
}
|
|
292
|
+
```
|
|
293
|
+
|
|
187
294
|
## Interacting with Sanity data
|
|
188
295
|
|
|
189
296
|
### Recommended: Using `query` and `Query` together
|
|
@@ -495,16 +602,16 @@ For Visual Editing to work, you need to configure Sanity preview mode in your co
|
|
|
495
602
|
// ./lib/context.ts
|
|
496
603
|
|
|
497
604
|
// ...all other imports
|
|
498
|
-
import {createSanityContext} from 'hydrogen-sanity'
|
|
605
|
+
import {createSanityContext, type SanityContext} from 'hydrogen-sanity'
|
|
499
606
|
+ import {PreviewSession} from 'hydrogen-sanity/preview/session'
|
|
500
607
|
+ import {isPreviewEnabled} from 'hydrogen-sanity/preview'
|
|
501
608
|
|
|
502
|
-
export async function
|
|
609
|
+
export async function createHydrogenRouterContext(
|
|
503
610
|
request: Request,
|
|
504
611
|
env: Env,
|
|
505
612
|
executionContext: ExecutionContext,
|
|
506
613
|
) {
|
|
507
|
-
// ... Leave all other functions
|
|
614
|
+
// ... Leave all other functions as-is
|
|
508
615
|
const waitUntil = executionContext.waitUntil.bind(executionContext)
|
|
509
616
|
- const [cache, session] = await Promise.all([
|
|
510
617
|
+ const [cache, session, previewSession] = await Promise.all([
|
|
@@ -537,11 +644,6 @@ export async function createAppLoadContext(
|
|
|
537
644
|
+ session: previewSession,
|
|
538
645
|
+ },
|
|
539
646
|
})
|
|
540
|
-
|
|
541
|
-
return {
|
|
542
|
-
...hydrogenContext,
|
|
543
|
-
sanity,
|
|
544
|
-
}
|
|
545
647
|
}
|
|
546
648
|
```
|
|
547
649
|
|
|
@@ -675,18 +777,19 @@ Since Sanity Studio's Presentation tool displays the storefront inside an iframe
|
|
|
675
777
|
// ./app/entry.server.tsx
|
|
676
778
|
|
|
677
779
|
// ...all other imports
|
|
678
|
-
+ import type {AppLoadContext, EntryContext} from 'react-router'
|
|
679
780
|
|
|
680
781
|
export default async function handleRequest(
|
|
681
782
|
request: Request,
|
|
682
783
|
responseStatusCode: number,
|
|
683
784
|
responseHeaders: Headers,
|
|
684
785
|
reactRouterContext: EntryContext,
|
|
685
|
-
context: AppLoadContext,
|
|
786
|
+
- context: AppLoadContext,
|
|
787
|
+
+ context: HydrogenRouterContextProvider,
|
|
686
788
|
) {
|
|
687
|
-
+ const
|
|
688
|
-
+ const
|
|
689
|
-
+ const
|
|
789
|
+
+ const {env, sanity} = context
|
|
790
|
+
+ const projectId = env.SANITY_PROJECT_ID
|
|
791
|
+
+ const studioHostname = env.SANITY_STUDIO_HOSTNAME || 'http://localhost:3333'
|
|
792
|
+
+ const isPreviewEnabled = sanity.preview?.enabled
|
|
690
793
|
|
|
691
794
|
const {nonce, header, NonceProvider} = createContentSecurityPolicy({
|
|
692
795
|
// If your storefront and Studio are on separate domains...
|
|
@@ -774,8 +877,31 @@ The following example configures Sanity Client and provides it in the request co
|
|
|
774
877
|
|
|
775
878
|
// ...all other imports
|
|
776
879
|
+ import {createClient} from '@sanity/client'
|
|
880
|
+
+ import {createSanityContext, type SanityContext} from 'hydrogen-sanity'
|
|
881
|
+
|
|
882
|
+
// Define the additional context object
|
|
883
|
+
const additionalContext = {
|
|
884
|
+
// Additional context for custom properties, CMS clients, 3P SDKs, etc.
|
|
885
|
+
// These will be available as both context.propertyName and context.get(propertyContext)
|
|
886
|
+
// Example of complex objects that could be added:
|
|
887
|
+
// cms: await createCMSClient(env),
|
|
888
|
+
// reviews: await createReviewsClient(env),
|
|
889
|
+
} as const;
|
|
890
|
+
|
|
777
891
|
|
|
778
|
-
|
|
892
|
+
// Automatically augment HydrogenAdditionalContext with the additional context type
|
|
893
|
+
type AdditionalContextType = typeof additionalContext;
|
|
894
|
+
|
|
895
|
+
declare global {
|
|
896
|
+
interface HydrogenAdditionalContext extends AdditionalContextType {
|
|
897
|
+
+
|
|
898
|
+
+ // Augment `HydrogenAdditionalContext` with the Sanity context
|
|
899
|
+
+ sanity: SanityContext;
|
|
900
|
+
+ withCache: WithCache;
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
export async function createHydrogenRouterContext(
|
|
779
905
|
request: Request,
|
|
780
906
|
env: Env,
|
|
781
907
|
executionContext: ExecutionContext,
|
|
@@ -791,14 +917,26 @@ export async function createAppLoadContext(
|
|
|
791
917
|
+ useCdn: process.env.NODE_ENV === 'production',
|
|
792
918
|
+ })
|
|
793
919
|
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
920
|
+
const hydrogenContext = createHydrogenContext(
|
|
921
|
+
{
|
|
922
|
+
env,
|
|
923
|
+
request,
|
|
924
|
+
cache,
|
|
925
|
+
waitUntil,
|
|
926
|
+
session,
|
|
927
|
+
i18n: {language: 'EN', country: 'US'},
|
|
928
|
+
cart: {
|
|
929
|
+
queryFragment: CART_QUERY_FRAGMENT,
|
|
930
|
+
},
|
|
931
|
+
},
|
|
932
|
+
+ {
|
|
933
|
+
+ ...additionalContext,
|
|
934
|
+
+ sanity,
|
|
935
|
+
+ withCache,
|
|
936
|
+
+ } as const,
|
|
937
|
+
+ )
|
|
938
|
+
+
|
|
939
|
+
+ return hydrogenContext
|
|
802
940
|
}
|
|
803
941
|
```
|
|
804
942
|
|
|
@@ -829,8 +967,8 @@ export async function loader({context, params}: LoaderArgs) {
|
|
|
829
967
|
|
|
830
968
|
## Migration Guides
|
|
831
969
|
|
|
832
|
-
- [From `v3` to `v4`](https://github.com/sanity-io/hydrogen-sanity/blob/main/
|
|
833
|
-
- [From `v4` to `v5`](https://github.com/sanity-io/hydrogen-sanity/blob/main/
|
|
970
|
+
- [From `v3` to `v4`](https://github.com/sanity-io/hydrogen-sanity/blob/main/packages/hydrogen-sanity/MIGRATE-v3-to-v4.md)
|
|
971
|
+
- [From `v4` to `v5`](https://github.com/sanity-io/hydrogen-sanity/blob/main/packages/hydrogen-sanity/MIGRATE-v4-to-v5.md)
|
|
834
972
|
|
|
835
973
|
## License
|
|
836
974
|
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { jsx, Fragment } from 'react/jsx-runtime';
|
|
2
|
-
import { useEffect } from 'react';
|
|
3
2
|
|
|
4
3
|
function assertSanityProviderValue(value) {
|
|
5
|
-
if (
|
|
4
|
+
if (typeof value === "undefined") {
|
|
6
5
|
throw new Error(
|
|
7
6
|
"Failed to find a Sanity provider value. Did you forget to wrap your app in a `SanityProvider`?"
|
|
8
7
|
);
|
|
@@ -18,9 +17,7 @@ function SanityProvider({
|
|
|
18
17
|
value,
|
|
19
18
|
children
|
|
20
19
|
}) {
|
|
21
|
-
|
|
22
|
-
setProviderValue(value);
|
|
23
|
-
}, [value]);
|
|
20
|
+
setProviderValue(value);
|
|
24
21
|
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
25
22
|
}
|
|
26
23
|
function Sanity(props) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider.js","sources":["../../src/provider.tsx"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"provider.js","sources":["../../src/provider.tsx"],"sourcesContent":["import type {InitializedClientConfig} from '@sanity/client'\nimport type {HTMLProps, PropsWithChildren, ReactNode} from 'react'\n\n/**\n * Contains essential Sanity client configuration and preview/stega state.\n */\nexport interface SanityProviderValue\n extends Required<\n Pick<\n InitializedClientConfig,\n 'projectId' | 'dataset' | 'apiHost' | 'apiVersion' | 'perspective'\n >\n > {\n previewEnabled: boolean\n stegaEnabled: boolean\n}\n\n/**\n * Type guard that asserts a value is a valid SanityProviderValue.\n * Throws an error if the provider value is missing or invalid.\n */\nexport function assertSanityProviderValue(value: unknown): value is SanityProviderValue {\n if (typeof value === 'undefined') {\n throw new Error(\n 'Failed to find a Sanity provider value. Did you forget to wrap your app in a `SanityProvider`?',\n )\n }\n\n return true\n}\n\n/**\n * Hook that retrieves the current Sanity provider configuration.\n * Must be used within a SanityProvider component tree.\n */\nexport function useSanityProviderValue(): SanityProviderValue {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const providerValue = (globalThis as any)[Symbol.for('Sanity Provider')]\n assertSanityProviderValue(providerValue)\n return providerValue\n}\n\n/**\n * Provider that makes Sanity configuration available to child components.\n * Serializes configuration across server-client boundary via globalThis.\n */\nexport function SanityProvider({\n value,\n children,\n}: PropsWithChildren<{value: SanityProviderValue}>): ReactNode {\n setProviderValue(value)\n return <>{children}</>\n}\n\n/**\n * Script component that hydrates Sanity configuration on the client side.\n * Injects provider configuration into the client bundle for SSR compatibility.\n */\nexport function Sanity(props: SanityProps): ReactNode {\n const providerValue = useSanityProviderValue()\n assertSanityProviderValue(providerValue)\n\n return (\n <script\n {...props}\n // eslint-disable-next-line react/no-danger\n dangerouslySetInnerHTML={{\n __html: `(${setProviderValue})(${JSON.stringify(providerValue)})`,\n }}\n suppressHydrationWarning\n />\n )\n}\n\n/**\n * Props for the Sanity script component.\n * Extends HTMLScriptElement props while excluding conflicting attributes.\n */\nexport type SanityProps = Omit<\n HTMLProps<HTMLScriptElement>,\n | 'children'\n | 'async'\n | 'defer'\n | 'src'\n | 'type'\n | 'noModule'\n | 'dangerouslySetInnerHTML'\n | 'suppressHydrationWarning'\n>\n\nfunction setProviderValue(value: SanityProviderValue) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ;(globalThis as any)[Symbol.for('Sanity Provider')] = Object.freeze(value)\n}\n"],"names":[],"mappings":";;AAqBO,SAAS,0BAA0B,KAAA,EAA8C;AACtF,EAAA,IAAI,OAAO,UAAU,WAAA,EAAa;AAChC,IAAA,MAAM,IAAI,KAAA;AAAA,MACR;AAAA,KACF;AAAA,EACF;AAEA,EAAA,OAAO,IAAA;AACT;AAMO,SAAS,sBAAA,GAA8C;AAE5D,EAAA,MAAM,aAAA,GAAiB,UAAA,CAAmB,MAAA,CAAO,GAAA,CAAI,iBAAiB,CAAC,CAAA;AACvE,EAAA,yBAAA,CAA0B,aAAa,CAAA;AACvC,EAAA,OAAO,aAAA;AACT;AAMO,SAAS,cAAA,CAAe;AAAA,EAC7B,KAAA;AAAA,EACA;AACF,CAAA,EAA+D;AAC7D,EAAA,gBAAA,CAAiB,KAAK,CAAA;AACtB,EAAA,uCAAU,QAAA,EAAS,CAAA;AACrB;AAMO,SAAS,OAAO,KAAA,EAA+B;AACpD,EAAA,MAAM,gBAAgB,sBAAA,EAAuB;AAC7C,EAAA,yBAAA,CAA0B,aAAa,CAAA;AAEvC,EAAA,uBACE,GAAA;AAAA,IAAC,QAAA;AAAA,IAAA;AAAA,MACE,GAAG,KAAA;AAAA,MAEJ,uBAAA,EAAyB;AAAA,QACvB,QAAQ,CAAA,CAAA,EAAI,gBAAgB,KAAK,IAAA,CAAK,SAAA,CAAU,aAAa,CAAC,CAAA,CAAA;AAAA,OAChE;AAAA,MACA,wBAAA,EAAwB;AAAA;AAAA,GAC1B;AAEJ;AAkBA,SAAS,iBAAiB,KAAA,EAA4B;AAEnD,EAAC,UAAA,CAAmB,OAAO,GAAA,CAAI,iBAAiB,CAAC,CAAA,GAAI,MAAA,CAAO,OAAO,KAAK,CAAA;AAC3E;;;;"}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CacheLong, createWithCache } from '@shopify/hydrogen';
|
|
2
2
|
import { SanityClient, createClient } from '@sanity/client';
|
|
3
|
-
import { createElement, useMemo, lazy,
|
|
3
|
+
import { createElement, useMemo, lazy, Suspense, useSyncExternalStore, useId, useEffect } from 'react';
|
|
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';
|
|
@@ -163,6 +163,15 @@ function useImageUrl(source) {
|
|
|
163
163
|
function SanityQueryFallback() {
|
|
164
164
|
return null;
|
|
165
165
|
}
|
|
166
|
+
function useIsHydrated() {
|
|
167
|
+
return useSyncExternalStore(
|
|
168
|
+
// eslint-disable-next-line no-empty-function
|
|
169
|
+
() => () => {
|
|
170
|
+
},
|
|
171
|
+
() => true,
|
|
172
|
+
() => false
|
|
173
|
+
);
|
|
174
|
+
}
|
|
166
175
|
const QueryClient = isServer() ? SanityQueryFallback : lazy(
|
|
167
176
|
() => (
|
|
168
177
|
/**
|
|
@@ -183,12 +192,7 @@ function Query({
|
|
|
183
192
|
...suspenseProps
|
|
184
193
|
}) {
|
|
185
194
|
const isPreviewMode = usePreviewMode();
|
|
186
|
-
const
|
|
187
|
-
useEffect(() => {
|
|
188
|
-
startTransition(() => {
|
|
189
|
-
setIsHydrated(true);
|
|
190
|
-
});
|
|
191
|
-
}, []);
|
|
195
|
+
const isHydrated = useIsHydrated();
|
|
192
196
|
if (isPreviewMode && isHydrated) {
|
|
193
197
|
return /* @__PURE__ */ jsx(Suspense, { ...suspenseProps, fallback: suspenseProps.fallback ?? /* @__PURE__ */ jsx(SanityQueryFallback, {}), children: /* @__PURE__ */ jsx(
|
|
194
198
|
QueryClient,
|
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\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 // Set server client for react-loader when in preview mode\n const {setServerClient} = await import('@sanity/react-loader')\n setServerClient(client)\n }\n }\n\n // Server client will be initialized lazily on first loadQuery call\n\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 const sanityContext: SanityContext = {\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 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 }\n\n return sanityContext\n}\n","import {default as createImageUrlBuilder} from '@sanity/image-url'\nimport type {ImageUrlBuilder} from '@sanity/image-url/lib/types/builder'\nimport type {SanityImageSource, SanityModernClientLike} from '@sanity/image-url/lib/types/types'\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 {ImageUrlBuilder} from '@sanity/image-url/lib/types/builder'\nexport type * from '@sanity/image-url/lib/types/types'\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 {\n lazy,\n type ReactNode,\n startTransition,\n Suspense,\n type SuspenseProps,\n useEffect,\n useState,\n} 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\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>\n extends Omit<QueryClientProps<Result, Query>, 'options'> {\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, setIsHydrated] = useState(false)\n\n // Mark component as hydrated after initial render to prevent hydration mismatches\n useEffect(() => {\n startTransition(() => {\n setIsHydrated(true)\n })\n }, [])\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":";;;;;;;;;;;;;AAGO,MAAM,mBAAA,GAAsB;AAG5B,MAAM,yBAA0C,SAAA;;ACiBvD,IAAI,wBAAA,GAA2B,KAAA;AAC/B,IAAI,gCAAA,GAAmC,KAAA;AA0JvC,eAAsB,oBACpB,OAAA,EACwB;AACxB,EAAA,MAAM,EAAC,KAAA,EAAO,SAAA,GAAY,MAAM,OAAA,CAAQ,SAAQ,EAAG,OAAA,EAAS,OAAA,EAAS,eAAA,EAAe,GAAI,OAAA;AACxF,EAAA,MAAM,SAAA,GAAY,QAAQ,eAAA,CAAgB,EAAC,OAAO,SAAA,EAAW,OAAA,EAAQ,CAAA,GAAI,IAAA;AACzE,EAAA,IAAI,MAAA,GACF,QAAQ,MAAA,YAAkB,YAAA,GAAe,QAAQ,MAAA,GAAS,YAAA,CAAa,QAAQ,MAAM,CAAA;AAEvF,EAAA,IAAI,MAAA,CAAO,MAAA,EAAO,CAAE,UAAA,KAAe,GAAA,EAAK;AACtC,IAAA,IAAI,OAAA,CAAQ,GAAA,CAAI,QAAA,KAAa,aAAA,IAAiB,CAAC,wBAAA,EAA0B;AACvE,MAAA,OAAA,CAAQ,IAAA;AAAA,QACN;AAAA,0CAAA,EACoC,mBAAmB,CAAA;AAAA;AAAA,IAAA,CAAA,CAEzD,IAAA;AAAK,OACL;AAEA,MAAA,wBAAA,GAA2B,IAAA;AAAA,IAC7B;AAEA,IAAA,MAAA,GAAS,MAAA,CAAO,UAAA,CAAW,EAAC,UAAA,EAAY,qBAAoB,CAAA;AAAA,EAC9D;AAGA,EAAA,IAAI,cAAA,GAAiB,KAAA;AACrB,EAAA,IAAI,OAAA,EAAS;AACX,IAAA,IAAI,CAAC,QAAQ,KAAA,EAAO;AAClB,MAAA,MAAM,IAAI,MAAM,yCAAyC,CAAA;AAAA,IAC3D;AAEA,IAAA,cAAA,GAAiB,iBAAiB,MAAA,CAAO,MAAA,EAAO,CAAE,SAAA,EAAY,QAAQ,OAAO,CAAA;AAE7E,IAAA,IAAI,cAAA,EAAgB;AAClB,MAAA,MAAMA,WAAAA,GAAa,MAAA,CAAO,MAAA,EAAO,CAAE,UAAA;AACnC,MAAA,IAAI,WAAA;AACJ,MAAA,IAAI,wBAAA,CAAyBA,WAAU,CAAA,EAAG;AACxC,QAAA,WAAA,GAAc,cAAA,CAAe,QAAQ,OAAO,CAAA;AAAA,MAC9C,CAAA,MAAO;AACL,QAAA,IAAI,OAAA,CAAQ,GAAA,CAAI,QAAA,KAAa,aAAA,IAAiB,CAAC,gCAAA,EAAkC;AAC/E,UAAA,OAAA,CAAQ,IAAA;AAAA,YACN,iBAAiBA,WAAU,CAAA,yJAAA;AAAA,WAC7B;AAEA,UAAA,gCAAA,GAAmC,IAAA;AAAA,QACrC;AACA,QAAA,WAAA,GAAc,eAAA;AAAA,MAChB;AAEA,MAAA,MAAA,GAAS,OAAO,UAAA,CAAW;AAAA,QACzB,MAAA,EAAQ,KAAA;AAAA,QACR,OAAO,OAAA,CAAQ,KAAA;AAAA,QACf;AAAA,OACD,CAAA;AAGD,MAAA,MAAM,EAAC,eAAA,EAAe,GAAI,MAAM,OAAO,sBAAsB,CAAA;AAC7D,MAAA,eAAA,CAAgB,MAAM,CAAA;AAAA,IACxB;AAAA,EACF;AAIA,EAAA,MAAM,EAAC,OAAA,EAAS,SAAA,EAAW,SAAS,UAAA,EAAU,GAAI,OAAO,MAAA,EAAO;AAChE,EAAA,MAAM,aAAA,GAAqC;AAAA,IACzC,SAAA;AAAA,IACA,OAAA;AAAA,IACA,OAAA;AAAA,IACA,UAAA;AAAA,IACA,cAAA;AAAA,IACA,WAAA,EAAa,MAAA,CAAO,MAAA,EAAO,CAAE,WAAA,IAAe,WAAA;AAAA,IAC5C,YAAA,EAAc,MAAA,CAAO,MAAA,EAAO,CAAE,OAAO,OAAA,IAAW;AAAA,GAClD;AAEA,EAAA,MAAM,aAAA,GAA+B;AAAA;AAAA;AAAA;AAAA;AAAA,IAKnC,MAAM,SAAA,CACJ,KAAA,EACA,MAAA,EACA,aAAA,EAC4D;AAC5D,MAAA,IAAI,CAAC,aAAa,cAAA,EAAgB;AAChC,QAAA,MAAM,EAAC,SAAA,EAAS,GAAI,MAAM,OAAO,sBAAsB,CAAA;AACvD,QAAA,OAAO,MAAM,SAAA,CAAuC,KAAA,EAAO,MAAA,EAAQ,aAAa,CAAA;AAAA,MAClF;AAEA,MAAA,MAAM,aAAA,GACJ,aAAA,EAAe,QAAA,EAAU,KAAA,IAAS,eAAA,IAAmB,sBAAA;AACvD,MAAA,MAAM,SAAA,GAAY,MAAM,SAAA,CAAU,KAAA,EAAO,MAAM,CAAA;AAC/C,MAAA,MAAM,iBAAA,GAAoB,aAAA,EAAe,QAAA,EAAU,iBAAA,KAAsB,MAAM,IAAA,CAAA;AAE/E,MAAA,OAAO,MAAM,SAAA,CAAU,GAAA;AAAA,QACrB,EAAC,QAAA,EAAU,SAAA,EAAW,aAAA,EAAe,iBAAA,EAAiB;AAAA,QACtD,OAAO;AAAA,UACL;AAAA,SACF,KAEK;AAEH,UAAA,MAAM,WAAA,GAAc,aAAA,EAAe,QAAA,EAAU,KAAA,EAAO,WAAA,IAAe,cAAA;AAEnE,UAAA,YAAA,CAAa;AAAA,YACX;AAAA,WACD,CAAA;AAED,UAAA,MAAM,EAAC,SAAA,EAAS,GAAI,MAAM,OAAO,sBAAsB,CAAA;AACvD,UAAA,OAAO,MAAM,SAAA,CAAuC,KAAA,EAAO,MAAA,EAAQ,aAAa,CAAA;AAAA,QAClF;AAAA,OACF;AAAA,IACF,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAMA,MAAM,KAAA,CACJ,KAAA,EACA,MAAA,GAA2C,IAC3C,YAAA,EAIsC;AACtC,MAAA,IAAI,CAAC,aAAa,cAAA,EAAgB;AAChC,QAAA,OAAO,MAAM,MAAA,CAAO,KAAA,CAAmC,KAAA,EAAO,QAAQ,YAAY,CAAA;AAAA,MACpF;AAEA,MAAA,MAAM,aAAA,GACJ,YAAA,EAAc,QAAA,EAAU,KAAA,IAAS,eAAA,IAAmB,sBAAA;AACtD,MAAA,MAAM,SAAA,GAAY,MAAM,SAAA,CAAU,KAAA,EAAO,MAAM,CAAA;AAE/C,MAAA,OAAO,MAAM,SAAA,CAAU,GAAA;AAAA,QACrB,EAAC,QAAA,EAAU,SAAA,EAAW,aAAA,EAAe,iBAAA,EAAmB,MAAM,IAAA,EAAI;AAAA,QAClE,OAAO,EAAC,YAAA,EAAY,KAAsE;AAExF,UAAA,MAAM,WAAA,GAAc,YAAA,EAAc,QAAA,EAAU,KAAA,EAAO,WAAA,IAAe,cAAA;AAElE,UAAA,YAAA,CAAa;AAAA,YACX;AAAA,WACD,CAAA;AAED,UAAA,OAAO,MAAM,MAAA,CAAO,KAAA,CAAmC,KAAA,EAAO,QAAQ,YAAY,CAAA;AAAA,QACpF;AAAA,OACF;AAAA,IACF,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOA,MAAM,KAAA,CACJ,KAAA,EACA,MAAA,EACA,YAAA,EAC0F;AAC1F,MAAA,OAAO,MAAA,CAAO,iBAAiB,IAAA,CAAK,SAAA,GAAY,KAAK,KAAA,EAAO,KAAA,EAAO,QAAQ,YAAY,CAAA;AAAA,IACzF,CAAA;AAAA;AAAA,IAGA,MAAA;AAAA;AAAA,IAGA,SAAS,OAAA,GAAU,EAAC,GAAG,OAAA,EAAS,OAAA,EAAS,gBAAc,GAAI,MAAA;AAAA;AAAA;AAAA;AAAA,IAK3D,cAAA,CAAe,EAAC,QAAA,EAAQ,EAA8B;AACpD,MAAA,OAAO,aAAA;AAAA,QACL,cAAA;AAAA,QACA;AAAA,UACE,KAAA,EAAO,MAAA,CAAO,MAAA,CAAO,aAAa;AAAA,SACpC;AAAA,QACA;AAAA,OACF;AAAA,IACF;AAAA,GACF;AAEA,EAAA,OAAO,aAAA;AACT;;AC7VO,SAAS,kBAAA,GAAsC;AACpD,EAAA,MAAM,EAAC,SAAA,EAAW,OAAA,EAAS,OAAA,KAAW,sBAAA,EAAuB;AAC7D,EAAA,OAAO,QAAQ,MAAM;AACnB,IAAA,OAAO,qBAAA,CAAsB;AAAA,MAC3B,MAAA,EAAQ,OAAO,EAAC,SAAA,EAAW,SAAS,OAAA,EAAO;AAAA,KAClB,CAAA;AAAA,EAC7B,CAAA,EAAG,CAAC,OAAA,EAAS,OAAA,EAAS,SAAS,CAAC,CAAA;AAClC;AAMO,SAAS,YAAY,MAAA,EAA4C;AACtE,EAAA,MAAM,UAAU,kBAAA,EAAmB;AACnC,EAAA,OAAO,OAAA,CAAQ,MAAM,MAAM,CAAA;AAC7B;;ACNA,SAAS,mBAAA,GAAiC;AACxC,EAAA,OAAO,IAAA;AACT;AAEA,MAAM,WAAA,GAAc,QAAA,EAAS,GACzB,mBAAA,GACC,IAAA;AAAA,EACC;AAAA;AAAA;AAAA;AAAA;AAAA,IAKE,OAAO,8BAAgB;AAAA;AAC3B,CAAA;AAIJ,MAAM,uBAAA,GAAuD,MAAA,CAAO,MAAA,CAAO,MAAM,MAAA,EAAW;AAAA,EAC1F,OAAO,MAAM;AACf,CAAC,CAAA;AAoBM,SAAS,KAAA,CAAmD;AAAA,EACjE,KAAA;AAAA,EACA,MAAA;AAAA,EACA,OAAA;AAAA,EACA,QAAA;AAAA,EACA,GAAG;AACL,CAAA,EAA2E;AACzE,EAAA,MAAM,gBAAgB,cAAA,EAAe;AACrC,EAAA,MAAM,CAAC,UAAA,EAAY,aAAa,CAAA,GAAI,SAAS,KAAK,CAAA;AAGlD,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,eAAA,CAAgB,MAAM;AACpB,MAAA,aAAA,CAAc,IAAI,CAAA;AAAA,IACpB,CAAC,CAAA;AAAA,EACH,CAAA,EAAG,EAAE,CAAA;AAGL,EAAA,IAAI,iBAAiB,UAAA,EAAY;AAC/B,IAAA,uBACE,GAAA,CAAC,YAAU,GAAG,aAAA,EAAe,UAAU,aAAA,CAAc,QAAA,oBAAY,GAAA,CAAC,mBAAA,EAAA,EAAoB,CAAA,EACpF,QAAA,kBAAA,GAAA;AAAA,MAAC,WAAA;AAAA,MAAA;AAAA,QACC,KAAA;AAAA,QACA,MAAA;AAAA,QACA,OAAA;AAAA,QAEC;AAAA;AAAA,KACH,EACF,CAAA;AAAA,EAEJ;AAGA,EAAA,OAAO,QAAA,CAAS,OAAA,CAAQ,OAAA,EAAwC,uBAAuB,CAAA;AACzF;;ACrFO,SAAS,QAAA,CACd,KAAA,EACA,MAAA,EACA,OAAA,EACmD;AAEnD,EAAA,MAAM,KAAK,KAAA,EAAM;AAGjB,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,MAAM,UAAA,GAAa,cAAc,EAAE,CAAA;AACnC,IAAA,OAAO,UAAA;AAAA,EACT,CAAA,EAAG,CAAC,EAAE,CAAC,CAAA;AAGP,EAAA,OAAOC,UAAA,CAA+B,KAAA,EAAO,MAAA,EAAQ,OAAO,CAAA;AAC9D;;;;"}
|
|
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\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 // Set server client for react-loader when in preview mode\n const {setServerClient} = await import('@sanity/react-loader')\n setServerClient(client)\n }\n }\n\n // Server client will be initialized lazily on first loadQuery call\n\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 const sanityContext: SanityContext = {\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 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 }\n\n return sanityContext\n}\n","import {default as createImageUrlBuilder} from '@sanity/image-url'\nimport type {ImageUrlBuilder} from '@sanity/image-url/lib/types/builder'\nimport type {SanityImageSource, SanityModernClientLike} from '@sanity/image-url/lib/types/types'\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 {ImageUrlBuilder} from '@sanity/image-url/lib/types/builder'\nexport type * from '@sanity/image-url/lib/types/types'\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>\n extends Omit<QueryClientProps<Result, Query>, 'options'> {\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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hydrogen-sanity",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.1.0",
|
|
4
4
|
"description": "Sanity.io toolkit for Hydrogen",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sanity",
|
|
@@ -109,7 +109,7 @@
|
|
|
109
109
|
"@sanity/client": "^7.9.0",
|
|
110
110
|
"@sanity/pkg-utils": "^7.11.9",
|
|
111
111
|
"@sanity/semantic-release-preset": "^5.0.0",
|
|
112
|
-
"@shopify/hydrogen": "~2025.
|
|
112
|
+
"@shopify/hydrogen": "~2025.7.0",
|
|
113
113
|
"@testing-library/react": "^16.3.0",
|
|
114
114
|
"@types/react": "^18.3.24",
|
|
115
115
|
"@typescript-eslint/eslint-plugin": "^8.41.0",
|
|
@@ -124,7 +124,7 @@
|
|
|
124
124
|
"groq": "^4.5.0",
|
|
125
125
|
"react": "^18.3.1",
|
|
126
126
|
"react-dom": "^18.3.1",
|
|
127
|
-
"react-router": "7.
|
|
127
|
+
"react-router": "7.9.2",
|
|
128
128
|
"semantic-release": "^24.2.7",
|
|
129
129
|
"typescript": "^5.9.2",
|
|
130
130
|
"vite": "^6.3.5",
|
|
@@ -132,9 +132,9 @@
|
|
|
132
132
|
},
|
|
133
133
|
"peerDependencies": {
|
|
134
134
|
"@sanity/client": "^7",
|
|
135
|
-
"@shopify/hydrogen": "~2025.5.0",
|
|
135
|
+
"@shopify/hydrogen": "~2025.5.0 || ~2025.7.0",
|
|
136
136
|
"react": "^18.2.0",
|
|
137
|
-
"react-router": "7.
|
|
137
|
+
"react-router": "7.9.2",
|
|
138
138
|
"vite": "^5.1.0 || ^6.2.1"
|
|
139
139
|
},
|
|
140
140
|
"engines": {
|
package/src/Query.tsx
CHANGED
|
@@ -1,15 +1,7 @@
|
|
|
1
1
|
import type {Any, ClientReturn, QueryParams, QueryWithoutParams} from '@sanity/client'
|
|
2
2
|
import type {EncodeDataAttributeFunction} from '@sanity/core-loader/encode-data-attribute'
|
|
3
3
|
import type {QueryResponseInitial} from '@sanity/react-loader'
|
|
4
|
-
import {
|
|
5
|
-
lazy,
|
|
6
|
-
type ReactNode,
|
|
7
|
-
startTransition,
|
|
8
|
-
Suspense,
|
|
9
|
-
type SuspenseProps,
|
|
10
|
-
useEffect,
|
|
11
|
-
useState,
|
|
12
|
-
} from 'react'
|
|
4
|
+
import {lazy, type ReactNode, Suspense, type SuspenseProps, useSyncExternalStore} from 'react'
|
|
13
5
|
|
|
14
6
|
import type {LoadQueryOptions} from './context'
|
|
15
7
|
import {usePreviewMode} from './preview/hooks'
|
|
@@ -23,6 +15,19 @@ function SanityQueryFallback(): ReactNode {
|
|
|
23
15
|
return null
|
|
24
16
|
}
|
|
25
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Simple hydration store to avoid hydration mismatches.
|
|
20
|
+
* Returns false on server, true on client after hydration.
|
|
21
|
+
*/
|
|
22
|
+
function useIsHydrated(): boolean {
|
|
23
|
+
return useSyncExternalStore(
|
|
24
|
+
// eslint-disable-next-line no-empty-function
|
|
25
|
+
() => () => {},
|
|
26
|
+
() => true,
|
|
27
|
+
() => false,
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
26
31
|
const QueryClient = isServer()
|
|
27
32
|
? SanityQueryFallback
|
|
28
33
|
: (lazy(
|
|
@@ -66,14 +71,7 @@ export function Query<Result = Any, Query extends string = string>({
|
|
|
66
71
|
...suspenseProps
|
|
67
72
|
}: QueryProps<Result, Query> & Omit<SuspenseProps, 'children'>): ReactNode {
|
|
68
73
|
const isPreviewMode = usePreviewMode()
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
// Mark component as hydrated after initial render to prevent hydration mismatches
|
|
72
|
-
useEffect(() => {
|
|
73
|
-
startTransition(() => {
|
|
74
|
-
setIsHydrated(true)
|
|
75
|
-
})
|
|
76
|
-
}, [])
|
|
74
|
+
const isHydrated = useIsHydrated()
|
|
77
75
|
|
|
78
76
|
// If in preview mode and hydrated, render the client component
|
|
79
77
|
if (isPreviewMode && isHydrated) {
|
package/src/provider.tsx
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
1
|
import type {InitializedClientConfig} from '@sanity/client'
|
|
3
2
|
import type {HTMLProps, PropsWithChildren, ReactNode} from 'react'
|
|
4
|
-
import {useEffect} from 'react'
|
|
5
3
|
|
|
6
4
|
/**
|
|
7
5
|
* Contains essential Sanity client configuration and preview/stega state.
|
|
@@ -22,7 +20,7 @@ export interface SanityProviderValue
|
|
|
22
20
|
* Throws an error if the provider value is missing or invalid.
|
|
23
21
|
*/
|
|
24
22
|
export function assertSanityProviderValue(value: unknown): value is SanityProviderValue {
|
|
25
|
-
if (
|
|
23
|
+
if (typeof value === 'undefined') {
|
|
26
24
|
throw new Error(
|
|
27
25
|
'Failed to find a Sanity provider value. Did you forget to wrap your app in a `SanityProvider`?',
|
|
28
26
|
)
|
|
@@ -36,13 +34,10 @@ export function assertSanityProviderValue(value: unknown): value is SanityProvid
|
|
|
36
34
|
* Must be used within a SanityProvider component tree.
|
|
37
35
|
*/
|
|
38
36
|
export function useSanityProviderValue(): SanityProviderValue {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
] as SanityProviderValue | null
|
|
42
|
-
|
|
37
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
38
|
+
const providerValue = (globalThis as any)[Symbol.for('Sanity Provider')]
|
|
43
39
|
assertSanityProviderValue(providerValue)
|
|
44
|
-
|
|
45
|
-
return providerValue!
|
|
40
|
+
return providerValue
|
|
46
41
|
}
|
|
47
42
|
|
|
48
43
|
/**
|
|
@@ -53,10 +48,7 @@ export function SanityProvider({
|
|
|
53
48
|
value,
|
|
54
49
|
children,
|
|
55
50
|
}: PropsWithChildren<{value: SanityProviderValue}>): ReactNode {
|
|
56
|
-
|
|
57
|
-
setProviderValue(value)
|
|
58
|
-
}, [value])
|
|
59
|
-
|
|
51
|
+
setProviderValue(value)
|
|
60
52
|
return <>{children}</>
|
|
61
53
|
}
|
|
62
54
|
|
|
@@ -66,7 +58,6 @@ export function SanityProvider({
|
|
|
66
58
|
*/
|
|
67
59
|
export function Sanity(props: SanityProps): ReactNode {
|
|
68
60
|
const providerValue = useSanityProviderValue()
|
|
69
|
-
|
|
70
61
|
assertSanityProviderValue(providerValue)
|
|
71
62
|
|
|
72
63
|
return (
|
|
@@ -98,5 +89,6 @@ export type SanityProps = Omit<
|
|
|
98
89
|
>
|
|
99
90
|
|
|
100
91
|
function setProviderValue(value: SanityProviderValue) {
|
|
92
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
101
93
|
;(globalThis as any)[Symbol.for('Sanity Provider')] = Object.freeze(value)
|
|
102
94
|
}
|