@sanity/sdk-react 3.0.0-rc.2 → 3.0.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.
Files changed (38) hide show
  1. package/dist/_exports/dashboard.d.ts +209 -0
  2. package/dist/_exports/dashboard.d.ts.map +1 -0
  3. package/dist/_exports/dashboard.js +278 -0
  4. package/dist/_exports/dashboard.js.map +1 -0
  5. package/dist/index.d.ts +86 -261
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +134 -532
  8. package/dist/index.js.map +1 -1
  9. package/dist/useStudioWorkspacesByProjectIdDataset-I4S3CuR5.js +177 -0
  10. package/dist/useStudioWorkspacesByProjectIdDataset-I4S3CuR5.js.map +1 -0
  11. package/package.json +9 -8
  12. package/src/_exports/dashboard.ts +11 -0
  13. package/src/_exports/sdk-react.ts +2 -12
  14. package/src/components/SDKProvider.test.tsx +5 -5
  15. package/src/components/auth/AuthBoundary.tsx +5 -5
  16. package/src/context/{WorkbenchTokenRefresh.test.tsx → DashboardTokenRefresh.test.tsx} +26 -26
  17. package/src/context/{WorkbenchTokenRefresh.tsx → DashboardTokenRefresh.tsx} +14 -14
  18. package/src/context/OrganizationResourcesProvider.test.tsx +9 -9
  19. package/src/context/OrganizationResourcesProvider.tsx +2 -2
  20. package/src/context/{workbenchToken.ts → dashboardToken.ts} +13 -13
  21. package/src/hooks/comlink/useWindowConnection.ts +1 -1
  22. package/src/hooks/{agent → dashboard}/useAgentResourceContext.ts +1 -1
  23. package/src/hooks/dashboard/useFavorite.test.tsx +101 -0
  24. package/src/hooks/dashboard/useFavorite.ts +34 -0
  25. package/src/hooks/dashboard/useFavoriteContext.ts +61 -0
  26. package/src/hooks/dashboard/{useDashboardNavigate.test.ts → useNavigate.test.ts} +3 -3
  27. package/src/hooks/dashboard/{useDashboardNavigate.ts → useNavigate.ts} +5 -5
  28. package/src/hooks/dashboard/useNavigateToStudioDocument.ts +2 -1
  29. package/src/hooks/{auth/useDashboardOrganizationId.test.tsx → dashboard/useOrganizationId.test.tsx} +4 -4
  30. package/src/hooks/{auth/useDashboardOrganizationId.tsx → dashboard/useOrganizationId.tsx} +2 -2
  31. package/src/hooks/dashboard/useUpdateFavorite.test.tsx +146 -0
  32. package/src/hooks/dashboard/useUpdateFavorite.ts +74 -0
  33. package/src/hooks/dashboard/useWindowTitle.ts +1 -1
  34. package/src/hooks/datasets/useDatasets.test.tsx +29 -22
  35. package/src/hooks/datasets/useDatasets.ts +31 -53
  36. package/src/hooks/dashboard/useManageFavorite.test.tsx +0 -379
  37. package/src/hooks/dashboard/useManageFavorite.ts +0 -173
  38. /package/src/hooks/{agent → dashboard}/useAgentResourceContext.test.tsx +0 -0
package/dist/index.js CHANGED
@@ -1,11 +1,12 @@
1
+ import { a as useSanityInstance, i as createStateSourceHook, n as useOrganizationId, o as SanityInstanceContext, r as useWindowConnection, t as useStudioWorkspacesByProjectIdDataset } from "./useStudioWorkspacesByProjectIdDataset-I4S3CuR5.js";
1
2
  import { c } from "react-compiler-runtime";
2
3
  import { ClientError, CorsOriginError } from "@sanity/client";
3
- import { AuthStateType, application, applications, applyDocumentActions, checkPermissions, createComment, createDocument, createSanityInstance, deleteApplication, editDocument, getActiveReleasesState, getAllReleasesState, getAuthState, getClientState, getCommentThreadsState, getCommentsState, getCorsErrorProjectId, getCurrentUserState, getDashboardOrganizationId, getDatasetsState, getDocumentPresence, getDocumentState, getDocumentSyncStatus, getFavoritesState, getIsInDashboardState, getLoginUrlState, getPermissionsState, getPerspectiveState, getPresence, getProjectionState, getQueryState, getTokenState, getUsersState, handleAuthCallback, installation, installations, isDatasetResource, isImportError, isMediaLibraryResource, loadMoreUsers, logout, observeOrganizationVerificationState, organization, organizations, project, projects, removeComment, replyToComment, reportPresence, resolveCommentThreads, resolveComments, resolveDatasets, resolveDocument, resolveFavoritesState, resolveProjection, resolveQuery, resolveUsers, setAuthToken, setCommentStatus, subscribeDocumentEvents, updateApplication, updateComment } from "@sanity/sdk";
4
+ import { AuthStateType, application, applications, applyDocumentActions, checkPermissions, createComment, createDocument, createSanityInstance, datasets, deleteApplication, editDocument, favorites, getActiveReleasesState, getAllReleasesState, getAuthState, getClientState, getCommentThreadsState, getCommentsState, getCorsErrorProjectId, getCurrentUserState, getDocumentPresence, getDocumentState, getDocumentSyncStatus, getIsInDashboardState, getLoginUrlState, getPermissionsState, getPerspectiveState, getPresence, getProjectionState, getQueryState, getTokenState, getUsersState, handleAuthCallback, installation, installations, isDatasetResource, isImportError, isMediaLibraryResource, loadMoreUsers, logout, observeOrganizationVerificationState, organization, organizations, project, projects, removeComment, replyToComment, reportPresence, resolveCommentThreads, resolveComments, resolveDocument, resolveProjection, resolveQuery, resolveUsers, setAuthToken, setCommentStatus, setFavorite, subscribeDocumentEvents, updateApplication, updateComment } from "@sanity/sdk";
4
5
  import { PREVIEW_PROJECTION, createGroqSearchFilter, getClientErrorApiBody, getClientErrorApiDescription, getCommentsOptionsKey, getQueryKey, getUsersKey, initTelemetry, isDeepEqual, isProjectUserNotFoundClientError, isStudioConfig, parseCommentsOptionsKey, parseQueryKey, parseUsersKey, pickProperties, trackHookMounted, transformProjectionToPreview } from "@sanity/sdk/_internal";
5
6
  import { StrictMode, Suspense, createContext, use, useCallback, useContext, useEffect, useInsertionEffect, useMemo, useRef, useState, useSyncExternalStore, useTransition } from "react";
6
7
  import { ErrorBoundary, getErrorMessage } from "react-error-boundary";
7
8
  import { SDK_CHANNEL_NAME, SDK_NODE_NAME } from "@sanity/message-protocol";
8
- import { getNodeState, getOrCreateChannel, getOrCreateController, releaseChannel } from "@sanity/sdk/comlink";
9
+ import { getOrCreateChannel, getOrCreateController, releaseChannel } from "@sanity/sdk/comlink";
9
10
  import { EMPTY, Observable, distinctUntilChanged, filter, firstValueFrom, from, identity, of, startWith, switchMap } from "rxjs";
