hydrogen-sanity 1.1.0 → 2.0.1

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 CHANGED
@@ -63,6 +63,9 @@ export default () => {
63
63
  ? {
64
64
  session: previewSession,
65
65
  token: env.SANITY_API_TOKEN,
66
+ // Optionally, provide an alternative to the default `previewDrafts` perspective when in preview mode
67
+ // See https://www.sanity.io/docs/perspectives
68
+ // perspective: "raw"
66
69
  }
67
70
  : undefined,
68
71
  // Pass configuration options for Sanity client
@@ -71,7 +74,7 @@ export default () => {
71
74
  dataset: env.SANITY_DATASET,
72
75
  apiVersion: env.SANITY_API_VERSION ?? '2023-03-30',
73
76
  useCdn: process.env.NODE_ENV === 'production',
74
- },
77
+ }
75
78
  });
76
79
 
77
80
  // 3. Add Sanity client to the request handler inside getLoadContext
@@ -176,16 +179,10 @@ First setup your root route to enable preview mode across the entire application
176
179
  // ./app/root.tsx
177
180
 
178
181
  // ...other imports
179
- import {Preview, type PreviewData, isPreviewModeEnabled} from 'hydrogen-sanity'
182
+ import {PreviewProvider, getPreview} from 'hydrogen-sanity'
180
183
 
181
184
  export async function loader({context}: LoaderArgs) {
182
- const preview: PreviewData | undefined = isPreviewModeEnabled(context.sanity.preview)
183
- ? {
184
- projectId: context.sanity.preview.projectId,
185
- dataset: context.sanity.preview.dataset,
186
- token: context.sanity.preview.token,
187
- }
188
- : undefined
185
+ const preview = getPreview(context)
189
186
 
190
187
  return json({
191
188
  // ... other loader data
@@ -205,8 +202,8 @@ export default function App() {
205
202
  <Links />
206
203
  </head>
207
204
  <body>
208
- {/* 👇 Wrap <Outlet /> in Preview component */}
209
- <Preview preview={preview}>
205
+ {/* 👇 Wrap <Outlet /> in PreviewProvider component */}
206
+ <PreviewProvider {...preview}>
210
207
  <Outlet />
211
208
  </Preview>
212
209
  <ScrollRestoration />
@@ -217,18 +214,20 @@ export default function App() {
217
214
  }
218
215
  ```
219
216
 
220
- You can also pass a `ReactNode` to render a loading indicator or adjust the default message:
217
+ `PreviewProvider` wraps the `LiveQueryProvider` component of `@sanity/preview-kit` - props passed to `PreviewProvider` will be passed to `LiveQueryProvider`. For more information, see the [`@sanity/preview-kit` documentation](https://github.com/sanity-io/preview-kit).
218
+
219
+ By default, `PreviewProvider` will passthrough rendering to its children if you don't provide a fallback; however you can also pass a `ReactNode` to render a loading indicator or message:
221
220
 
222
221
  ```tsx
223
222
  import {PreviewLoading} from '~/components/PreviewLoading';
224
223
 
225
- // pass a string or your own React component to show while data is loading
226
- <Preview preview={preview} fallback={<PreviewLoading />}>
224
+ // (Optional) pass a string or your own React component to show while data is loading
225
+ <PreviewProvider {...preview} fallback={<PreviewLoading />}>
227
226
  ```
228
227
 
229
- Next, for any route that needs to render a preview, wrap it in a `Preview` component which re-runs the same query client-side but will render draft content in place of published content, if it exists. Updating in real-time as changes are streamed in.
228
+ Next, for any route that needs to render a preview, wrap it in a `SanityPreview` component which re-runs the same query client-side but will render draft content in place of published content, if it exists. Updating in real-time as changes are streamed in.
230
229
 
231
- The `usePreview` hook conditionally renders the preview component if the preview session is found, otherwise, it renders the default component.
230
+ The component will be rendered with live preview if the preview session is found, otherwise, it renders the component with static content.
232
231
 
233
232
  ```tsx
234
233
  // Any route file, such as ./app/routes/index.tsx
@@ -295,8 +294,11 @@ export const loader: LoaderFunction = async function ({request, context}) {
295
294
 
296
295
  ## Limits
297
296
 
298
- The real-time preview isn't optimized and comes with a configured limit of 3000 documents. You can experiment with larger datasets by configuring the hook with `documentLimit: <Integer>`. Be aware that this might significantly affect the preview performance.
299
- You may use the `includeTypes` option to reduce the amount of documents and reduce the risk of hitting the `documentLimit`:
297
+ The real-time preview comes with a configured limit of 3000 documents. You can experiment with larger datasets by configuring `cache.maxDocuments: <Integer>` in your `PreviewProvider`. Be aware that this might affect the preview performance.
298
+
299
+ You can also use the `cache.includeTypes` option to reduce the amount of documents and reduce the risk of hitting the document limit.
300
+
301
+ If you're a Sanity Enterprise user with Content Source Maps enabled, you can optimize further by enabling `turboSourceMap` which opts-in to a faster and smarter cache. It'll only listen for mutations on the documents that you are using in your queries, and apply the mutations to the cache in real-time.
300
302
 
301
303
  ## Using `@sanity/client` directly
302
304
 
package/dist/index.d.ts CHANGED
@@ -1,9 +1,11 @@
1
+ /// <reference types="react" />
2
+
1
3
  import type {CacheShort} from '@shopify/hydrogen'
2
4
  import {ClientConfig} from '@sanity/client'
3
- import {ElementType} from 'react'
5
+ import {ClientPerspective} from '@sanity/client'
4
6
  import {JSX as JSX_2} from 'react/jsx-runtime'
5
- import {Params} from '@sanity/preview-kit'
6
- import {ReactFragment} from 'react'
7
+ import type {LiveQueryProviderProps} from '@sanity/preview-kit'
8
+ import {QueryParams} from '@sanity/client'
7
9
  import {ReactNode} from 'react'
8
10
  import {SanityClient} from '@sanity/client'
9
11
  import {Session} from '@shopify/remix-oxygen'
@@ -22,6 +24,7 @@ declare type CreateSanityClientOptions = EnvironmentOptions & {
22
24
  preview?: {
23
25
  session: PreviewSession
24
26
  token: string
27
+ perspective?: ClientPerspective
25
28
  }
26
29
  }
27
30
 
@@ -42,38 +45,40 @@ declare interface ExecutionContext {
42
45
  waitUntil(promise: Promise<any>): void
43
46
  }
44
47
 
48
+ /** TODO: inline documentation */
49
+ export declare function getPreview<
50
+ T extends {
51
+ sanity: Sanity
52
+ }
53
+ >(context: T): ClientConfig | undefined
54
+
45
55
  export declare function isPreviewModeEnabled(preview?: Sanity['preview']): preview is {
46
56
  session: PreviewSession
47
- } & PreviewData
48
-
49
- /**
50
- * Conditionally apply `PreviewSuspense` boundary
51
- * @see https://www.sanity.io/docs/preview-content-on-site
52
- */
53
- export declare function Preview(props: PreviewProps): JSX_2.Element
54
-
55
- export declare type PreviewData = {
56
57
  projectId: string
57
58
  dataset: string
58
59
  token: string
59
60
  }
60
61
 
61
- declare type PreviewDataProps<T> = {
62
+ declare type PreviewProps<T> = {
62
63
  data: T
63
- children: ReactNode | ((data: T | null) => ReactNode)
64
+ children: ReactNode | ((data?: T | null) => ReactNode)
64
65
  query?: string | null
65
- params?: Params
66
+ params?: QueryParams
66
67
  }
67
68
 
68
- export declare type PreviewProps = {
69
- children: ReactNode
70
- fallback?: ReactNode
71
- preview?: PreviewData
72
- }
69
+ /**
70
+ * TODO: inline documentation
71
+ * @see https://www.sanity.io/docs/preview-content-on-site
72
+ */
73
+ export declare function PreviewProvider(
74
+ props: SanityPreviewProps
75
+ ): string | number | boolean | Iterable<ReactNode> | JSX_2.Element | null | undefined
73
76
 
77
+ /**
78
+ * TODO: needs inline documentation
79
+ */
74
80
  export declare class PreviewSession {
75
- private sessionStorage
76
- private session
81
+ #private
77
82
  constructor(sessionStorage: SessionStorage, session: Session)
78
83
  static init(request: Request, secrets: string[]): Promise<PreviewSession>
79
84
  has(key: string): boolean
@@ -85,9 +90,12 @@ export declare class PreviewSession {
85
90
  export declare type Sanity = {
86
91
  client: SanityClient
87
92
  preview?:
88
- | ({
93
+ | {
89
94
  session: PreviewSession
90
- } & PreviewData)
95
+ projectId: string
96
+ dataset: string
97
+ token: string
98
+ }
91
99
  | {
92
100
  session: PreviewSession
93
101
  }
@@ -101,8 +109,13 @@ export declare type Sanity = {
101
109
  * changes will be streamed in the client
102
110
  */
103
111
  export declare function SanityPreview<T = unknown>(
104
- props: PreviewDataProps<T>
105
- ): string | number | boolean | ReactFragment | JSX_2.Element | null | undefined
112
+ props: PreviewProps<T>
113
+ ): string | number | boolean | Iterable<ReactNode> | JSX_2.Element | null | undefined
114
+
115
+ declare type SanityPreviewProps = Omit<LiveQueryProviderProps, 'client'> & {
116
+ fallback?: ReactNode
117
+ previewConfig?: ClientConfig
118
+ }
106
119
 
107
120
  /**
108
121
  * Create an SHA-256 hash as a hex string
@@ -110,34 +123,16 @@ export declare function SanityPreview<T = unknown>(
110
123
  */
111
124
  export declare function sha256(message: string): Promise<string>
112
125
 
113
- declare type UsePreview = <R = any, P extends Params = Params, Q extends string = string>(
114
- query: Q,
115
- params?: P,
116
- serverSnapshot?: R
117
- ) => R
118
-
119
- /**
120
- * Select and memoize which component to render based on preview mode
121
- * @deprecated use `SanityPreview` instead
122
- */
123
- export declare function usePreviewComponent<T>(
124
- component: ElementType<T>,
125
- preview: ElementType<T>
126
- ): ElementType<T>
127
-
126
+ /** TODO: needs inline documentation */
128
127
  export declare const usePreviewContext: () =>
129
128
  | {
130
- /**
131
- * Query Sanity and subscribe to changes, optionally
132
- * passing a server snapshot to speed up hydration
133
- */
134
- usePreview: UsePreview
129
+ projectId: string
135
130
  }
136
131
  | undefined
137
132
 
138
133
  declare type useSanityQuery = {
139
134
  query: string
140
- params?: Record<string, unknown>
135
+ params?: QueryParams
141
136
  cache?: CachingStrategy
142
137
  }
143
138
 
package/dist/index.js CHANGED
@@ -1,202 +1 @@
1
- 'use strict';
2
-
3
- Object.defineProperty(exports, '__esModule', {
4
- value: true
5
- });
6
- var client = require('@sanity/client');
7
- var hydrogen = require('@shopify/hydrogen');
8
- var jsxRuntime = require('react/jsx-runtime');
9
- var previewKit = require('@sanity/preview-kit');
10
- var remixOxygen = require('@shopify/remix-oxygen');
11
- var react = require('react');
12
- function createSanityClient(options) {
13
- const {
14
- cache,
15
- waitUntil,
16
- preview,
17
- config
18
- } = options;
19
- const withCache = hydrogen.createWithCache_unstable({
20
- cache,
21
- waitUntil
22
- });
23
- const sanity = {
24
- client: client.createClient(config),
25
- async query(_ref) {
26
- let {
27
- query,
28
- params,
29
- cache: strategy = hydrogen.CacheLong()
30
- } = _ref;
31
- const queryHash = await hashQuery(query, params);
32
- return withCache(queryHash, strategy, () => sanity.client.fetch(query, params));
33
- }
34
- };
35
- if (preview) {
36
- sanity.preview = {
37
- session: preview.session
38
- };
39
- if (preview.session.has("projectId")) {
40
- sanity.preview = {
41
- ...sanity.preview,
42
- projectId: config.projectId,
43
- dataset: config.dataset,
44
- token: preview.token
45
- };
46
- sanity.client = sanity.client.withConfig({
47
- useCdn: false,
48
- token: preview.token
49
- });
50
- sanity.query = _ref2 => {
51
- let {
52
- query,
53
- params
54
- } = _ref2;
55
- return sanity.client.fetch(query, params);
56
- };
57
- }
58
- }
59
- return sanity;
60
- }
61
- function isPreviewModeEnabled(preview) {
62
- return preview && preview.token !== null;
63
- }
64
- async function sha256(message) {
65
- const messageBuffer = await new TextEncoder().encode(message);
66
- const hashBuffer = await crypto.subtle.digest("SHA-256", messageBuffer);
67
- return Array.from(new Uint8Array(hashBuffer)).map(b => b.toString(16).padStart(2, "0")).join("");
68
- }
69
- function hashQuery(query, params) {
70
- let hash = query;
71
- if (params !== null) {
72
- hash += JSON.stringify(params);
73
- }
74
- return sha256(hash);
75
- }
76
- class PreviewSession {
77
- // eslint-disable-next-line no-useless-constructor, no-empty-function
78
- constructor(sessionStorage, session) {
79
- this.sessionStorage = sessionStorage;
80
- this.session = session;
81
- }
82
- static async init(request, secrets) {
83
- const storage = remixOxygen.createCookieSessionStorage({
84
- cookie: {
85
- name: "__preview",
86
- httpOnly: true,
87
- sameSite: true,
88
- secrets
89
- }
90
- });
91
- const session = await storage.getSession(request.headers.get("Cookie"));
92
- return new this(storage, session);
93
- }
94
- has(key) {
95
- return this.session.has(key);
96
- }
97
- // get(key: string) {
98
- // return this.session.get(key);
99
- // }
100
- destroy() {
101
- return this.sessionStorage.destroySession(this.session);
102
- }
103
- // unset(key: string) {
104
- // this.session.unset(key);
105
- // }
106
- set(key, value) {
107
- this.session.set(key, value);
108
- }
109
- commit() {
110
- return this.sessionStorage.commitSession(this.session);
111
- }
112
- }
113
- const PreviewContext = react.createContext(void 0);
114
- const usePreviewContext = () => react.useContext(PreviewContext);
115
- function Preview(props) {
116
- var _a;
117
- const {
118
- children,
119
- preview
120
- } = props;
121
- if (!(preview == null ? void 0 : preview.token)) {
122
- return /* @__PURE__ */jsxRuntime.jsx(jsxRuntime.Fragment, {
123
- children
124
- });
125
- }
126
- const fallback = (_a = props.fallback) != null ? _a : /* @__PURE__ */jsxRuntime.jsx("div", {
127
- children: "Loading preview..."
128
- });
129
- const {
130
- projectId,
131
- dataset,
132
- token
133
- } = preview;
134
- const _usePreview = previewKit.definePreview({
135
- projectId,
136
- dataset,
137
- overlayDrafts: true
138
- });
139
- function usePreview(query, params, serverSnapshot) {
140
- return _usePreview(token, query, params, serverSnapshot);
141
- }
142
- return /* @__PURE__ */jsxRuntime.jsx(PreviewContext.Provider, {
143
- value: {
144
- usePreview
145
- },
146
- children: /* @__PURE__ */jsxRuntime.jsx(previewKit.PreviewSuspense, {
147
- fallback,
148
- children
149
- })
150
- });
151
- }
152
- function usePreviewComponent(component, preview) {
153
- const isPreview = Boolean(usePreviewContext());
154
- return react.useMemo(() => isPreview ? preview : component, [component, isPreview, preview]);
155
- }
156
- function SanityPreview(props) {
157
- const {
158
- data,
159
- children,
160
- query,
161
- params
162
- } = props;
163
- const isPreview = Boolean(usePreviewContext());
164
- if (typeof children !== "function") {
165
- return children;
166
- }
167
- if (isPreview && query) {
168
- return /* @__PURE__ */jsxRuntime.jsx(ResolvePreview, {
169
- query,
170
- params,
171
- serverSnapshot: data,
172
- children
173
- });
174
- }
175
- return /* @__PURE__ */jsxRuntime.jsx(jsxRuntime.Fragment, {
176
- children: children(data)
177
- });
178
- }
179
- function ResolvePreview(props) {
180
- const {
181
- serverSnapshot,
182
- query,
183
- params,
184
- children
185
- } = props;
186
- const {
187
- usePreview
188
- } = usePreviewContext();
189
- const data = usePreview(query, params, serverSnapshot);
190
- return /* @__PURE__ */jsxRuntime.jsx(jsxRuntime.Fragment, {
191
- children: children(data)
192
- });
193
- }
194
- exports.Preview = Preview;
195
- exports.PreviewSession = PreviewSession;
196
- exports.SanityPreview = SanityPreview;
197
- exports.createSanityClient = createSanityClient;
198
- exports.isPreviewModeEnabled = isPreviewModeEnabled;
199
- exports.sha256 = sha256;
200
- exports.usePreviewComponent = usePreviewComponent;
201
- exports.usePreviewContext = usePreviewContext;
202
- //# sourceMappingURL=index.js.map
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("@sanity/client"),t=require("@shopify/hydrogen"),r=require("react"),i=require("react/jsx-runtime"),n=require("@shopify/remix-oxygen"),s=require("@sanity/preview-kit");function a(e){return Boolean(e&&e.token&&null!==e.token)}async function o(e){const t=await(new TextEncoder).encode(e),r=await crypto.subtle.digest("SHA-256",t);return Array.from(new Uint8Array(r)).map((e=>e.toString(16).padStart(2,"0"))).join("")}const c=r.createContext(void 0),u=()=>r.useContext(c);const l=r.lazy((()=>import("@sanity/preview-kit").then((e=>({default:e.LiveQueryProvider})))));var p,d,h=(e,t,r)=>{if(!t.has(e))throw TypeError("Cannot "+r)},y=(e,t,r)=>(h(e,t,"read from private field"),r?r.call(e):t.get(e)),f=(e,t,r)=>{if(t.has(e))throw TypeError("Cannot add the same private member more than once");t instanceof WeakSet?t.add(e):t.set(e,r)},v=(e,t,r,i)=>(h(e,t,"write to private field"),i?i.call(e,r):t.set(e,r),r);function w(e){const{initialData:t,query:r,params:n,children:a}=e,[o]=s.useLiveQuery(t,r,n);return i.jsx(i.Fragment,{children:a(o)})}p=new WeakMap,d=new WeakMap,exports.PreviewProvider=function(t){const{children:n,previewConfig:s,fallback:a=n,...o}=t,[,u]=r.useTransition(),[p,d]=r.useState(!1);if(r.useEffect((()=>u((()=>d(!0)))),[]),!p||!s||!s.projectId)return n;const h=e.createClient(s);return i.jsx(c.Provider,{value:{projectId:s.projectId},children:i.jsx(r.Suspense,{fallback:a,children:i.jsx(l,{...o,client:h,children:n})})})},exports.PreviewSession=class{constructor(e,t){f(this,p,void 0),f(this,d,void 0),v(this,p,e),v(this,d,t)}static async init(e,t){const r=n.createCookieSessionStorage({cookie:{name:"__preview",httpOnly:!0,sameSite:!0,secrets:t}}),i=await r.getSession(e.headers.get("Cookie"));return new this(r,i)}has(e){return y(this,d).has(e)}destroy(){return y(this,p).destroySession(y(this,d))}set(e,t){y(this,d).set(e,t)}commit(){return y(this,p).commitSession(y(this,d))}},exports.SanityPreview=function(e){const{data:t,children:r,query:n,params:s}=e,a=Boolean(u());return"function"!=typeof r?r:a&&n?i.jsx(w,{query:n,params:s,initialData:t,children:r}):i.jsx(i.Fragment,{children:r(t)})},exports.createSanityClient=function(r){const{cache:i,waitUntil:n,preview:s,config:a}=r,c={client:e.createClient(a),async query(e){let{query:r,params:s,cache:a=t.CacheLong()}=e;const u=await function(e,t){let r=e;null!==t&&(r+=JSON.stringify(t));return o(r)}(r,s);return t.createWithCache_unstable({cache:i,waitUntil:n})(u,a,(()=>c.client.fetch(r,s)))}};return s&&(c.preview={session:s.session},s.session.has("projectId")&&(c.preview={...c.preview,projectId:a.projectId,dataset:a.dataset,token:s.token},c.client=c.client.withConfig({useCdn:!1,token:s.token,perspective:s.perspective||"previewDrafts",ignoreBrowserTokenWarning:!0}),c.query=e=>{let{query:t,params:r}=e;return c.client.fetch(t,r)})),c},exports.getPreview=function(e){return a(e.sanity.preview)?{...e.sanity.client.config()}:void 0},exports.isPreviewModeEnabled=a,exports.sha256=o,exports.usePreviewContext=u;//# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/client.ts","../src/preview.tsx"],"sourcesContent":["import {type ClientConfig, createClient, type SanityClient} from '@sanity/client'\n// eslint-disable-next-line camelcase\nimport {CacheLong, createWithCache_unstable} from '@shopify/hydrogen'\n\nimport type {PreviewData, PreviewSession} from './preview'\nimport type {CachingStrategy, EnvironmentOptions} from './types'\n\ntype CreateSanityClientOptions = EnvironmentOptions & {\n config: ClientConfig & Required<Pick<ClientConfig, 'projectId' | 'dataset'>>\n preview?: {\n session: PreviewSession\n token: string\n }\n}\n\ntype useSanityQuery = {\n query: string\n params?: Record<string, unknown>\n cache?: CachingStrategy\n}\n\nexport type Sanity = {\n client: SanityClient\n preview?: ({session: PreviewSession} & PreviewData) | {session: PreviewSession}\n query<T>(options: useSanityQuery): Promise<T>\n}\n\n/**\n * Create Sanity provider with API client.\n */\nexport function createSanityClient(options: CreateSanityClientOptions): Sanity {\n const {cache, waitUntil, preview, config} = options\n const withCache = createWithCache_unstable({\n cache,\n waitUntil,\n })\n\n const sanity: Sanity = {\n client: createClient(config),\n async query({query, params, cache: strategy = CacheLong()}) {\n const queryHash = await hashQuery(query, params)\n\n return withCache(queryHash, strategy, () => sanity.client.fetch(query, params))\n },\n }\n\n if (preview) {\n sanity.preview = {session: preview.session}\n\n if (preview.session.has('projectId')) {\n sanity.preview = {\n ...sanity.preview,\n projectId: config.projectId,\n dataset: config.dataset,\n token: preview.token,\n }\n\n sanity.client = sanity.client.withConfig({\n useCdn: false,\n token: preview.token,\n })\n\n sanity.query = ({query, params}) => {\n return sanity.client.fetch(query, params)\n }\n }\n }\n\n return sanity\n}\n\nexport function isPreviewModeEnabled(\n preview?: Sanity['preview']\n): preview is {session: PreviewSession} & PreviewData {\n // @ts-expect-error\n return preview && preview.token !== null\n}\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 */\nfunction hashQuery(\n query: useSanityQuery['query'],\n params: useSanityQuery['params']\n): Promise<string> {\n let hash = query\n\n if (params !== null) {\n hash += JSON.stringify(params)\n }\n\n return sha256(hash)\n}\n","/* eslint-disable react/require-default-props */\nimport {definePreview, type Params, PreviewSuspense} from '@sanity/preview-kit'\nimport {createCookieSessionStorage, type Session, type SessionStorage} from '@shopify/remix-oxygen'\nimport {createContext, ElementType, type ReactNode, useContext, useMemo} from 'react'\n\ntype UsePreview = <R = any, P extends Params = Params, Q extends string = string>(\n query: Q,\n params?: P,\n serverSnapshot?: R\n) => R\n\nexport type PreviewData = {\n projectId: string\n dataset: string\n token: string\n}\n\nexport type PreviewProps = {\n children: ReactNode\n fallback?: ReactNode\n preview?: PreviewData\n}\n\nexport class PreviewSession {\n // eslint-disable-next-line no-useless-constructor, no-empty-function\n constructor(private sessionStorage: SessionStorage, private session: Session) {}\n\n static async init(request: Request, secrets: string[]) {\n const storage = createCookieSessionStorage({\n cookie: {\n name: '__preview',\n httpOnly: true,\n sameSite: true,\n secrets,\n },\n })\n\n const session = await storage.getSession(request.headers.get('Cookie'))\n\n return new this(storage, session)\n }\n\n has(key: string) {\n return this.session.has(key)\n }\n\n // get(key: string) {\n // return this.session.get(key);\n // }\n\n destroy() {\n return this.sessionStorage.destroySession(this.session)\n }\n\n // unset(key: string) {\n // this.session.unset(key);\n // }\n\n set(key: string, value: any) {\n this.session.set(key, value)\n }\n\n commit() {\n return this.sessionStorage.commitSession(this.session)\n }\n}\n\nconst PreviewContext = createContext<\n | {\n /**\n * Query Sanity and subscribe to changes, optionally\n * passing a server snapshot to speed up hydration\n */\n usePreview: UsePreview\n }\n | undefined\n>(undefined)\n\nexport const usePreviewContext = () => useContext(PreviewContext)\n\n/**\n * Conditionally apply `PreviewSuspense` boundary\n * @see https://www.sanity.io/docs/preview-content-on-site\n */\nexport function Preview(props: PreviewProps) {\n const {children, preview} = props\n\n if (!preview?.token) {\n return <>{children}</>\n }\n\n const fallback = props.fallback ?? <div>Loading preview...</div>\n const {projectId, dataset, token} = preview\n const _usePreview = definePreview({\n projectId,\n dataset,\n overlayDrafts: true,\n })\n\n function usePreview<R = any, P extends Params = Params, Q extends string = string>(\n query: Q,\n params?: P,\n serverSnapshot?: R\n ): R {\n return _usePreview(token, query, params, serverSnapshot)\n }\n usePreview satisfies UsePreview\n\n return (\n <PreviewContext.Provider value={{usePreview}}>\n <PreviewSuspense fallback={fallback}>{children}</PreviewSuspense>\n </PreviewContext.Provider>\n )\n}\n\n/**\n * Select and memoize which component to render based on preview mode\n * @deprecated use `SanityPreview` instead\n */\nexport function usePreviewComponent<T>(component: ElementType<T>, preview: ElementType<T>) {\n const isPreview = Boolean(usePreviewContext())\n\n return useMemo(() => (isPreview ? preview : component), [component, isPreview, preview])\n}\n\ntype PreviewDataProps<T> = {\n data: T\n children: ReactNode | ((data: T | null) => ReactNode)\n query?: string | null\n params?: Params\n}\n\n/**\n * Component to use for rendering in preview mode\n *\n * When provided a Sanity query and render prop,\n * changes will be streamed in the client\n */\nexport function SanityPreview<T = unknown>(props: PreviewDataProps<T>) {\n const {data, children, query, params} = props\n const isPreview = Boolean(usePreviewContext())\n\n if (typeof children !== 'function') {\n return children\n }\n\n if (isPreview && query) {\n return (\n <ResolvePreview<typeof data> query={query} params={params} serverSnapshot={data}>\n {children}\n </ResolvePreview>\n )\n }\n\n return <>{children(data)}</>\n}\n\ntype ResolvePreviewProps<T> = {\n serverSnapshot?: T | null\n query: string\n params?: Params\n children: (data: T | null) => ReactNode\n}\n\n/**\n * Subscribe to live preview and delegate rendering to consumer\n */\nfunction ResolvePreview<T = unknown>(props: ResolvePreviewProps<T>) {\n const {serverSnapshot, query, params, children} = props\n // This won't break the conditional rule of hooks,\n // **but** it relies on the assumption that this component\n // will only be used in preview mode 👇🏻\n const {usePreview} = usePreviewContext()!\n const data = usePreview(query, params, serverSnapshot)\n\n return <>{children(data)}</>\n}\n"],"names":["createSanityClient","options","cache","waitUntil","preview","config","withCache","createWithCache_unstable","sanity","client","createClient","query","params","strategy","CacheLong","_ref","queryHash","hashQuery","fetch","session","has","projectId","dataset","token","withConfig","useCdn","_ref2","isPreviewModeEnabled","sha256","message","messageBuffer","TextEncoder","encode","hashBuffer","crypto","subtle","digest","Array","from","Uint8Array","map","b","toString","padStart","join","hash","JSON","stringify","PreviewSession","constructor","sessionStorage","init","request","secrets","storage","createCookieSessionStorage","cookie","name","httpOnly","sameSite","getSession","headers","get","key","destroy","destroySession","set","value","commit","commitSession","PreviewContext","createContext","usePreviewContext","useContext","Preview","props","_a","children","fallback","jsx","_usePreview","definePreview","overlayDrafts","usePreview","serverSnapshot","Provider","PreviewSuspense","usePreviewComponent","component","isPreview","Boolean","useMemo","SanityPreview","data","ResolvePreview","Fragment"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../src/client.ts","../src/preview/context.tsx","../src/preview/PreviewProvider.tsx","../src/preview/PreviewSession.ts","../src/preview/SanityPreview.tsx","../src/preview/getPreview.ts"],"sourcesContent":["import {\n type ClientConfig,\n type ClientPerspective,\n createClient,\n type QueryParams,\n type SanityClient,\n} from '@sanity/client'\n// eslint-disable-next-line camelcase\nimport {CacheLong, createWithCache_unstable} from '@shopify/hydrogen'\n\nimport type {PreviewSession} from './preview'\nimport type {CachingStrategy, EnvironmentOptions} from './types'\n\ntype CreateSanityClientOptions = EnvironmentOptions & {\n config: ClientConfig & Required<Pick<ClientConfig, 'projectId' | 'dataset'>>\n preview?: {\n session: PreviewSession\n token: string\n perspective?: ClientPerspective\n }\n}\n\ntype useSanityQuery = {\n query: string\n params?: QueryParams\n cache?: CachingStrategy\n}\n\nexport type Sanity = {\n client: SanityClient\n preview?:\n | {session: PreviewSession; projectId: string; dataset: string; token: string}\n | {session: PreviewSession}\n query<T>(options: useSanityQuery): Promise<T>\n}\n\n/**\n * Create Sanity provider with API client.\n */\nexport function createSanityClient(options: CreateSanityClientOptions): Sanity {\n const {cache, waitUntil, preview, config} = options\n\n const sanity: Sanity = {\n client: createClient(config),\n async query<T = any>({query, params, cache: strategy = CacheLong()}: useSanityQuery) {\n const queryHash = await hashQuery(query, params)\n const withCache = createWithCache_unstable<T>({\n cache,\n waitUntil,\n })\n\n return withCache(queryHash, strategy, () => sanity.client.fetch(query, params))\n },\n }\n\n if (preview) {\n sanity.preview = {session: preview.session}\n\n if (preview.session.has('projectId')) {\n sanity.preview = {\n ...sanity.preview,\n projectId: config.projectId,\n dataset: config.dataset,\n token: preview.token,\n }\n\n sanity.client = sanity.client.withConfig({\n useCdn: false,\n token: preview.token,\n perspective: preview.perspective || 'previewDrafts',\n ignoreBrowserTokenWarning: true,\n })\n\n sanity.query = ({query, params}) => {\n return sanity.client.fetch(query, params)\n }\n }\n }\n\n return sanity\n}\n\nexport function isPreviewModeEnabled(\n preview?: Sanity['preview']\n): preview is {session: PreviewSession; projectId: string; dataset: string; token: string} {\n // @ts-expect-error\n return Boolean(preview && preview.token && preview.token !== null)\n}\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 */\nfunction hashQuery(\n query: useSanityQuery['query'],\n params: useSanityQuery['params']\n): Promise<string> {\n let hash = query\n\n if (params !== null) {\n hash += JSON.stringify(params)\n }\n\n return sha256(hash)\n}\n","import {createContext, useContext} from 'react'\n\nexport const PreviewContext = createContext<{projectId: string} | undefined>(undefined)\n\n/** TODO: needs inline documentation */\nexport const usePreviewContext = () => useContext(PreviewContext)\n","/* eslint-disable react/require-default-props */\nimport {type ClientConfig, createClient} from '@sanity/client'\nimport type {LiveQueryProviderProps} from '@sanity/preview-kit'\nimport {lazy, type ReactNode, Suspense, useEffect, useState, useTransition} from 'react'\n\nimport {PreviewContext} from './context'\n\nconst LiveQueryProvider = lazy(() =>\n import('@sanity/preview-kit').then((m) => ({default: m.LiveQueryProvider}))\n)\n\ntype SanityPreviewProps = Omit<LiveQueryProviderProps, 'client'> & {\n fallback?: ReactNode\n previewConfig?: ClientConfig\n}\n\n/**\n * TODO: inline documentation\n * @see https://www.sanity.io/docs/preview-content-on-site\n */\nexport function PreviewProvider(props: SanityPreviewProps) {\n const {children, previewConfig, fallback = children, ...rest} = props\n\n const [, startTransition] = useTransition()\n const [hydrated, setHydrated] = useState(false)\n useEffect(() => startTransition(() => setHydrated(true)), [])\n\n if (!hydrated || !previewConfig || !previewConfig.projectId) {\n return children\n }\n\n const client = createClient(previewConfig)\n\n return (\n <PreviewContext.Provider value={{projectId: previewConfig.projectId}}>\n <Suspense fallback={fallback}>\n <LiveQueryProvider {...rest} client={client}>\n {children}\n </LiveQueryProvider>\n </Suspense>\n </PreviewContext.Provider>\n )\n}\n","import {createCookieSessionStorage, type Session, type SessionStorage} from '@shopify/remix-oxygen'\n\n/**\n * TODO: needs inline documentation\n */\nexport class PreviewSession {\n #sessionStorage: SessionStorage\n #session: Session\n\n constructor(sessionStorage: SessionStorage, session: Session) {\n this.#sessionStorage = sessionStorage\n this.#session = session\n }\n\n static async init(request: Request, secrets: string[]): Promise<PreviewSession> {\n const storage = createCookieSessionStorage({\n cookie: {\n name: '__preview',\n httpOnly: true,\n sameSite: true,\n secrets,\n },\n })\n\n const session = await storage.getSession(request.headers.get('Cookie'))\n\n return new this(storage, session)\n }\n\n has(key: string): boolean {\n return this.#session.has(key)\n }\n\n // get(key: string) {\n // return this.session.get(key);\n // }\n\n destroy(): Promise<string> {\n return this.#sessionStorage.destroySession(this.#session)\n }\n\n // unset(key: string) {\n // this.session.unset(key);\n // }\n\n // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types\n set(key: string, value: any): void {\n this.#session.set(key, value)\n }\n\n commit(): Promise<string> {\n return this.#sessionStorage.commitSession(this.#session)\n }\n}\n","/* eslint-disable react/require-default-props */\nimport type {QueryParams} from '@sanity/client'\nimport {useLiveQuery} from '@sanity/preview-kit'\nimport {type ReactNode} from 'react'\n\nimport {usePreviewContext} from './context'\n\ntype PreviewProps<T> = {\n data: T\n children: ReactNode | ((data?: T | null) => ReactNode)\n query?: string | null\n params?: QueryParams\n}\n\n/**\n * Component to use for rendering in preview mode\n *\n * When provided a Sanity query and render prop,\n * changes will be streamed in the client\n */\nexport function SanityPreview<T = unknown>(props: PreviewProps<T>) {\n const {data, children, query, params} = props\n const isPreview = Boolean(usePreviewContext())\n\n if (typeof children !== 'function') {\n return children\n }\n\n if (isPreview && query) {\n return (\n <ResolvePreview<typeof data> query={query} params={params} initialData={data}>\n {children}\n </ResolvePreview>\n )\n }\n\n return <>{children(data)}</>\n}\n\ntype ResolvePreviewProps<T> = {\n initialData?: T | null\n query: string\n params?: QueryParams\n children: (data?: T | null) => ReactNode\n}\n\n/**\n * Subscribe to live preview and delegate rendering to consumer\n */\nfunction ResolvePreview<T = unknown>(props: ResolvePreviewProps<T>) {\n const {initialData, query, params, children} = props\n const [data] = useLiveQuery(initialData, query, params)\n\n return <>{children(data)}</>\n}\n","import type {ClientConfig} from '@sanity/client'\n\nimport {isPreviewModeEnabled, Sanity} from '../client'\n\n/** TODO: inline documentation */\nexport function getPreview<T extends {sanity: Sanity}>(context: T): ClientConfig | undefined {\n return isPreviewModeEnabled(context.sanity.preview)\n ? {\n ...context.sanity.client.config(),\n }\n : undefined\n}\n"],"names":["isPreviewModeEnabled","preview","Boolean","token","async","sha256","message","messageBuffer","TextEncoder","encode","hashBuffer","crypto","subtle","digest","Array","from","Uint8Array","map","b","toString","padStart","join","PreviewContext","createContext","usePreviewContext","useContext","LiveQueryProvider","lazy","import","then","m","default","_sessionStorage","_session","ResolvePreview","props","initialData","query","params","children","data","useLiveQuery","jsx","Fragment","WeakMap","exports","PreviewProvider","previewConfig","fallback","rest","startTransition","useTransition","hydrated","setHydrated","useState","useEffect","projectId","client","createClient","Provider","value","Suspense","PreviewSession","constructor","sessionStorage","session","__privateAdd","this","__privateSet","static","request","secrets","storage","createCookieSessionStorage","cookie","name","httpOnly","sameSite","getSession","headers","get","has","key","__privateGet","destroy","destroySession","set","commit","commitSession","SanityPreview","isPreview","createSanityClient","options","cache","waitUntil","config","sanity","strategy","CacheLong","_ref","queryHash","hash","JSON","stringify","hashQuery","createWithCache_unstable","withCache","fetch","dataset","withConfig","useCdn","perspective","ignoreBrowserTokenWarning","_ref2","getPreview","context"],"mappings":"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"}
package/dist/index.mjs ADDED
@@ -0,0 +1 @@
1
+ import{createClient as e}from"@sanity/client";import{createWithCache_unstable as t,CacheLong as r}from"@shopify/hydrogen";import{createContext as i,useContext as n,lazy as o,useTransition as a,useState as s,useEffect as c,Suspense as p}from"react";import{jsx as l,Fragment as d}from"react/jsx-runtime";import{createCookieSessionStorage as h}from"@shopify/remix-oxygen";import{useLiveQuery as u}from"@sanity/preview-kit";function f(i){const{cache:n,waitUntil:o,preview:a,config:s}=i,c={client:e(s),async query(e){let{query:i,params:a,cache:s=r()}=e;const p=await function(e,t){let r=e;null!==t&&(r+=JSON.stringify(t));return y(r)}(i,a);return t({cache:n,waitUntil:o})(p,s,(()=>c.client.fetch(i,a)))}};return a&&(c.preview={session:a.session},a.session.has("projectId")&&(c.preview={...c.preview,projectId:s.projectId,dataset:s.dataset,token:a.token},c.client=c.client.withConfig({useCdn:!1,token:a.token,perspective:a.perspective||"previewDrafts",ignoreBrowserTokenWarning:!0}),c.query=e=>{let{query:t,params:r}=e;return c.client.fetch(t,r)})),c}function m(e){return Boolean(e&&e.token&&null!==e.token)}async function y(e){const t=await(new TextEncoder).encode(e),r=await crypto.subtle.digest("SHA-256",t);return Array.from(new Uint8Array(r)).map((e=>e.toString(16).padStart(2,"0"))).join("")}const w=i(void 0),v=()=>n(w);function k(e){return m(e.sanity.preview)?{...e.sanity.client.config()}:void 0}const g=o((()=>import("@sanity/preview-kit").then((e=>({default:e.LiveQueryProvider})))));function S(t){const{children:r,previewConfig:i,fallback:n=r,...o}=t,[,d]=a(),[h,u]=s(!1);if(c((()=>d((()=>u(!0)))),[]),!h||!i||!i.projectId)return r;const f=e(i);return l(w.Provider,{value:{projectId:i.projectId},children:l(p,{fallback:n,children:l(g,{...o,client:f,children:r})})})}var j,q,C=(e,t,r)=>{if(!t.has(e))throw TypeError("Cannot "+r)},I=(e,t,r)=>(C(e,t,"read from private field"),r?r.call(e):t.get(e)),x=(e,t,r)=>{if(t.has(e))throw TypeError("Cannot add the same private member more than once");t instanceof WeakSet?t.add(e):t.set(e,r)},b=(e,t,r,i)=>(C(e,t,"write to private field"),i?i.call(e,r):t.set(e,r),r);class T{constructor(e,t){x(this,j,void 0),x(this,q,void 0),b(this,j,e),b(this,q,t)}static async init(e,t){const r=h({cookie:{name:"__preview",httpOnly:!0,sameSite:!0,secrets:t}}),i=await r.getSession(e.headers.get("Cookie"));return new this(r,i)}has(e){return I(this,q).has(e)}destroy(){return I(this,j).destroySession(I(this,q))}set(e,t){I(this,q).set(e,t)}commit(){return I(this,j).commitSession(I(this,q))}}function W(e){const{data:t,children:r,query:i,params:n}=e,o=Boolean(v());return"function"!=typeof r?r:o&&i?l(A,{query:i,params:n,initialData:t,children:r}):l(d,{children:r(t)})}function A(e){const{initialData:t,query:r,params:i,children:n}=e,[o]=u(t,r,i);return l(d,{children:n(o)})}j=new WeakMap,q=new WeakMap;export{S as PreviewProvider,T as PreviewSession,W as SanityPreview,f as createSanityClient,k as getPreview,m as isPreviewModeEnabled,y as sha256,v as usePreviewContext};//# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","sources":["../src/client.ts","../src/preview/context.tsx","../src/preview/getPreview.ts","../src/preview/PreviewProvider.tsx","../src/preview/PreviewSession.ts","../src/preview/SanityPreview.tsx"],"sourcesContent":["import {\n type ClientConfig,\n type ClientPerspective,\n createClient,\n type QueryParams,\n type SanityClient,\n} from '@sanity/client'\n// eslint-disable-next-line camelcase\nimport {CacheLong, createWithCache_unstable} from '@shopify/hydrogen'\n\nimport type {PreviewSession} from './preview'\nimport type {CachingStrategy, EnvironmentOptions} from './types'\n\ntype CreateSanityClientOptions = EnvironmentOptions & {\n config: ClientConfig & Required<Pick<ClientConfig, 'projectId' | 'dataset'>>\n preview?: {\n session: PreviewSession\n token: string\n perspective?: ClientPerspective\n }\n}\n\ntype useSanityQuery = {\n query: string\n params?: QueryParams\n cache?: CachingStrategy\n}\n\nexport type Sanity = {\n client: SanityClient\n preview?:\n | {session: PreviewSession; projectId: string; dataset: string; token: string}\n | {session: PreviewSession}\n query<T>(options: useSanityQuery): Promise<T>\n}\n\n/**\n * Create Sanity provider with API client.\n */\nexport function createSanityClient(options: CreateSanityClientOptions): Sanity {\n const {cache, waitUntil, preview, config} = options\n\n const sanity: Sanity = {\n client: createClient(config),\n async query<T = any>({query, params, cache: strategy = CacheLong()}: useSanityQuery) {\n const queryHash = await hashQuery(query, params)\n const withCache = createWithCache_unstable<T>({\n cache,\n waitUntil,\n })\n\n return withCache(queryHash, strategy, () => sanity.client.fetch(query, params))\n },\n }\n\n if (preview) {\n sanity.preview = {session: preview.session}\n\n if (preview.session.has('projectId')) {\n sanity.preview = {\n ...sanity.preview,\n projectId: config.projectId,\n dataset: config.dataset,\n token: preview.token,\n }\n\n sanity.client = sanity.client.withConfig({\n useCdn: false,\n token: preview.token,\n perspective: preview.perspective || 'previewDrafts',\n ignoreBrowserTokenWarning: true,\n })\n\n sanity.query = ({query, params}) => {\n return sanity.client.fetch(query, params)\n }\n }\n }\n\n return sanity\n}\n\nexport function isPreviewModeEnabled(\n preview?: Sanity['preview']\n): preview is {session: PreviewSession; projectId: string; dataset: string; token: string} {\n // @ts-expect-error\n return Boolean(preview && preview.token && preview.token !== null)\n}\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 */\nfunction hashQuery(\n query: useSanityQuery['query'],\n params: useSanityQuery['params']\n): Promise<string> {\n let hash = query\n\n if (params !== null) {\n hash += JSON.stringify(params)\n }\n\n return sha256(hash)\n}\n","import {createContext, useContext} from 'react'\n\nexport const PreviewContext = createContext<{projectId: string} | undefined>(undefined)\n\n/** TODO: needs inline documentation */\nexport const usePreviewContext = () => useContext(PreviewContext)\n","import type {ClientConfig} from '@sanity/client'\n\nimport {isPreviewModeEnabled, Sanity} from '../client'\n\n/** TODO: inline documentation */\nexport function getPreview<T extends {sanity: Sanity}>(context: T): ClientConfig | undefined {\n return isPreviewModeEnabled(context.sanity.preview)\n ? {\n ...context.sanity.client.config(),\n }\n : undefined\n}\n","/* eslint-disable react/require-default-props */\nimport {type ClientConfig, createClient} from '@sanity/client'\nimport type {LiveQueryProviderProps} from '@sanity/preview-kit'\nimport {lazy, type ReactNode, Suspense, useEffect, useState, useTransition} from 'react'\n\nimport {PreviewContext} from './context'\n\nconst LiveQueryProvider = lazy(() =>\n import('@sanity/preview-kit').then((m) => ({default: m.LiveQueryProvider}))\n)\n\ntype SanityPreviewProps = Omit<LiveQueryProviderProps, 'client'> & {\n fallback?: ReactNode\n previewConfig?: ClientConfig\n}\n\n/**\n * TODO: inline documentation\n * @see https://www.sanity.io/docs/preview-content-on-site\n */\nexport function PreviewProvider(props: SanityPreviewProps) {\n const {children, previewConfig, fallback = children, ...rest} = props\n\n const [, startTransition] = useTransition()\n const [hydrated, setHydrated] = useState(false)\n useEffect(() => startTransition(() => setHydrated(true)), [])\n\n if (!hydrated || !previewConfig || !previewConfig.projectId) {\n return children\n }\n\n const client = createClient(previewConfig)\n\n return (\n <PreviewContext.Provider value={{projectId: previewConfig.projectId}}>\n <Suspense fallback={fallback}>\n <LiveQueryProvider {...rest} client={client}>\n {children}\n </LiveQueryProvider>\n </Suspense>\n </PreviewContext.Provider>\n )\n}\n","import {createCookieSessionStorage, type Session, type SessionStorage} from '@shopify/remix-oxygen'\n\n/**\n * TODO: needs inline documentation\n */\nexport class PreviewSession {\n #sessionStorage: SessionStorage\n #session: Session\n\n constructor(sessionStorage: SessionStorage, session: Session) {\n this.#sessionStorage = sessionStorage\n this.#session = session\n }\n\n static async init(request: Request, secrets: string[]): Promise<PreviewSession> {\n const storage = createCookieSessionStorage({\n cookie: {\n name: '__preview',\n httpOnly: true,\n sameSite: true,\n secrets,\n },\n })\n\n const session = await storage.getSession(request.headers.get('Cookie'))\n\n return new this(storage, session)\n }\n\n has(key: string): boolean {\n return this.#session.has(key)\n }\n\n // get(key: string) {\n // return this.session.get(key);\n // }\n\n destroy(): Promise<string> {\n return this.#sessionStorage.destroySession(this.#session)\n }\n\n // unset(key: string) {\n // this.session.unset(key);\n // }\n\n // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types\n set(key: string, value: any): void {\n this.#session.set(key, value)\n }\n\n commit(): Promise<string> {\n return this.#sessionStorage.commitSession(this.#session)\n }\n}\n","/* eslint-disable react/require-default-props */\nimport type {QueryParams} from '@sanity/client'\nimport {useLiveQuery} from '@sanity/preview-kit'\nimport {type ReactNode} from 'react'\n\nimport {usePreviewContext} from './context'\n\ntype PreviewProps<T> = {\n data: T\n children: ReactNode | ((data?: T | null) => ReactNode)\n query?: string | null\n params?: QueryParams\n}\n\n/**\n * Component to use for rendering in preview mode\n *\n * When provided a Sanity query and render prop,\n * changes will be streamed in the client\n */\nexport function SanityPreview<T = unknown>(props: PreviewProps<T>) {\n const {data, children, query, params} = props\n const isPreview = Boolean(usePreviewContext())\n\n if (typeof children !== 'function') {\n return children\n }\n\n if (isPreview && query) {\n return (\n <ResolvePreview<typeof data> query={query} params={params} initialData={data}>\n {children}\n </ResolvePreview>\n )\n }\n\n return <>{children(data)}</>\n}\n\ntype ResolvePreviewProps<T> = {\n initialData?: T | null\n query: string\n params?: QueryParams\n children: (data?: T | null) => ReactNode\n}\n\n/**\n * Subscribe to live preview and delegate rendering to consumer\n */\nfunction ResolvePreview<T = unknown>(props: ResolvePreviewProps<T>) {\n const {initialData, query, params, children} = props\n const [data] = useLiveQuery(initialData, query, params)\n\n return <>{children(data)}</>\n}\n"],"names":["createSanityClient","options","cache","waitUntil","preview","config","sanity","client","createClient","async","query","params","strategy","CacheLong","_ref","queryHash","hash","JSON","stringify","sha256","hashQuery","createWithCache_unstable","withCache","fetch","session","has","projectId","dataset","token","withConfig","useCdn","perspective","ignoreBrowserTokenWarning","_ref2","isPreviewModeEnabled","Boolean","message","messageBuffer","TextEncoder","encode","hashBuffer","crypto","subtle","digest","Array","from","Uint8Array","map","b","toString","padStart","join","PreviewContext","createContext","usePreviewContext","useContext","getPreview","context","LiveQueryProvider","lazy","import","then","m","default","PreviewProvider","props","children","previewConfig","fallback","rest","startTransition","useTransition","hydrated","setHydrated","useState","useEffect","jsx","Provider","value","Suspense","_sessionStorage","_session","PreviewSession","constructor","sessionStorage","__privateAdd","this","__privateSet","static","request","secrets","storage","createCookieSessionStorage","cookie","name","httpOnly","sameSite","getSession","headers","get","key","__privateGet","destroy","destroySession","set","commit","commitSession","SanityPreview","data","isPreview","ResolvePreview","initialData","Fragment","useLiveQuery","WeakMap"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hydrogen-sanity",
3
- "version": "1.1.0",
3
+ "version": "2.0.1",
4
4
  "description": "Sanity.io toolkit for Hydrogen",
5
5
  "keywords": [
6
6
  "sanity",
@@ -20,20 +20,20 @@
20
20
  "url": "git+ssh://git@github.com/sanity-io/hydrogen-sanity.git"
21
21
  },
22
22
  "license": "MIT",
23
- "author": "Sanity.io <hello@sanity.io> <noah@sanity.io>",
23
+ "author": "Sanity.io <hello@sanity.io>",
24
24
  "sideEffects": false,
25
25
  "exports": {
26
26
  ".": {
27
27
  "types": "./dist/index.d.ts",
28
28
  "source": "./src/index.ts",
29
29
  "require": "./dist/index.js",
30
- "import": "./dist/index.esm.js",
31
- "default": "./dist/index.esm.js"
30
+ "import": "./dist/index.mjs",
31
+ "default": "./dist/index.mjs"
32
32
  },
33
33
  "./package.json": "./package.json"
34
34
  },
35
35
  "main": "./dist/index.js",
36
- "module": "./dist/index.esm.js",
36
+ "module": "./dist/index.mjs",
37
37
  "source": "./src/index.ts",
38
38
  "types": "./dist/index.d.ts",
39
39
  "files": [
@@ -45,27 +45,27 @@
45
45
  "clean": "rimraf dist",
46
46
  "format": "prettier --write --cache --ignore-unknown .",
47
47
  "link-watch": "plugin-kit link-watch",
48
- "lint": "eslint .",
48
+ "lint": "eslint --ignore-path .gitignore .",
49
49
  "prepublishOnly": "run-s build",
50
50
  "watch": "pkg-utils watch --strict",
51
51
  "prepare": "husky install"
52
52
  },
53
53
  "dependencies": {
54
- "@sanity/client": "^6.0.1",
55
- "@sanity/preview-kit": "^1.5.2"
54
+ "@sanity/client": "^6.1.6",
55
+ "@sanity/preview-kit": "^2.3.0"
56
56
  },
57
57
  "devDependencies": {
58
- "@commitlint/cli": "^17.6.3",
59
- "@commitlint/config-conventional": "^17.6.3",
60
- "@sanity/pkg-utils": "^2.2.14",
58
+ "@commitlint/cli": "^17.6.6",
59
+ "@commitlint/config-conventional": "^17.6.6",
60
+ "@sanity/pkg-utils": "^2.3.3",
61
61
  "@sanity/plugin-kit": "^3.1.7",
62
62
  "@sanity/semantic-release-preset": "^4.1.1",
63
- "@shopify/hydrogen": "^2023.4.0",
64
- "@shopify/remix-oxygen": "^1.0.5",
65
- "@types/react": "^18.2.6",
66
- "@typescript-eslint/eslint-plugin": "^5.59.5",
67
- "@typescript-eslint/parser": "^5.59.5",
68
- "eslint": "^8.40.0",
63
+ "@shopify/hydrogen": "^2023.4.6",
64
+ "@shopify/remix-oxygen": "^1.1.1",
65
+ "@types/react": "^18.2.14",
66
+ "@typescript-eslint/eslint-plugin": "^5.61.0",
67
+ "@typescript-eslint/parser": "^5.61.0",
68
+ "eslint": "^8.44.0",
69
69
  "eslint-config-prettier": "^8.8.0",
70
70
  "eslint-config-sanity": "^6.0.0",
71
71
  "eslint-plugin-prettier": "^4.2.1",
@@ -73,14 +73,14 @@
73
73
  "eslint-plugin-react-hooks": "^4.6.0",
74
74
  "eslint-plugin-simple-import-sort": "^10.0.0",
75
75
  "husky": "^8.0.3",
76
- "lint-staged": "^13.2.2",
76
+ "lint-staged": "^13.2.3",
77
77
  "npm-run-all": "^4.1.5",
78
78
  "prettier": "^2.8.8",
79
- "prettier-plugin-packagejson": "^2.4.3",
79
+ "prettier-plugin-packagejson": "^2.4.4",
80
80
  "react": "^18.2.0",
81
81
  "react-dom": "^18.2.0",
82
- "rimraf": "^5.0.0",
83
- "typescript": "^5.0.4"
82
+ "rimraf": "^5.0.1",
83
+ "typescript": "^5.1.6"
84
84
  },
85
85
  "peerDependencies": {
86
86
  "@shopify/hydrogen": "^2023.4.0",
package/src/client.ts CHANGED
@@ -1,8 +1,14 @@
1
- import {type ClientConfig, createClient, type SanityClient} from '@sanity/client'
1
+ import {
2
+ type ClientConfig,
3
+ type ClientPerspective,
4
+ createClient,
5
+ type QueryParams,
6
+ type SanityClient,
7
+ } from '@sanity/client'
2
8
  // eslint-disable-next-line camelcase
3
9
  import {CacheLong, createWithCache_unstable} from '@shopify/hydrogen'
4
10
 
5
- import type {PreviewData, PreviewSession} from './preview'
11
+ import type {PreviewSession} from './preview'
6
12
  import type {CachingStrategy, EnvironmentOptions} from './types'
7
13
 
8
14
  type CreateSanityClientOptions = EnvironmentOptions & {
@@ -10,18 +16,21 @@ type CreateSanityClientOptions = EnvironmentOptions & {
10
16
  preview?: {
11
17
  session: PreviewSession
12
18
  token: string
19
+ perspective?: ClientPerspective
13
20
  }
14
21
  }
15
22
 
16
23
  type useSanityQuery = {
17
24
  query: string
18
- params?: Record<string, unknown>
25
+ params?: QueryParams
19
26
  cache?: CachingStrategy
20
27
  }
21
28
 
22
29
  export type Sanity = {
23
30
  client: SanityClient
24
- preview?: ({session: PreviewSession} & PreviewData) | {session: PreviewSession}
31
+ preview?:
32
+ | {session: PreviewSession; projectId: string; dataset: string; token: string}
33
+ | {session: PreviewSession}
25
34
  query<T>(options: useSanityQuery): Promise<T>
26
35
  }
27
36
 
@@ -30,15 +39,15 @@ export type Sanity = {
30
39
  */
31
40
  export function createSanityClient(options: CreateSanityClientOptions): Sanity {
32
41
  const {cache, waitUntil, preview, config} = options
33
- const withCache = createWithCache_unstable({
34
- cache,
35
- waitUntil,
36
- })
37
42
 
38
43
  const sanity: Sanity = {
39
44
  client: createClient(config),
40
- async query({query, params, cache: strategy = CacheLong()}) {
45
+ async query<T = any>({query, params, cache: strategy = CacheLong()}: useSanityQuery) {
41
46
  const queryHash = await hashQuery(query, params)
47
+ const withCache = createWithCache_unstable<T>({
48
+ cache,
49
+ waitUntil,
50
+ })
42
51
 
43
52
  return withCache(queryHash, strategy, () => sanity.client.fetch(query, params))
44
53
  },
@@ -58,6 +67,8 @@ export function createSanityClient(options: CreateSanityClientOptions): Sanity {
58
67
  sanity.client = sanity.client.withConfig({
59
68
  useCdn: false,
60
69
  token: preview.token,
70
+ perspective: preview.perspective || 'previewDrafts',
71
+ ignoreBrowserTokenWarning: true,
61
72
  })
62
73
 
63
74
  sanity.query = ({query, params}) => {
@@ -71,9 +82,9 @@ export function createSanityClient(options: CreateSanityClientOptions): Sanity {
71
82
 
72
83
  export function isPreviewModeEnabled(
73
84
  preview?: Sanity['preview']
74
- ): preview is {session: PreviewSession} & PreviewData {
85
+ ): preview is {session: PreviewSession; projectId: string; dataset: string; token: string} {
75
86
  // @ts-expect-error
76
- return preview && preview.token !== null
87
+ return Boolean(preview && preview.token && preview.token !== null)
77
88
  }
78
89
 
79
90
  /**
@@ -0,0 +1,43 @@
1
+ /* eslint-disable react/require-default-props */
2
+ import {type ClientConfig, createClient} from '@sanity/client'
3
+ import type {LiveQueryProviderProps} from '@sanity/preview-kit'
4
+ import {lazy, type ReactNode, Suspense, useEffect, useState, useTransition} from 'react'
5
+
6
+ import {PreviewContext} from './context'
7
+
8
+ const LiveQueryProvider = lazy(() =>
9
+ import('@sanity/preview-kit').then((m) => ({default: m.LiveQueryProvider}))
10
+ )
11
+
12
+ type SanityPreviewProps = Omit<LiveQueryProviderProps, 'client'> & {
13
+ fallback?: ReactNode
14
+ previewConfig?: ClientConfig
15
+ }
16
+
17
+ /**
18
+ * TODO: inline documentation
19
+ * @see https://www.sanity.io/docs/preview-content-on-site
20
+ */
21
+ export function PreviewProvider(props: SanityPreviewProps) {
22
+ const {children, previewConfig, fallback = children, ...rest} = props
23
+
24
+ const [, startTransition] = useTransition()
25
+ const [hydrated, setHydrated] = useState(false)
26
+ useEffect(() => startTransition(() => setHydrated(true)), [])
27
+
28
+ if (!hydrated || !previewConfig || !previewConfig.projectId) {
29
+ return children
30
+ }
31
+
32
+ const client = createClient(previewConfig)
33
+
34
+ return (
35
+ <PreviewContext.Provider value={{projectId: previewConfig.projectId}}>
36
+ <Suspense fallback={fallback}>
37
+ <LiveQueryProvider {...rest} client={client}>
38
+ {children}
39
+ </LiveQueryProvider>
40
+ </Suspense>
41
+ </PreviewContext.Provider>
42
+ )
43
+ }
@@ -0,0 +1,54 @@
1
+ import {createCookieSessionStorage, type Session, type SessionStorage} from '@shopify/remix-oxygen'
2
+
3
+ /**
4
+ * TODO: needs inline documentation
5
+ */
6
+ export class PreviewSession {
7
+ #sessionStorage: SessionStorage
8
+ #session: Session
9
+
10
+ constructor(sessionStorage: SessionStorage, session: Session) {
11
+ this.#sessionStorage = sessionStorage
12
+ this.#session = session
13
+ }
14
+
15
+ static async init(request: Request, secrets: string[]): Promise<PreviewSession> {
16
+ const storage = createCookieSessionStorage({
17
+ cookie: {
18
+ name: '__preview',
19
+ httpOnly: true,
20
+ sameSite: true,
21
+ secrets,
22
+ },
23
+ })
24
+
25
+ const session = await storage.getSession(request.headers.get('Cookie'))
26
+
27
+ return new this(storage, session)
28
+ }
29
+
30
+ has(key: string): boolean {
31
+ return this.#session.has(key)
32
+ }
33
+
34
+ // get(key: string) {
35
+ // return this.session.get(key);
36
+ // }
37
+
38
+ destroy(): Promise<string> {
39
+ return this.#sessionStorage.destroySession(this.#session)
40
+ }
41
+
42
+ // unset(key: string) {
43
+ // this.session.unset(key);
44
+ // }
45
+
46
+ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
47
+ set(key: string, value: any): void {
48
+ this.#session.set(key, value)
49
+ }
50
+
51
+ commit(): Promise<string> {
52
+ return this.#sessionStorage.commitSession(this.#session)
53
+ }
54
+ }
@@ -0,0 +1,55 @@
1
+ /* eslint-disable react/require-default-props */
2
+ import type {QueryParams} from '@sanity/client'
3
+ import {useLiveQuery} from '@sanity/preview-kit'
4
+ import {type ReactNode} from 'react'
5
+
6
+ import {usePreviewContext} from './context'
7
+
8
+ type PreviewProps<T> = {
9
+ data: T
10
+ children: ReactNode | ((data?: T | null) => ReactNode)
11
+ query?: string | null
12
+ params?: QueryParams
13
+ }
14
+
15
+ /**
16
+ * Component to use for rendering in preview mode
17
+ *
18
+ * When provided a Sanity query and render prop,
19
+ * changes will be streamed in the client
20
+ */
21
+ export function SanityPreview<T = unknown>(props: PreviewProps<T>) {
22
+ const {data, children, query, params} = props
23
+ const isPreview = Boolean(usePreviewContext())
24
+
25
+ if (typeof children !== 'function') {
26
+ return children
27
+ }
28
+
29
+ if (isPreview && query) {
30
+ return (
31
+ <ResolvePreview<typeof data> query={query} params={params} initialData={data}>
32
+ {children}
33
+ </ResolvePreview>
34
+ )
35
+ }
36
+
37
+ return <>{children(data)}</>
38
+ }
39
+
40
+ type ResolvePreviewProps<T> = {
41
+ initialData?: T | null
42
+ query: string
43
+ params?: QueryParams
44
+ children: (data?: T | null) => ReactNode
45
+ }
46
+
47
+ /**
48
+ * Subscribe to live preview and delegate rendering to consumer
49
+ */
50
+ function ResolvePreview<T = unknown>(props: ResolvePreviewProps<T>) {
51
+ const {initialData, query, params, children} = props
52
+ const [data] = useLiveQuery(initialData, query, params)
53
+
54
+ return <>{children(data)}</>
55
+ }
@@ -0,0 +1,6 @@
1
+ import {createContext, useContext} from 'react'
2
+
3
+ export const PreviewContext = createContext<{projectId: string} | undefined>(undefined)
4
+
5
+ /** TODO: needs inline documentation */
6
+ export const usePreviewContext = () => useContext(PreviewContext)
@@ -0,0 +1,12 @@
1
+ import type {ClientConfig} from '@sanity/client'
2
+
3
+ import {isPreviewModeEnabled, Sanity} from '../client'
4
+
5
+ /** TODO: inline documentation */
6
+ export function getPreview<T extends {sanity: Sanity}>(context: T): ClientConfig | undefined {
7
+ return isPreviewModeEnabled(context.sanity.preview)
8
+ ? {
9
+ ...context.sanity.client.config(),
10
+ }
11
+ : undefined
12
+ }
@@ -0,0 +1,5 @@
1
+ export {usePreviewContext} from './context'
2
+ export {getPreview} from './getPreview'
3
+ export {PreviewProvider} from './PreviewProvider'
4
+ export {PreviewSession} from './PreviewSession'
5
+ export {SanityPreview} from './SanityPreview'
package/dist/index.esm.js DELETED
@@ -1,190 +0,0 @@
1
- import { createClient } from '@sanity/client';
2
- import { createWithCache_unstable, CacheLong } from '@shopify/hydrogen';
3
- import { jsx, Fragment } from 'react/jsx-runtime';
4
- import { definePreview, PreviewSuspense } from '@sanity/preview-kit';
5
- import { createCookieSessionStorage } from '@shopify/remix-oxygen';
6
- import { createContext, useContext, useMemo } from 'react';
7
- function createSanityClient(options) {
8
- const {
9
- cache,
10
- waitUntil,
11
- preview,
12
- config
13
- } = options;
14
- const withCache = createWithCache_unstable({
15
- cache,
16
- waitUntil
17
- });
18
- const sanity = {
19
- client: createClient(config),
20
- async query(_ref) {
21
- let {
22
- query,
23
- params,
24
- cache: strategy = CacheLong()
25
- } = _ref;
26
- const queryHash = await hashQuery(query, params);
27
- return withCache(queryHash, strategy, () => sanity.client.fetch(query, params));
28
- }
29
- };
30
- if (preview) {
31
- sanity.preview = {
32
- session: preview.session
33
- };
34
- if (preview.session.has("projectId")) {
35
- sanity.preview = {
36
- ...sanity.preview,
37
- projectId: config.projectId,
38
- dataset: config.dataset,
39
- token: preview.token
40
- };
41
- sanity.client = sanity.client.withConfig({
42
- useCdn: false,
43
- token: preview.token
44
- });
45
- sanity.query = _ref2 => {
46
- let {
47
- query,
48
- params
49
- } = _ref2;
50
- return sanity.client.fetch(query, params);
51
- };
52
- }
53
- }
54
- return sanity;
55
- }
56
- function isPreviewModeEnabled(preview) {
57
- return preview && preview.token !== null;
58
- }
59
- async function sha256(message) {
60
- const messageBuffer = await new TextEncoder().encode(message);
61
- const hashBuffer = await crypto.subtle.digest("SHA-256", messageBuffer);
62
- return Array.from(new Uint8Array(hashBuffer)).map(b => b.toString(16).padStart(2, "0")).join("");
63
- }
64
- function hashQuery(query, params) {
65
- let hash = query;
66
- if (params !== null) {
67
- hash += JSON.stringify(params);
68
- }
69
- return sha256(hash);
70
- }
71
- class PreviewSession {
72
- // eslint-disable-next-line no-useless-constructor, no-empty-function
73
- constructor(sessionStorage, session) {
74
- this.sessionStorage = sessionStorage;
75
- this.session = session;
76
- }
77
- static async init(request, secrets) {
78
- const storage = createCookieSessionStorage({
79
- cookie: {
80
- name: "__preview",
81
- httpOnly: true,
82
- sameSite: true,
83
- secrets
84
- }
85
- });
86
- const session = await storage.getSession(request.headers.get("Cookie"));
87
- return new this(storage, session);
88
- }
89
- has(key) {
90
- return this.session.has(key);
91
- }
92
- // get(key: string) {
93
- // return this.session.get(key);
94
- // }
95
- destroy() {
96
- return this.sessionStorage.destroySession(this.session);
97
- }
98
- // unset(key: string) {
99
- // this.session.unset(key);
100
- // }
101
- set(key, value) {
102
- this.session.set(key, value);
103
- }
104
- commit() {
105
- return this.sessionStorage.commitSession(this.session);
106
- }
107
- }
108
- const PreviewContext = createContext(void 0);
109
- const usePreviewContext = () => useContext(PreviewContext);
110
- function Preview(props) {
111
- var _a;
112
- const {
113
- children,
114
- preview
115
- } = props;
116
- if (!(preview == null ? void 0 : preview.token)) {
117
- return /* @__PURE__ */jsx(Fragment, {
118
- children
119
- });
120
- }
121
- const fallback = (_a = props.fallback) != null ? _a : /* @__PURE__ */jsx("div", {
122
- children: "Loading preview..."
123
- });
124
- const {
125
- projectId,
126
- dataset,
127
- token
128
- } = preview;
129
- const _usePreview = definePreview({
130
- projectId,
131
- dataset,
132
- overlayDrafts: true
133
- });
134
- function usePreview(query, params, serverSnapshot) {
135
- return _usePreview(token, query, params, serverSnapshot);
136
- }
137
- return /* @__PURE__ */jsx(PreviewContext.Provider, {
138
- value: {
139
- usePreview
140
- },
141
- children: /* @__PURE__ */jsx(PreviewSuspense, {
142
- fallback,
143
- children
144
- })
145
- });
146
- }
147
- function usePreviewComponent(component, preview) {
148
- const isPreview = Boolean(usePreviewContext());
149
- return useMemo(() => isPreview ? preview : component, [component, isPreview, preview]);
150
- }
151
- function SanityPreview(props) {
152
- const {
153
- data,
154
- children,
155
- query,
156
- params
157
- } = props;
158
- const isPreview = Boolean(usePreviewContext());
159
- if (typeof children !== "function") {
160
- return children;
161
- }
162
- if (isPreview && query) {
163
- return /* @__PURE__ */jsx(ResolvePreview, {
164
- query,
165
- params,
166
- serverSnapshot: data,
167
- children
168
- });
169
- }
170
- return /* @__PURE__ */jsx(Fragment, {
171
- children: children(data)
172
- });
173
- }
174
- function ResolvePreview(props) {
175
- const {
176
- serverSnapshot,
177
- query,
178
- params,
179
- children
180
- } = props;
181
- const {
182
- usePreview
183
- } = usePreviewContext();
184
- const data = usePreview(query, params, serverSnapshot);
185
- return /* @__PURE__ */jsx(Fragment, {
186
- children: children(data)
187
- });
188
- }
189
- export { Preview, PreviewSession, SanityPreview, createSanityClient, isPreviewModeEnabled, sha256, usePreviewComponent, usePreviewContext };
190
- //# sourceMappingURL=index.esm.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.esm.js","sources":["../src/client.ts","../src/preview.tsx"],"sourcesContent":["import {type ClientConfig, createClient, type SanityClient} from '@sanity/client'\n// eslint-disable-next-line camelcase\nimport {CacheLong, createWithCache_unstable} from '@shopify/hydrogen'\n\nimport type {PreviewData, PreviewSession} from './preview'\nimport type {CachingStrategy, EnvironmentOptions} from './types'\n\ntype CreateSanityClientOptions = EnvironmentOptions & {\n config: ClientConfig & Required<Pick<ClientConfig, 'projectId' | 'dataset'>>\n preview?: {\n session: PreviewSession\n token: string\n }\n}\n\ntype useSanityQuery = {\n query: string\n params?: Record<string, unknown>\n cache?: CachingStrategy\n}\n\nexport type Sanity = {\n client: SanityClient\n preview?: ({session: PreviewSession} & PreviewData) | {session: PreviewSession}\n query<T>(options: useSanityQuery): Promise<T>\n}\n\n/**\n * Create Sanity provider with API client.\n */\nexport function createSanityClient(options: CreateSanityClientOptions): Sanity {\n const {cache, waitUntil, preview, config} = options\n const withCache = createWithCache_unstable({\n cache,\n waitUntil,\n })\n\n const sanity: Sanity = {\n client: createClient(config),\n async query({query, params, cache: strategy = CacheLong()}) {\n const queryHash = await hashQuery(query, params)\n\n return withCache(queryHash, strategy, () => sanity.client.fetch(query, params))\n },\n }\n\n if (preview) {\n sanity.preview = {session: preview.session}\n\n if (preview.session.has('projectId')) {\n sanity.preview = {\n ...sanity.preview,\n projectId: config.projectId,\n dataset: config.dataset,\n token: preview.token,\n }\n\n sanity.client = sanity.client.withConfig({\n useCdn: false,\n token: preview.token,\n })\n\n sanity.query = ({query, params}) => {\n return sanity.client.fetch(query, params)\n }\n }\n }\n\n return sanity\n}\n\nexport function isPreviewModeEnabled(\n preview?: Sanity['preview']\n): preview is {session: PreviewSession} & PreviewData {\n // @ts-expect-error\n return preview && preview.token !== null\n}\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 */\nfunction hashQuery(\n query: useSanityQuery['query'],\n params: useSanityQuery['params']\n): Promise<string> {\n let hash = query\n\n if (params !== null) {\n hash += JSON.stringify(params)\n }\n\n return sha256(hash)\n}\n","/* eslint-disable react/require-default-props */\nimport {definePreview, type Params, PreviewSuspense} from '@sanity/preview-kit'\nimport {createCookieSessionStorage, type Session, type SessionStorage} from '@shopify/remix-oxygen'\nimport {createContext, ElementType, type ReactNode, useContext, useMemo} from 'react'\n\ntype UsePreview = <R = any, P extends Params = Params, Q extends string = string>(\n query: Q,\n params?: P,\n serverSnapshot?: R\n) => R\n\nexport type PreviewData = {\n projectId: string\n dataset: string\n token: string\n}\n\nexport type PreviewProps = {\n children: ReactNode\n fallback?: ReactNode\n preview?: PreviewData\n}\n\nexport class PreviewSession {\n // eslint-disable-next-line no-useless-constructor, no-empty-function\n constructor(private sessionStorage: SessionStorage, private session: Session) {}\n\n static async init(request: Request, secrets: string[]) {\n const storage = createCookieSessionStorage({\n cookie: {\n name: '__preview',\n httpOnly: true,\n sameSite: true,\n secrets,\n },\n })\n\n const session = await storage.getSession(request.headers.get('Cookie'))\n\n return new this(storage, session)\n }\n\n has(key: string) {\n return this.session.has(key)\n }\n\n // get(key: string) {\n // return this.session.get(key);\n // }\n\n destroy() {\n return this.sessionStorage.destroySession(this.session)\n }\n\n // unset(key: string) {\n // this.session.unset(key);\n // }\n\n set(key: string, value: any) {\n this.session.set(key, value)\n }\n\n commit() {\n return this.sessionStorage.commitSession(this.session)\n }\n}\n\nconst PreviewContext = createContext<\n | {\n /**\n * Query Sanity and subscribe to changes, optionally\n * passing a server snapshot to speed up hydration\n */\n usePreview: UsePreview\n }\n | undefined\n>(undefined)\n\nexport const usePreviewContext = () => useContext(PreviewContext)\n\n/**\n * Conditionally apply `PreviewSuspense` boundary\n * @see https://www.sanity.io/docs/preview-content-on-site\n */\nexport function Preview(props: PreviewProps) {\n const {children, preview} = props\n\n if (!preview?.token) {\n return <>{children}</>\n }\n\n const fallback = props.fallback ?? <div>Loading preview...</div>\n const {projectId, dataset, token} = preview\n const _usePreview = definePreview({\n projectId,\n dataset,\n overlayDrafts: true,\n })\n\n function usePreview<R = any, P extends Params = Params, Q extends string = string>(\n query: Q,\n params?: P,\n serverSnapshot?: R\n ): R {\n return _usePreview(token, query, params, serverSnapshot)\n }\n usePreview satisfies UsePreview\n\n return (\n <PreviewContext.Provider value={{usePreview}}>\n <PreviewSuspense fallback={fallback}>{children}</PreviewSuspense>\n </PreviewContext.Provider>\n )\n}\n\n/**\n * Select and memoize which component to render based on preview mode\n * @deprecated use `SanityPreview` instead\n */\nexport function usePreviewComponent<T>(component: ElementType<T>, preview: ElementType<T>) {\n const isPreview = Boolean(usePreviewContext())\n\n return useMemo(() => (isPreview ? preview : component), [component, isPreview, preview])\n}\n\ntype PreviewDataProps<T> = {\n data: T\n children: ReactNode | ((data: T | null) => ReactNode)\n query?: string | null\n params?: Params\n}\n\n/**\n * Component to use for rendering in preview mode\n *\n * When provided a Sanity query and render prop,\n * changes will be streamed in the client\n */\nexport function SanityPreview<T = unknown>(props: PreviewDataProps<T>) {\n const {data, children, query, params} = props\n const isPreview = Boolean(usePreviewContext())\n\n if (typeof children !== 'function') {\n return children\n }\n\n if (isPreview && query) {\n return (\n <ResolvePreview<typeof data> query={query} params={params} serverSnapshot={data}>\n {children}\n </ResolvePreview>\n )\n }\n\n return <>{children(data)}</>\n}\n\ntype ResolvePreviewProps<T> = {\n serverSnapshot?: T | null\n query: string\n params?: Params\n children: (data: T | null) => ReactNode\n}\n\n/**\n * Subscribe to live preview and delegate rendering to consumer\n */\nfunction ResolvePreview<T = unknown>(props: ResolvePreviewProps<T>) {\n const {serverSnapshot, query, params, children} = props\n // This won't break the conditional rule of hooks,\n // **but** it relies on the assumption that this component\n // will only be used in preview mode 👇🏻\n const {usePreview} = usePreviewContext()!\n const data = usePreview(query, params, serverSnapshot)\n\n return <>{children(data)}</>\n}\n"],"names":["createSanityClient","options","cache","waitUntil","preview","config","withCache","createWithCache_unstable","sanity","client","createClient","query","params","strategy","CacheLong","_ref","queryHash","hashQuery","fetch","session","has","projectId","dataset","token","withConfig","useCdn","_ref2","isPreviewModeEnabled","sha256","message","messageBuffer","TextEncoder","encode","hashBuffer","crypto","subtle","digest","Array","from","Uint8Array","map","b","toString","padStart","join","hash","JSON","stringify","PreviewSession","constructor","sessionStorage","init","request","secrets","storage","createCookieSessionStorage","cookie","name","httpOnly","sameSite","getSession","headers","get","key","destroy","destroySession","set","value","commit","commitSession","PreviewContext","createContext","usePreviewContext","useContext","Preview","props","_a","children","fallback","jsx","_usePreview","definePreview","overlayDrafts","usePreview","serverSnapshot","Provider","PreviewSuspense","usePreviewComponent","component","isPreview","Boolean","useMemo","SanityPreview","data","ResolvePreview","Fragment"],"mappings":"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"}
package/src/preview.tsx DELETED
@@ -1,177 +0,0 @@
1
- /* eslint-disable react/require-default-props */
2
- import {definePreview, type Params, PreviewSuspense} from '@sanity/preview-kit'
3
- import {createCookieSessionStorage, type Session, type SessionStorage} from '@shopify/remix-oxygen'
4
- import {createContext, ElementType, type ReactNode, useContext, useMemo} from 'react'
5
-
6
- type UsePreview = <R = any, P extends Params = Params, Q extends string = string>(
7
- query: Q,
8
- params?: P,
9
- serverSnapshot?: R
10
- ) => R
11
-
12
- export type PreviewData = {
13
- projectId: string
14
- dataset: string
15
- token: string
16
- }
17
-
18
- export type PreviewProps = {
19
- children: ReactNode
20
- fallback?: ReactNode
21
- preview?: PreviewData
22
- }
23
-
24
- export class PreviewSession {
25
- // eslint-disable-next-line no-useless-constructor, no-empty-function
26
- constructor(private sessionStorage: SessionStorage, private session: Session) {}
27
-
28
- static async init(request: Request, secrets: string[]) {
29
- const storage = createCookieSessionStorage({
30
- cookie: {
31
- name: '__preview',
32
- httpOnly: true,
33
- sameSite: true,
34
- secrets,
35
- },
36
- })
37
-
38
- const session = await storage.getSession(request.headers.get('Cookie'))
39
-
40
- return new this(storage, session)
41
- }
42
-
43
- has(key: string) {
44
- return this.session.has(key)
45
- }
46
-
47
- // get(key: string) {
48
- // return this.session.get(key);
49
- // }
50
-
51
- destroy() {
52
- return this.sessionStorage.destroySession(this.session)
53
- }
54
-
55
- // unset(key: string) {
56
- // this.session.unset(key);
57
- // }
58
-
59
- set(key: string, value: any) {
60
- this.session.set(key, value)
61
- }
62
-
63
- commit() {
64
- return this.sessionStorage.commitSession(this.session)
65
- }
66
- }
67
-
68
- const PreviewContext = createContext<
69
- | {
70
- /**
71
- * Query Sanity and subscribe to changes, optionally
72
- * passing a server snapshot to speed up hydration
73
- */
74
- usePreview: UsePreview
75
- }
76
- | undefined
77
- >(undefined)
78
-
79
- export const usePreviewContext = () => useContext(PreviewContext)
80
-
81
- /**
82
- * Conditionally apply `PreviewSuspense` boundary
83
- * @see https://www.sanity.io/docs/preview-content-on-site
84
- */
85
- export function Preview(props: PreviewProps) {
86
- const {children, preview} = props
87
-
88
- if (!preview?.token) {
89
- return <>{children}</>
90
- }
91
-
92
- const fallback = props.fallback ?? <div>Loading preview...</div>
93
- const {projectId, dataset, token} = preview
94
- const _usePreview = definePreview({
95
- projectId,
96
- dataset,
97
- overlayDrafts: true,
98
- })
99
-
100
- function usePreview<R = any, P extends Params = Params, Q extends string = string>(
101
- query: Q,
102
- params?: P,
103
- serverSnapshot?: R
104
- ): R {
105
- return _usePreview(token, query, params, serverSnapshot)
106
- }
107
- usePreview satisfies UsePreview
108
-
109
- return (
110
- <PreviewContext.Provider value={{usePreview}}>
111
- <PreviewSuspense fallback={fallback}>{children}</PreviewSuspense>
112
- </PreviewContext.Provider>
113
- )
114
- }
115
-
116
- /**
117
- * Select and memoize which component to render based on preview mode
118
- * @deprecated use `SanityPreview` instead
119
- */
120
- export function usePreviewComponent<T>(component: ElementType<T>, preview: ElementType<T>) {
121
- const isPreview = Boolean(usePreviewContext())
122
-
123
- return useMemo(() => (isPreview ? preview : component), [component, isPreview, preview])
124
- }
125
-
126
- type PreviewDataProps<T> = {
127
- data: T
128
- children: ReactNode | ((data: T | null) => ReactNode)
129
- query?: string | null
130
- params?: Params
131
- }
132
-
133
- /**
134
- * Component to use for rendering in preview mode
135
- *
136
- * When provided a Sanity query and render prop,
137
- * changes will be streamed in the client
138
- */
139
- export function SanityPreview<T = unknown>(props: PreviewDataProps<T>) {
140
- const {data, children, query, params} = props
141
- const isPreview = Boolean(usePreviewContext())
142
-
143
- if (typeof children !== 'function') {
144
- return children
145
- }
146
-
147
- if (isPreview && query) {
148
- return (
149
- <ResolvePreview<typeof data> query={query} params={params} serverSnapshot={data}>
150
- {children}
151
- </ResolvePreview>
152
- )
153
- }
154
-
155
- return <>{children(data)}</>
156
- }
157
-
158
- type ResolvePreviewProps<T> = {
159
- serverSnapshot?: T | null
160
- query: string
161
- params?: Params
162
- children: (data: T | null) => ReactNode
163
- }
164
-
165
- /**
166
- * Subscribe to live preview and delegate rendering to consumer
167
- */
168
- function ResolvePreview<T = unknown>(props: ResolvePreviewProps<T>) {
169
- const {serverSnapshot, query, params, children} = props
170
- // This won't break the conditional rule of hooks,
171
- // **but** it relies on the assumption that this component
172
- // will only be used in preview mode 👇🏻
173
- const {usePreview} = usePreviewContext()!
174
- const data = usePreview(query, params, serverSnapshot)
175
-
176
- return <>{children(data)}</>
177
- }