10
11
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
11
12
  import { catchError, switchMap as switchMap$1 } from "rxjs/operators";
@@ -14,23 +15,6 @@ import { agentGenerate, agentPatch, agentPrompt, agentTransform, agentTranslate
14
15
  import "groq";
15
16
  import "@sanity/types";
16
17
  export * from "@sanity/sdk";
17
- const SanityInstanceContext = createContext(null), useSanityInstance = () => {
18
- let instance = useContext(SanityInstanceContext);
19
- if (!instance) throw Error("SanityInstance context not found. Please ensure that your component is wrapped in a ResourceProvider or a SanityApp component.");
20
- return instance;
21
- };
22
- function createStateSourceHook(options) {
23
- let getState = typeof options == "function" ? options : options.getState, suspense = "shouldSuspend" in options && "suspender" in options ? options : void 0;
24
- function useHook(...t0) {
25
- let $ = c(3), params = t0, instance = useSanityInstance();
26
- if (suspense?.suspender && suspense?.shouldSuspend?.(instance, ...params)) throw suspense.suspender(instance, ...params);
27
- let t1;
28
- $[0] !== instance || $[1] !== params ? (t1 = getState(instance, ...params), $[0] = instance, $[1] = params, $[2] = t1) : t1 = $[2];
29
- let state = t1;
30
- return useSyncExternalStore(state.subscribe, state.getCurrent);
31
- }
32
- return useHook;
33
- }
34
18
  /**
35
19
  * @internal
36
20
  * A React hook that subscribes to authentication state changes.
@@ -52,61 +36,11 @@ function createStateSourceHook(options) {
52
36
  * }
53
37
  * ```
54
38
  */
55
- const useAuthState = createStateSourceHook(getAuthState), useNodeState = createStateSourceHook({
56
- getState: getNodeState,
57
- shouldSuspend: (instance, nodeInput) => getNodeState(instance, nodeInput).getCurrent() === void 0,
58
- suspender: (instance, nodeInput) => firstValueFrom(getNodeState(instance, nodeInput).observable.pipe(filter(Boolean)))
59
- });
60
- /**
61
- * @internal
62
- * Hook to wrap a Comlink node in a React hook.
63
- * Our store functionality takes care of the lifecycle of the node,
64
- * as well as sharing a single node between invocations if they share the same name.
65
- *
66
- * Generally not to be used directly, but to be used as a dependency of
67
- * Comlink-powered hooks like `useManageFavorite`.
68
- */
69
- function useWindowConnection(t0) {
70
- let $ = c(19), { name, connectTo, onMessage } = t0, t1;
71
- $[0] !== connectTo || $[1] !== name ? (t1 = {
72
- name,
73
- connectTo
74
- }, $[0] = connectTo, $[1] = name, $[2] = t1) : t1 = $[2];
75
- let { node } = useNodeState(t1), t2;
76
- $[3] === Symbol.for("react.memo_cache_sentinel") ? (t2 = [], $[3] = t2) : t2 = $[3];
77
- let messageUnsubscribers = useRef(t2), instance = useSanityInstance(), t3;
78
- $[4] !== node || $[5] !== onMessage ? (t3 = () => (onMessage && Object.entries(onMessage).forEach((t4) => {
79
- let [type, handler] = t4, messageUnsubscribe = node.on(type, handler);
80
- messageUnsubscribe && messageUnsubscribers.current.push(messageUnsubscribe);
81
- }), () => {
82
- messageUnsubscribers.current.forEach(_temp$9), messageUnsubscribers.current = [];
83
- }), $[4] = node, $[5] = onMessage, $[6] = t3) : t3 = $[6];
84
- let t4;
85
- $[7] !== instance || $[8] !== name || $[9] !== node || $[10] !== onMessage ? (t4 = [
86
- instance,
87
- name,
88
- onMessage,
89
- node
90
- ], $[7] = instance, $[8] = name, $[9] = node, $[10] = onMessage, $[11] = t4) : t4 = $[11], useEffect(t3, t4);
91
- let t5;
92
- $[12] === node ? t5 = $[13] : (t5 = (type_0, data) => {
93
- node.post(type_0, data);
94
- }, $[12] = node, $[13] = t5);
95
- let sendMessage = t5, t6;
96
- $[14] === node ? t6 = $[15] : (t6 = (type_1, data_0, fetchOptions) => node.fetch(type_1, data_0, fetchOptions ?? {}), $[14] = node, $[15] = t6);
97
- let fetch = t6, t7;
98
- return $[16] !== fetch || $[17] !== sendMessage ? (t7 = {
99
- sendMessage,
100
- fetch
101
- }, $[16] = fetch, $[17] = sendMessage, $[18] = t7) : t7 = $[18], t7;
102
- }
103
- function _temp$9(unsubscribe) {
104
- return unsubscribe();
105
- }
39
+ const useAuthState = createStateSourceHook(getAuthState);
106
40
  /**
107
41
  * Component that handles token refresh in dashboard mode
108
42
  */
109
- function DashboardTokenRefresh(t0) {
43
+ function DashboardTokenRefresh$1(t0) {
110
44
  let $ = c(14), { children } = t0, instance = useSanityInstance(), isTokenRefreshInProgress = useRef(!1), timeoutRef = useRef(null), processed401ErrorRef = useRef(null), authState = useAuthState(), t1;
111
45
  $[0] === Symbol.for("react.memo_cache_sentinel") ? (t1 = () => {
112
46
  timeoutRef.current &&= (clearTimeout(timeoutRef.current), null);
@@ -159,12 +93,12 @@ const ComlinkTokenRefreshProvider = (t0) => {
159
93
  let $ = c(2), { children } = t0, instance = useSanityInstance(), isInDashboard = getIsInDashboardState(instance).getCurrent(), isStudio = isStudioConfig(instance.config);
160
94
  if (isInDashboard && !isStudio) {
161
95
  let t1;
162
- return $[0] === children ? t1 = $[1] : (t1 = /* @__PURE__ */ jsx(DashboardTokenRefresh, { children }), $[0] = children, $[1] = t1), t1;
96
+ return $[0] === children ? t1 = $[1] : (t1 = /* @__PURE__ */ jsx(DashboardTokenRefresh$1, { children }), $[0] = children, $[1] = t1), t1;
163
97
  }
164
98
  return children;
165
99
  }, OS_BUS_KEY = Symbol.for("sanity.os.bus");
166
100
  /**
167
- * Whether this app is running as a federated remote inside the workbench.
101
+ * Whether this app is running as a federated remote inside the dashboard.
168
102
  *
169
103
  * Federation shares the host's realm, so the installed bus is visible on
170
104
  * `globalThis`. This is `false` in a standalone app, where we must never import
@@ -175,15 +109,15 @@ const ComlinkTokenRefreshProvider = (t0) => {
175
109
  *
176
110
  * @internal
177
111
  */
178
- function isWorkbenchEnvironment() {
112
+ function isDashboardEnvironment() {
179
113
  return typeof globalThis == "object" && OS_BUS_KEY in globalThis;
180
114
  }
181
115
  /**
182
- * Observes the session token issued by the workbench "OS", tracking the OS auth
116
+ * Observes the session token issued by the dashboard "OS", tracking the OS auth
183
117
  * state over time.
184
118
  *
185
- * Returns `undefined` when the app is not embedded in the workbench, so the
186
- * caller uses its normal auth flow. Inside the workbench, subscribes to the
119
+ * Returns `undefined` when the app is not embedded in the dashboard, so the
120
+ * caller uses its normal auth flow. Inside the dashboard, subscribes to the
187
121
  * `auth.token` state topic, emitting the current token — or `null` when the OS
188
122
  * is signed out — and re-emitting as the OS auth state changes, so sign-in/out
189
123
  * propagates instead of being captured once. Any bus error is treated as "no
@@ -191,34 +125,34 @@ function isWorkbenchEnvironment() {
191
125
  *
192
126
  * @internal
193
127
  */
194
- function observeWorkbenchToken() {
195
- if (isWorkbenchEnvironment()) return from(import("@sanity/workbench")).pipe(switchMap$1(({ os }) => os.subscribe("auth.token")), catchError(() => of(null)));
128
+ function observeDashboardToken() {
129
+ if (isDashboardEnvironment()) return from(import("@sanity/workbench")).pipe(switchMap$1(({ os }) => os.subscribe("auth.token")), catchError(() => of(null)));
196
130
  }
197
131
  /**
198
- * Asks the workbench "OS" to reissue the session token, e.g. after its current
132
+ * Asks the dashboard "OS" to reissue the session token, e.g. after its current
199
133
  * one was rejected with a 401. Fire-and-forget: the reissued token arrives via
200
- * the `auth.token` subscription in {@link observeWorkbenchToken}. No-op outside
201
- * the workbench.
134
+ * the `auth.token` subscription in {@link observeDashboardToken}. No-op outside
135
+ * the dashboard.
202
136
  *
203
137
  * @internal
204
138
  */
205
- function refreshWorkbenchToken() {
206
- isWorkbenchEnvironment() && import("@sanity/workbench").then(({ os }) => os.emit("auth.token.refresh", void 0), () => {});
139
+ function refreshDashboardToken() {
140
+ isDashboardEnvironment() && import("@sanity/workbench").then(({ os }) => os.emit("auth.token.refresh", void 0), () => {});
207
141
  }
208
142
  /**
209
- * Keeps the SDK auth token in sync with the workbench "OS".
143
+ * Keeps the SDK auth token in sync with the dashboard "OS".
210
144
  *
211
- * When running as a federated remote inside the workbench the OS owns the
145
+ * When running as a federated remote inside the dashboard the OS owns the
212
146
  * session, so we subscribe to its `auth.token` stream and mirror each value into
213
147
  * the auth store — a token logs us in, `null` logs us out, and later OS
214
148
  * sign-in/out propagates automatically. When a request is rejected with a 401
215
149
  * (the token expired), we ask the OS to reissue rather than tearing the session
216
150
  * down; the new token arrives back through the same subscription.
217
151
  */
218
- function WorkbenchTokenRefresh(t0) {
152
+ function DashboardTokenRefresh(t0) {
219
153
  let $ = c(8), { children } = t0, instance = useSanityInstance(), authState = useAuthState(), processed401ErrorRef = useRef(null), t1, t2;
220
154
  $[0] === instance ? (t1 = $[1], t2 = $[2]) : (t1 = () => {
221
- let token$ = observeWorkbenchToken();
155
+ let token$ = observeDashboardToken();
222
156
  if (!token$) return;
223
157
  let subscription = token$.subscribe((token) => setAuthToken(instance, token));
224
158
  return () => subscription.unsubscribe();
@@ -226,21 +160,21 @@ function WorkbenchTokenRefresh(t0) {
226
160
  let t3;
227
161
  $[3] !== authState.error || $[4] !== authState.type ? (t3 = () => {
228
162
  let has401Error = authState.type === AuthStateType.ERROR && authState.error?.statusCode === 401;
229
- has401Error && processed401ErrorRef.current !== authState.error ? (processed401ErrorRef.current = authState.error, refreshWorkbenchToken()) : has401Error || (processed401ErrorRef.current = null);
163
+ has401Error && processed401ErrorRef.current !== authState.error ? (processed401ErrorRef.current = authState.error, refreshDashboardToken()) : has401Error || (processed401ErrorRef.current = null);
230
164
  }, $[3] = authState.error, $[4] = authState.type, $[5] = t3) : t3 = $[5];
231
165
  let t4;
232
166
  return $[6] === authState ? t4 = $[7] : (t4 = [authState], $[6] = authState, $[7] = t4), useEffect(t3, t4), children;
233
167
  }
234
168
  /**
235
- * Provides workbench OS token refresh. No-op outside the workbench, where the
169
+ * Provides dashboard OS token refresh. No-op outside the dashboard, where the
236
170
  * app uses its normal auth flow.
237
- * @public
171
+ * @internal
238
172
  */
239
- const WorkbenchTokenRefreshProvider = (t0) => {
173
+ const DashboardTokenRefreshProvider = (t0) => {
240
174
  let $ = c(2), { children } = t0;
241
- if (isWorkbenchEnvironment()) {
175
+ if (isDashboardEnvironment()) {
242
176
  let t1;
243
- return $[0] === children ? t1 = $[1] : (t1 = /* @__PURE__ */ jsx(WorkbenchTokenRefresh, { children }), $[0] = children, $[1] = t1), t1;
177
+ return $[0] === children ? t1 = $[1] : (t1 = /* @__PURE__ */ jsx(DashboardTokenRefresh, { children }), $[0] = children, $[1] = t1), t1;
244
178
  }
245
179
  return children;
246
180
  };
@@ -699,7 +633,7 @@ function AuthBoundary(t0) {
699
633
  let t4;
700
634
  $[7] === props ? t4 = $[8] : (t4 = /* @__PURE__ */ jsx(AuthSwitch, { ...props }), $[7] = props, $[8] = t4);
701
635
  let t5;
702
- return $[9] !== FallbackComponent || $[10] !== t3 || $[11] !== t4 ? (t5 = /* @__PURE__ */ jsx(ComlinkTokenRefreshProvider, { children: /* @__PURE__ */ jsx(WorkbenchTokenRefreshProvider, { children: /* @__PURE__ */ jsx(ErrorBoundary, {
636
+ return $[9] !== FallbackComponent || $[10] !== t3 || $[11] !== t4 ? (t5 = /* @__PURE__ */ jsx(ComlinkTokenRefreshProvider, { children: /* @__PURE__ */ jsx(DashboardTokenRefreshProvider, { children: /* @__PURE__ */ jsx(ErrorBoundary, {
703
637
  FallbackComponent,
704
638
  resetKeys: t3,
705
639
  children: t4
@@ -712,7 +646,7 @@ function AuthSwitch(t0) {
712
646
  $[6] === instance.config ? t3 = $[7] : (t3 = isStudioConfig(instance.config), $[6] = instance.config, $[7] = t3);
713
647
  let isStudio = t3, orgError = useVerifyOrgProjects(!verifyOrganization || isStudio || authState.type !== AuthStateType.LOGGED_IN, projectIds), isLoggedOut = authState.type === AuthStateType.LOGGED_OUT && !authState.isDestroyingSession, loginUrl = useLoginUrl(), t4, t5;
714
648
  if ($[8] !== isLoggedOut || $[9] !== isStudio || $[10] !== loginUrl ? (t4 = () => {
715
- isLoggedOut && !isInIframe() && !isStudio && !isWorkbenchEnvironment() && (window.location.href = loginUrl);
649
+ isLoggedOut && !isInIframe() && !isStudio && !isDashboardEnvironment() && (window.location.href = loginUrl);
716
650
  }, t5 = [
717
651
  isLoggedOut,
718
652
  loginUrl,
@@ -771,31 +705,6 @@ SDKStudioContext.displayName = "SDKStudioContext";
771
705
  */
772
706
  const DEFAULT_RESOURCE_NAME = "default";
773
707
  /**
774
- * @public
775
- *
776
- * A React hook that retrieves the dashboard organization ID that is currently selected in the Sanity Dashboard.
777
- *
778
- * @example
779
- * ```tsx
780
- * function DashboardComponent() {
781
- * const orgId = useDashboardOrganizationId()
782
- *
783
- * if (!orgId) return null
784
- *
785
- * return <div>Organization ID: {String(orgId)}</div>
786
- * }
787
- * ```
788
- *
789
- * @category Dashboard
790
- * @returns The dashboard organization ID (string | undefined)
791
- */
792
- function useDashboardOrganizationId() {
793
- let $ = c(2), instance = useSanityInstance(), t0;
794
- $[0] === instance ? t0 = $[1] : (t0 = getDashboardOrganizationId(instance), $[0] = instance, $[1] = t0);
795
- let { subscribe, getCurrent } = t0;
796
- return useSyncExternalStore(subscribe, getCurrent);
797
- }
798
- /**
799
708
  * Gets the id of the first resource from a settled request result. The request
800
709
  * is already scoped to a single organization, so the first item is the one we want.
801
710
  */
@@ -870,7 +779,7 @@ function InferredResourcesProvider(t0) {
870
779
  function OrganizationResourcesProvider(t0) {
871
780
  let $ = c(10), { resources: t1, inferMediaLibraryAndCanvas, children } = t0, t2;
872
781
  $[0] === t1 ? t2 = $[1] : (t2 = t1 === void 0 ? {} : t1, $[0] = t1, $[1] = t2);
873
- let explicitResources = t2, instance = useSanityInstance(), orgId = useDashboardOrganizationId();
782
+ let explicitResources = t2, instance = useSanityInstance(), orgId = useOrganizationId();
874
783
  if (!inferMediaLibraryAndCanvas || !orgId) {
875
784
  let t3;
876
785
  return $[2] !== children || $[3] !== explicitResources ? (t3 = /* @__PURE__ */ jsx(ResourcesContext.Provider, {
@@ -1885,71 +1794,6 @@ function useAgentPatch(resourceHandle) {
1885
1794
  return $[2] !== instance || $[3] !== resource ? (t1 = (options) => firstValueFrom(agentPatch(instance, options, resource)), $[2] = instance, $[3] = resource, $[4] = t1) : t1 = $[4], t1;
1886
1795
  }
1887
1796
  /**
1888
- * @public
1889
- * Hook for emitting agent resource context updates to the Dashboard.
1890
- * This allows the Agent to understand what resource the user is currently
1891
- * interacting with (e.g., which document they're editing).
1892
- *
1893
- * The hook will automatically emit the context when it changes, and also
1894
- * emit the initial context when the hook is first mounted.
1895
- *
1896
- * @category Agent
1897
- * @param options - The resource context options containing projectId, dataset, and optional documentId
1898
- *
1899
- * @example
1900
- * ```tsx
1901
- * import {useAgentResourceContext} from '@sanity/sdk-react'
1902
- *
1903
- * function MyComponent() {
1904
- * const documentId = 'my-document-id'
1905
- *
1906
- * // Automatically updates the Agent's context whenever the document changes
1907
- * useAgentResourceContext({
1908
- * projectId: 'my-project',
1909
- * dataset: 'production',
1910
- * documentId,
1911
- * })
1912
- *
1913
- * return <div>Editing document: {documentId}</div>
1914
- * }
1915
- * ```
1916
- */
1917
- function useAgentResourceContext(options) {
1918
- let $ = c(9), { projectId, dataset, documentId } = options, t0;
1919
- $[0] === Symbol.for("react.memo_cache_sentinel") ? (t0 = {
1920
- name: SDK_NODE_NAME,
1921
- connectTo: SDK_CHANNEL_NAME
1922
- }, $[0] = t0) : t0 = $[0];
1923
- let { sendMessage } = useWindowConnection(t0), lastContextRef = useRef(null), t1;
1924
- $[1] !== dataset || $[2] !== documentId || $[3] !== projectId || $[4] !== sendMessage ? (t1 = () => {
1925
- if (!projectId || !dataset) {
1926
- console.warn("[useAgentResourceContext] projectId and dataset are required", {
1927
- projectId,
1928
- dataset
1929
- });
1930
- return;
1931
- }
1932
- let contextKey = `${projectId}:${dataset}:${documentId || ""}`;
1933
- if (lastContextRef.current !== contextKey) try {
1934
- let message = {
1935
- type: "dashboard/v1/events/agent/resource/update",
1936
- data: {
1937
- projectId,
1938
- dataset,
1939
- documentId
1940
- }
1941
- };
1942
- sendMessage(message.type, message.data), lastContextRef.current = contextKey;
1943
- } catch (t2) {
1944
- console.error("[useAgentResourceContext] Failed to update context:", t2);
1945
- }
1946
- }, $[1] = dataset, $[2] = documentId, $[3] = projectId, $[4] = sendMessage, $[5] = t1) : t1 = $[5];
1947
- let updateContext = t1, t2, t3;
1948
- $[6] === updateContext ? (t2 = $[7], t3 = $[8]) : (t2 = () => {
1949
- updateContext();
1950
- }, t3 = [updateContext], $[6] = updateContext, $[7] = t2, $[8] = t3), useEffect(t2, t3);
1951
- }
1952
- /**
1953
1797
  * Returns a single application by id.
1954
1798
  *
1955
1799
  * The hook suspends until the first fetch succeeds, so `data` is always present.
@@ -2300,311 +2144,52 @@ function useResource() {
2300
2144
  return useEffectiveContextResource();
2301
2145
  }
2302
2146
  /**
2303
- * @public
2304
- *
2305
- * A helper hook designed to be injected into routing components for apps within the Dashboard.
2306
- * While the Dashboard can usually handle navigation, there are special cases when you
2307
- * are already within a target app, and need to navigate to another route inside of that app.
2308
- *
2309
- * For example, your user might "favorite" a document inside of your application.
2310
- * If they click on the Dashboard favorites item in the sidebar, and are already within your application,
2311
- * there needs to be some way for the dashboard to signal to your application to reroute to where that document was favorited.
2147
+ * Resolves {@link UseFavoriteProps} into the {@link FavoriteDocumentContext}
2148
+ * shared by the favorites fetcher and mutation, defaulting a studio `resourceId`
2149
+ * from the instance's project and dataset.
2312
2150
  *
2313
- * This hook is intended to receive those messages, and takes a function to route to the correct path.
2314
- *
2315
- * @param navigateFn - Function to handle navigation; should accept:
2316
- * - `path`: a string, which will be a relative path (for example, 'my-route')
2317
- * - `type`: 'push', 'replace', or 'pop', which will be the type of navigation to perform
2318
- *
2319
- * @example
2320
- * ```tsx
2321
- * import {useDashboardNavigate} from '@sanity/sdk-react'
2322
- * import {BrowserRouter, useNavigate} from 'react-router'
2323
- * import {Suspense} from 'react'
2324
- *
2325
- * function DashboardNavigationHandler() {
2326
- * const navigate = useNavigate()
2327
- * useDashboardNavigate(({path, type}) => {
2328
- * navigate(path, {replace: type === 'replace'})
2329
- * })
2330
- * return null
2331
- * }
2332
- *
2333
- * // Wrap the component with Suspense since the hook may suspend
2334
- * function MyApp() {
2335
- * return (
2336
- * <BrowserRouter>
2337
- * <Suspense>
2338
- * <DashboardNavigationHandler />
2339
- * </Suspense>
2340
- * </BrowserRouter>
2341
- * )
2342
- * }
2343
- * ```
2344
- */
2345
- function useDashboardNavigate(navigateFn) {
2346
- let $ = c(2), t0;
2347
- $[0] === navigateFn ? t0 = $[1] : (t0 = {
2348
- name: SDK_NODE_NAME,
2349
- connectTo: SDK_CHANNEL_NAME,
2350
- onMessage: { "dashboard/v1/history/change-path": (data) => {
2351
- navigateFn(data);
2352
- } }
2353
- }, $[0] = navigateFn, $[1] = t0), useWindowConnection(t0);
2354
- }
2355
- /**
2356
2151
  * @internal
2357
- *
2358
- * This hook provides functionality to add and remove documents from favorites,
2359
- * and tracks the current favorite status of the document.
2360
- * @param documentHandle - The document handle containing document ID and type, like `{_id: '123', _type: 'book'}`
2361
- * @returns An object containing:
2362
- * - `favorite` - Function to add document to favorites
2363
- * - `unfavorite` - Function to remove document from favorites
2364
- * - `isFavorited` - Boolean indicating if document is currently favorited
2365
- *
2366
- * @example
2367
- * ```tsx
2368
- * function FavoriteButton(props: DocumentActionProps) {
2369
- * const {documentId, documentType} = props
2370
- * const {favorite, unfavorite, isFavorited} = useManageFavorite({
2371
- * documentId,
2372
- * documentType
2373
- * })
2374
- *
2375
- * return (
2376
- * <Button
2377
- * onClick={() => isFavorited ? unfavorite() : favorite()}
2378
- * text={isFavorited ? 'Remove from favorites' : 'Add to favorites'}
2379
- * />
2380
- * )
2381
- * }
2382
- *
2383
- * // Wrap the component with Suspense since the hook may suspend
2384
- * function MyDocumentAction(props: DocumentActionProps) {
2385
- * return (
2386
- * <Suspense
2387
- * fallback={
2388
- * <Button
2389
- * text="Loading..."
2390
- * disabled
2391
- * />
2392
- * }
2393
- * >
2394
- * <FavoriteButton {...props} />
2395
- * </Suspense>
2396
- * )
2397
- * }
2398
- * ```
2399
2152
  */
2400
- function useManageFavorite({ documentId, documentType, projectId: paramProjectId, dataset: paramDataset, resourceId: paramResourceId, resourceType, schemaName }) {
2401
- let { fetch } = useWindowConnection({
2402
- name: SDK_NODE_NAME,
2403
- connectTo: SDK_CHANNEL_NAME
2404
- }), instance = useSanityInstance(), { config } = instance, instanceProjectId = config?.projectId, instanceDataset = config?.dataset, projectId = paramProjectId ?? instanceProjectId, dataset = paramDataset ?? instanceDataset;
2153
+ function useFavoriteContext(t0) {
2154
+ let $ = c(6), { documentId, documentType, projectId: paramProjectId, dataset: paramDataset, resourceId: paramResourceId, resourceType, schemaName } = t0, { config } = useSanityInstance(), projectId = paramProjectId ?? config?.projectId, dataset = paramDataset ?? config?.dataset;
2405
2155
  if (resourceType === "studio" && (!projectId || !dataset)) throw Error("projectId and dataset are required for studio resources");
2406
2156
  let resourceId = resourceType === "studio" && !paramResourceId ? `${projectId}.${dataset}` : paramResourceId;
2407
2157
  if (!resourceId) throw Error("resourceId is required for media-library and canvas resources");
2408
- let context = useMemo(() => ({
2409
- documentId,
2410
- documentType,
2411
- resourceId,
2412
- resourceType,
2413
- schemaName
2414
- }), [
2158
+ let t1;
2159
+ return $[0] !== documentId || $[1] !== documentType || $[2] !== resourceId || $[3] !== resourceType || $[4] !== schemaName ? (t1 = {
2415
2160
  documentId,
2416
2161
  documentType,
2417
2162
  resourceId,
2418
2163
  resourceType,
2419
2164
  schemaName
2420
- ]), favoriteState = getFavoritesState(instance, context), isFavorited = useSyncExternalStore(favoriteState.subscribe, favoriteState.getCurrent)?.isFavorited ?? !1, handleFavoriteAction = useCallback(async (action) => {
2421
- if (!(!fetch || !documentId || !documentType || !resourceType)) try {
2422
- let payload = {
2423
- eventType: action,
2424
- document: {
2425
- id: documentId,
2426
- type: documentType,
2427
- resource: {
2428
- id: resourceId,
2429
- type: resourceType,
2430
- ...schemaName ? { schemaName } : {}
2431
- }
2432
- }
2433
- };
2434
- (await fetch("dashboard/v1/events/favorite/mutate", payload)).success && await resolveFavoritesState(instance, context);
2435
- } catch (err) {
2436
- throw console.error(`Failed to ${action === "added" ? "favorite" : "unfavorite"} document:`, err), err;
2437
- }
2438
- }, [
2439
- fetch,
2440
- documentId,
2441
- documentType,
2442
- resourceId,
2443
- resourceType,
2444
- schemaName,
2445
- instance,
2446
- context
2447
- ]);
2448
- return {
2449
- favorite: useCallback(() => handleFavoriteAction("added"), [handleFavoriteAction]),
2450
- unfavorite: useCallback(() => handleFavoriteAction("removed"), [handleFavoriteAction]),
2451
- isFavorited
2452
- };
2165
+ }, $[0] = documentId, $[1] = documentType, $[2] = resourceId, $[3] = resourceType, $[4] = schemaName, $[5] = t1) : t1 = $[5], t1;
2453
2166
  }
2167
+ const useFavoriteStatus = createFetcherHook(favorites);
2454
2168
  /**
2455
- * Hook that fetches studio workspaces and organizes them by projectId:dataset
2456
2169
  * @internal
2457
2170
  *
2458
- * @example
2459
- * ```tsx
2460
- * import {useStudioWorkspacesByProjectIdDataset} from '@sanity/sdk-react'
2461
- * import {Card, Code, Button} from '@sanity/ui'
2462
- * import {Suspense} from 'react'
2463
- *
2464
- * function WorkspacesCard() {
2465
- * const {workspacesByProjectIdAndDataset, error} = useStudioWorkspacesByProjectIdDataset()
2466
- * if (error) {
2467
- * return <div>Error: {error}</div>
2468
- * }
2469
- * return (
2470
- * <Card padding={4} radius={2} shadow={1}>
2471
- * <Code language="json">
2472
- * {JSON.stringify(workspacesByProjectIdAndDataset, null, 2)}
2473
- * </Code>
2474
- * </Card>
2475
- * )
2476
- * }
2477
- *
2478
- * // Wrap the component with Suspense since the hook may suspend
2479
- * function DashboardWorkspaces() {
2480
- * return (
2481
- * <Suspense fallback={<Button text="Loading..." disabled />}>
2482
- * <WorkspacesCard />
2483
- * </Suspense>
2484
- * )
2485
- * }
2486
- * ```
2487
- */
2488
- function useStudioWorkspacesByProjectIdDataset() {
2489
- let $ = c(8), t0;
2490
- $[0] === Symbol.for("react.memo_cache_sentinel") ? (t0 = {}, $[0] = t0) : t0 = $[0];
2491
- let [workspacesByProjectIdAndDataset, setWorkspacesByProjectIdAndDataset] = useState(t0), [error, setError] = useState(null), t1;
2492
- $[1] === Symbol.for("react.memo_cache_sentinel") ? (t1 = {
2493
- name: SDK_NODE_NAME,
2494
- connectTo: SDK_CHANNEL_NAME
2495
- }, $[1] = t1) : t1 = $[1];
2496
- let { fetch } = useWindowConnection(t1), t2, t3;
2497
- $[2] === fetch ? (t2 = $[3], t3 = $[4]) : (t2 = () => {
2498
- if (!fetch) return;
2499
- let fetchWorkspaces = async function fetchWorkspaces(signal) {
2500
- try {
2501
- let data = await fetch("dashboard/v1/context", void 0, { signal }), workspaceMap = {}, noProjectIdAndDataset = [];
2502
- data.context.availableResources.forEach((resource) => {
2503
- if (resource.type !== "studio") return;
2504
- if (!resource.projectId || !resource.dataset) {
2505
- noProjectIdAndDataset.push(resource);
2506
- return;
2507
- }
2508
- let key = `${resource.projectId}:${resource.dataset}`;
2509
- workspaceMap[key] || (workspaceMap[key] = []), workspaceMap[key].push(resource);
2510
- }), noProjectIdAndDataset.length > 0 && (workspaceMap["NO_PROJECT_ID:NO_DATASET"] = noProjectIdAndDataset), setWorkspacesByProjectIdAndDataset(workspaceMap), setError(null);
2511
- } catch (t4) {
2512
- let err = t4;
2513
- if (err instanceof Error) {
2514
- if (err.name === "AbortError") return;
2515
- setError("Failed to fetch workspaces");
2516
- }
2517
- }
2518
- }, controller = new AbortController();
2519
- return fetchWorkspaces(controller.signal), () => {
2520
- controller.abort();
2521
- };
2522
- }, t3 = [fetch], $[2] = fetch, $[3] = t2, $[4] = t3), useEffect(t2, t3);
2523
- let t4;
2524
- return $[5] !== error || $[6] !== workspacesByProjectIdAndDataset ? (t4 = {
2525
- workspacesByProjectIdAndDataset,
2526
- error
2527
- }, $[5] = error, $[6] = workspacesByProjectIdAndDataset, $[7] = t4) : t4 = $[7], t4;
2528
- }
2529
- /**
2530
- * @public
2531
- *
2532
- * Hook that provides a function to navigate to a given document in its parent Studio.
2171
+ * Reads whether a document is currently favorited. The write-side counterpart is
2172
+ * {@link useUpdateFavorite}.
2533
2173
  *
2534
- * Uses the `projectId` and `dataset` properties of the {@link DocumentHandle} you provide to resolve the correct Studio.
2535
- * This will only work if you have deployed a studio with a workspace with this `projectId` / `dataset` combination.
2174
+ * The hook suspends until the first favorite status resolves, so wrap the
2175
+ * component in a `<Suspense>` boundary.
2536
2176
  *
2537
- * @remarks If you write your own Document Handle to pass to this hook (as opposed to a Document Handle generated by another hook),
2538
- * it must include values for `documentId`, `documentType`, `projectId`, and `dataset`.
2539
- *
2540
- * @category Documents
2541
- * @param documentHandle - The document handle for the document to navigate to
2542
- * @param preferredStudioUrl - The preferred studio url to navigate to if you have multiple
2543
- * studios with the same projectId and dataset
2544
- * @returns An object containing:
2545
- * - `navigateToStudioDocument` - Function that when called will navigate to the studio document
2177
+ * @param props - The document handle plus the resource it lives in.
2178
+ * @returns `true` when the document is favorited, otherwise `false`.
2546
2179
  *
2547
2180
  * @example
2548
2181
  * ```tsx
2549
- * import {useNavigateToStudioDocument, type DocumentHandle} from '@sanity/sdk-react'
2550
- * import {Button} from '@sanity/ui'
2551
- * import {Suspense} from 'react'
2552
- *
2553
- * function NavigateButton({documentHandle}: {documentHandle: DocumentHandle}) {
2554
- * const {navigateToStudioDocument} = useNavigateToStudioDocument(documentHandle)
2555
- * return (
2556
- * <Button
2557
- * onClick={navigateToStudioDocument}
2558
- * text="Navigate to Studio Document"
2559
- * />
2560
- * )
2561
- * }
2182
+ * function FavoriteLabel(props: DocumentActionProps) {
2183
+ * const {documentId, documentType} = props
2184
+ * const isFavorited = useFavorite({documentId, documentType, resourceType: 'studio'})
2562
2185
  *
2563
- * // Wrap the component with Suspense since the hook may suspend
2564
- * function MyDocumentAction({documentHandle}: {documentHandle: DocumentHandle}) {
2565
- * return (
2566
- * <Suspense fallback={<Button text="Loading..." disabled />}>
2567
- * <NavigateButton documentHandle={documentHandle} />
2568
- * </Suspense>
2569
- * )
2186
+ * return <span>{isFavorited ? 'Favorited' : 'Not favorited'}</span>
2570
2187
  * }
2571
2188
  * ```
2572
2189
  */
2573
- function useNavigateToStudioDocument(documentHandle, preferredStudioUrl) {
2574
- let $ = c(8), { workspacesByProjectIdAndDataset } = useStudioWorkspacesByProjectIdDataset(), t0;
2575
- $[0] === Symbol.for("react.memo_cache_sentinel") ? (t0 = {
2576
- name: SDK_NODE_NAME,
2577
- connectTo: SDK_CHANNEL_NAME
2578
- }, $[0] = t0) : t0 = $[0];
2579
- let { sendMessage } = useWindowConnection(t0), t1;
2580
- $[1] !== documentHandle || $[2] !== preferredStudioUrl || $[3] !== sendMessage || $[4] !== workspacesByProjectIdAndDataset ? (t1 = () => {
2581
- let { projectId, dataset } = documentHandle;
2582
- if (!projectId || !dataset) {
2583
- console.warn("Project ID and dataset are required to navigate to a studio document");
2584
- return;
2585
- }
2586
- let workspace;
2587
- if (preferredStudioUrl) workspace = [...workspacesByProjectIdAndDataset[`${projectId}:${dataset}`] || [], ...workspacesByProjectIdAndDataset["NO_PROJECT_ID:NO_DATASET"] || []].find((w) => w.url === preferredStudioUrl);
2588
- else {
2589
- let workspaces = workspacesByProjectIdAndDataset[`${projectId}:${dataset}`];
2590
- workspaces?.length > 1 && (console.warn("Multiple workspaces found for document and no preferred studio url", documentHandle), console.warn("Using the first one", workspaces[0])), workspace = workspaces?.[0];
2591
- }
2592
- if (!workspace) {
2593
- console.warn(`No workspace found for document with projectId: ${projectId} and dataset: ${dataset}${preferredStudioUrl ? ` or with preferred studio url: ${preferredStudioUrl}` : ""}`);
2594
- return;
2595
- }
2596
- let message = {
2597
- type: "dashboard/v1/bridge/navigate-to-resource",
2598
- data: {
2599
- resourceId: workspace.id,
2600
- resourceType: "studio",
2601
- path: `/intent/edit/id=${documentHandle.documentId};type=${documentHandle.documentType}`
2602
- }
2603
- };
2604
- sendMessage(message.type, message.data);
2605
- }, $[1] = documentHandle, $[2] = preferredStudioUrl, $[3] = sendMessage, $[4] = workspacesByProjectIdAndDataset, $[5] = t1) : t1 = $[5];
2606
- let navigateToStudioDocument = t1, t2;
2607
- return $[6] === navigateToStudioDocument ? t2 = $[7] : (t2 = { navigateToStudioDocument }, $[6] = navigateToStudioDocument, $[7] = t2), t2;
2190
+ function useFavorite(props) {
2191
+ let context = useFavoriteContext(props), { data } = useFavoriteStatus(context);
2192
+ return data.isFavorited;
2608
2193
  }
2609
2194
  /**
2610
2195
  * @internal
@@ -2682,75 +2267,65 @@ function useRecordDocumentHistoryEvent(t0) {
2682
2267
  let recordEvent = t2, t3;
2683
2268
  return $[8] === recordEvent ? t3 = $[9] : (t3 = { recordEvent }, $[8] = recordEvent, $[9] = t3), t3;
2684
2269
  }
2685
- function resolveAppTitle(resource) {
2686
- return resource.manifest?.title || resource.activeDeployment?.manifest?.title || resource.title;
2687
- }
2270
+ const useSetFavorite = createMutationHook(setFavorite);
2688
2271
  /**
2689
- * Sets the browser's document title, automatically including the app's name
2690
- * from the manifest.
2691
- *
2692
- * This follows the same convention as Sanity Studio workspaces, where the
2693
- * workspace name is always present in the title:
2272
+ * @internal
2694
2273
  *
2695
- * - With a view title: `<viewTitle> | <appTitle>`
2696
- * - Without a view title: `<appTitle>`
2274
+ * Adds or removes a document from favorites. The read-side counterpart is
2275
+ * {@link useFavorite}, which reflects the change once the mutation settles.
2697
2276
  *
2698
- * The Sanity dashboard appends `| Sanity` to produce the final browser tab title.
2277
+ * Unlike {@link useFavorite}, this hook does not suspend.
2699
2278
  *
2700
- * @param viewTitle - An optional view-specific title to prepend to the app title.
2279
+ * @param props - The document handle plus the resource it lives in.
2280
+ * @returns `favorite`/`unfavorite` actions and the `{isPending, error, reset}`
2281
+ * mutation state.
2701
2282
  *
2702
2283
  * @example
2703
2284
  * ```tsx
2704
- * import {useWindowTitle} from '@sanity/sdk-react'
2285
+ * function FavoriteButton(props: DocumentActionProps) {
2286
+ * const {documentId, documentType} = props
2287
+ * const handle = {documentId, documentType, resourceType: 'studio'} as const
2288
+ * const isFavorited = useFavorite(handle)
2289
+ * const {favorite, unfavorite, isPending} = useUpdateFavorite(handle)
2705
2290
  *
2706
- * function MoviesList() {
2707
- * useWindowTitle('Movies')
2708
- * return <div>...</div>
2291
+ * return (
2292
+ * <Button
2293
+ * disabled={isPending}
2294
+ * onClick={() => (isFavorited ? unfavorite() : favorite())}
2295
+ * text={isFavorited ? 'Remove from favorites' : 'Add to favorites'}
2296
+ * />
2297
+ * )
2709
2298
  * }
2710
2299
  *
2711
- * // Browser tab: "Movies | My App | Sanity"
2712
- * ```
2713
- *
2714
- * @example
2715
- * ```tsx
2716
- * // Call without arguments to show just the app title
2717
- * function AppRoot() {
2718
- * useWindowTitle()
2719
- * return <Outlet />
2300
+ * // Wrap the component with Suspense since useFavorite suspends
2301
+ * function MyDocumentAction(props: DocumentActionProps) {
2302
+ * return (
2303
+ * <Suspense fallback={<Button text="Loading..." disabled />}>
2304
+ * <FavoriteButton {...props} />
2305
+ * </Suspense>
2306
+ * )
2720
2307
  * }
2721
- *
2722
- * // Browser tab: "My App | Sanity"
2723
2308
  * ```
2724
- *
2725
- * @public
2726
2309
  */
2727
- function useWindowTitle(viewTitle) {
2728
- let [appTitle, setAppTitle] = useState(null), { fetch } = useWindowConnection({
2729
- name: SDK_NODE_NAME,
2730
- connectTo: SDK_CHANNEL_NAME
2731
- });
2732
- useEffect(() => {
2733
- if (!fetch) return;
2734
- let controller = new AbortController();
2735
- async function fetchAppTitle(signal) {
2736
- try {
2737
- let title = resolveAppTitle((await fetch("dashboard/v1/context", void 0, { signal })).context.resource) || document.title;
2738
- title && setAppTitle(title);
2739
- } catch (err) {
2740
- if (err instanceof Error && err.name === "AbortError") return;
2741
- console.error("Failed to fetch app title from dashboard context:", err);
2742
- }
2743
- }
2744
- return fetchAppTitle(controller.signal), () => {
2745
- controller.abort();
2746
- };
2747
- }, [fetch]), useEffect(() => {
2748
- if (!appTitle) return;
2749
- let previous = document.title;
2750
- return document.title = viewTitle ? `${viewTitle} | ${appTitle}` : appTitle, () => {
2751
- document.title = previous;
2752
- };
2753
- }, [viewTitle, appTitle]);
2310
+ function useUpdateFavorite(props) {
2311
+ let $ = c(12), context = useFavoriteContext(props), { mutate, isPending, error, reset } = useSetFavorite(), t0;
2312
+ $[0] !== context || $[1] !== mutate ? (t0 = () => mutate({
2313
+ ...context,
2314
+ isFavorited: !0
2315
+ }), $[0] = context, $[1] = mutate, $[2] = t0) : t0 = $[2];
2316
+ let favorite = t0, t1;
2317
+ $[3] !== context || $[4] !== mutate ? (t1 = () => mutate({
2318
+ ...context,
2319
+ isFavorited: !1
2320
+ }), $[3] = context, $[4] = mutate, $[5] = t1) : t1 = $[5];
2321
+ let unfavorite = t1, t2;
2322
+ return $[6] !== error || $[7] !== favorite || $[8] !== isPending || $[9] !== reset || $[10] !== unfavorite ? (t2 = {
2323
+ favorite,
2324
+ unfavorite,
2325
+ isPending,
2326
+ error,
2327
+ reset
2328
+ }, $[6] = error, $[7] = favorite, $[8] = isPending, $[9] = reset, $[10] = unfavorite, $[11] = t2) : t2 = $[11], t2;
2754
2329
  }
2755
2330
  /**
2756
2331
  * Resolves the effective `projectId` for project-scoped hooks (`useProject`,
@@ -2773,17 +2348,44 @@ function useResolvedProjectId(options) {
2773
2348
  let { resource } = useNormalizedResourceOptions(t0), contextProjectId = useContext(ProjectContext), t1;
2774
2349
  return $[2] !== contextProjectId || $[3] !== options?.projectId || $[4] !== resource ? (t1 = options?.projectId ?? contextProjectId ?? (resource && isDatasetResource(resource) ? resource.projectId : void 0), $[2] = contextProjectId, $[3] = options?.projectId, $[4] = resource, $[5] = t1) : t1 = $[5], t1;
2775
2350
  }
2776
- const useDatasetsBase = createStateSourceHook({
2777
- getState: getDatasetsState,
2778
- shouldSuspend: (instance, projectHandle) => getDatasetsState(instance, projectHandle).getCurrent() === void 0,
2779
- suspender: resolveDatasets
2780
- }), useDatasets = (options) => {
2351
+ const useDatasetsBase = createFetcherHook(datasets);
2352
+ /**
2353
+ * Returns metadata for each dataset the current user has access to.
2354
+ *
2355
+ * @category Datasets
2356
+ * @param options - Optional project/resource to read datasets for. Defaults to
2357
+ * the resource named in `ResourceProvider`/`SDKProvider`.
2358
+ * @returns A {@link FetcherHookResult} whose `data` is the metadata for the
2359
+ * datasets.
2360
+ *
2361
+ * @example
2362
+ * ```tsx
2363
+ * const {data: datasets} = useDatasets()
2364
+ *
2365
+ * return (
2366
+ * <select>
2367
+ * {datasets.map((dataset) => (
2368
+ * <option key={dataset.name}>{dataset.name}</option>
2369
+ * ))}
2370
+ * </select>
2371
+ * )
2372
+ * ```
2373
+ *
2374
+ * @remarks
2375
+ * The `projectId` is resolved in order from:
2376
+ * 1. an explicit `projectId` option
2377
+ * 2. A legacy ProjectContext (e.g. a `<ResourceProvider projectId="…">` with no dataset), then
2378
+ * 3. The active resource (`ResourceProvider`/`SDKProvider`)
2379
+ * 4. `instance.config`.
2380
+ * @public
2381
+ */
2382
+ function useDatasets(options) {
2781
2383
  let $ = c(3), projectId = useResolvedProjectId(options), t0;
2782
2384
  return $[0] !== options || $[1] !== projectId ? (t0 = projectId ? {
2783
2385
  ...options,
2784
2386
  projectId
2785
2387
  } : options, $[0] = options, $[1] = projectId, $[2] = t0) : t0 = $[2], useDatasetsBase(t0);
2786
- };
2388
+ }
2787
2389
  /**
2788
2390
  * @internal
2789
2391
  *
@@ -4515,7 +4117,7 @@ function useUsers(options) {
4515
4117
  loadMore
4516
4118
  }, $[20] = data, $[21] = hasMore, $[22] = isPending, $[23] = loadMore, $[24] = t8) : t8 = $[24], t8;
4517
4119
  }
4518
- var version = "3.0.0-rc.2";
4120
+ var version = "3.0.0";
4519
4121
  function getEnv(key) {
4520
4122
  if (import.meta.env) return import.meta.env[key];
4521
4123
  if (typeof process < "u" && process.env) return process.env[key];
@@ -4526,6 +4128,6 @@ function getEnv(key) {
4526
4128
  * @internal
4527
4129
  */
4528
4130
  const REACT_SDK_VERSION = getEnv("PKG_VERSION") || `${version}-development`;
4529
- export { AuthBoundary, ComlinkTokenRefreshProvider, REACT_SDK_VERSION, ResourceProvider, SDKProvider, SDKStudioContext, SanityApp, SanityInstanceProvider, renderSanityApp, useActiveReleases, useAgentGenerate, useAgentPatch, useAgentPrompt, useAgentResourceContext, useAgentTransform, useAgentTranslate, useAllReleases, useApplication, useApplications, useApplyDocumentActions, useApplyReleaseActions, useAuthState, useAuthToken, useCheckPermissions, useClient, useCommentActions, useCommentThreads, useComments, useCreateDocument, useCurrentUser, useDashboardNavigate, useDashboardOrganizationId, useDatasets, useDeleteApplication, useDocument, useDocumentEvent, useDocumentPermissions, useDocumentPreview, useDocumentProjection, useDocumentSyncStatus, useDocuments, useEditDocument, useFrameConnection, useHandleAuthCallback, useInstallation, useInstallations, useLogOut, useLoginUrl, useManageFavorite, useNavigateToStudioDocument, useOrganization, useOrganizations, usePaginatedDocuments, usePerspective, usePresence, usePresenceForDocument, useProject, useProjects, useQuery, useRecordDocumentHistoryEvent, useReportPresence, useResource, useSanityInstance, useStudioWorkspacesByProjectIdDataset, useUpdateApplication, useUser, useUsers, useVerifyOrgProjects, useWindowConnection, useWindowTitle };
4131
+ export { AuthBoundary, ComlinkTokenRefreshProvider, REACT_SDK_VERSION, ResourceProvider, SDKProvider, SDKStudioContext, SanityApp, SanityInstanceProvider, renderSanityApp, useActiveReleases, useAgentGenerate, useAgentPatch, useAgentPrompt, useAgentTransform, useAgentTranslate, useAllReleases, useApplication, useApplications, useApplyDocumentActions, useApplyReleaseActions, useAuthState, useAuthToken, useCheckPermissions, useClient, useCommentActions, useCommentThreads, useComments, useCreateDocument, useCurrentUser, useDatasets, useDeleteApplication, useDocument, useDocumentEvent, useDocumentPermissions, useDocumentPreview, useDocumentProjection, useDocumentSyncStatus, useDocuments, useEditDocument, useFavorite, useFrameConnection, useHandleAuthCallback, useInstallation, useInstallations, useLogOut, useLoginUrl, useOrganization, useOrganizations, usePaginatedDocuments, usePerspective, usePresence, usePresenceForDocument, useProject, useProjects, useQuery, useRecordDocumentHistoryEvent, useReportPresence, useResource, useSanityInstance, useStudioWorkspacesByProjectIdDataset, useUpdateApplication, useUpdateFavorite, useUser, useUsers, useVerifyOrgProjects, useWindowConnection };
4530
4132
 
4531
4133
  //# sourceMappingURL=index.js.map