@sanity/sdk-react 3.0.0-rc.2 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/_exports/dashboard.d.ts +250 -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 +114 -616
  8. package/dist/index.js.map +1 -1
  9. package/dist/useStudioWorkspacesByProjectIdDataset-DxUlukmF.js +317 -0
  10. package/dist/useStudioWorkspacesByProjectIdDataset-DxUlukmF.js.map +1 -0
  11. package/package.json +12 -11
  12. package/src/_exports/dashboard.ts +12 -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/DashboardTokenRefresh.tsx +95 -0
  18. package/src/context/OrganizationResourcesProvider.test.tsx +9 -9
  19. package/src/context/OrganizationResourcesProvider.tsx +2 -2
  20. package/src/context/dashboardToken.ts +63 -0
  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/document/useCreateDocument.ts +2 -1
  37. package/src/utils/resolveOrgResources.test.ts +2 -2
  38. package/src/utils/resolveOrgResources.ts +2 -2
  39. package/src/context/WorkbenchTokenRefresh.tsx +0 -61
  40. package/src/context/workbenchToken.ts +0 -63
  41. package/src/hooks/dashboard/useManageFavorite.test.tsx +0 -379
  42. package/src/hooks/dashboard/useManageFavorite.ts +0 -173
  43. /package/src/hooks/{agent → dashboard}/useAgentResourceContext.test.tsx +0 -0
package/dist/index.js CHANGED
@@ -1,108 +1,19 @@
1
+ import { a as useWindowConnection, c as useSanityInstance, i as isDashboardEnvironment, l as SanityInstanceContext, n as useOrganizationId, o as useAuthState, r as DashboardTokenRefreshProvider, s as createStateSourceHook, t as useStudioWorkspacesByProjectIdDataset } from "./useStudioWorkspacesByProjectIdDataset-DxUlukmF.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 { PREVIEW_PROJECTION, createGroqSearchFilter, getClientErrorApiBody, getClientErrorApiDescription, getCommentsOptionsKey, getQueryKey, getUsersKey, initTelemetry, isDeepEqual, isProjectUserNotFoundClientError, isStudioConfig, parseCommentsOptionsKey, parseQueryKey, parseUsersKey, pickProperties, trackHookMounted, transformProjectionToPreview } from "@sanity/sdk/_internal";
4
+ import { AuthStateType, application, applications, applyDocumentActions, checkPermissions, createComment, createDocument, createSanityInstance, datasets, deleteApplication, editDocument, favorites, getActiveReleasesState, getAllReleasesState, 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";
5
+ import { PREVIEW_PROJECTION, createGroqSearchFilter, getClientErrorApiBody, getClientErrorApiDescription, getCommentsOptionsKey, getQueryKey, getUsersKey, initTelemetry, isDeepEqual, isProjectUserNotFoundClientError, isStudioConfig, parseCommentsOptionsKey, parseQueryKey, parseUsersKey, pickProperties, randomUuid, 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 { EMPTY, Observable, distinctUntilChanged, filter, firstValueFrom, from, identity, of, startWith, switchMap } from "rxjs";
9
+ import { getOrCreateChannel, getOrCreateController, releaseChannel } from "@sanity/sdk/comlink";
10
+ import { EMPTY, Observable, distinctUntilChanged, filter, firstValueFrom, identity, startWith, switchMap } from "rxjs";
10
11
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
11
- import { catchError, switchMap as switchMap$1 } from "rxjs/operators";
12
12
  import { createRoot } from "react-dom/client";
13
13
  import { agentGenerate, agentPatch, agentPrompt, agentTransform, agentTranslate } from "@sanity/sdk/agent";
14
14
  import "groq";
15
15
  import "@sanity/types";
16
16
  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
- /**
35
- * @internal
36
- * A React hook that subscribes to authentication state changes.
37
- *
38
- * This hook provides access to the current authentication state type from the Sanity auth store.
39
- * It automatically re-renders when the authentication state changes.
40
- *
41
- * @remarks
42
- * The hook uses `useSyncExternalStore` to safely subscribe to auth state changes
43
- * and ensure consistency between server and client rendering.
44
- *
45
- * @returns The current authentication state type
46
- *
47
- * @example
48
- * ```tsx
49
- * function AuthStatus() {
50
- * const authState = useAuthState()
51
- * return <div>Current auth state: {authState}</div>
52
- * }
53
- * ```
54
- */
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
- }
106
17
  /**
107
18
  * Component that handles token refresh in dashboard mode
108
19
  */
@@ -162,87 +73,6 @@ const ComlinkTokenRefreshProvider = (t0) => {
162
73
  return $[0] === children ? t1 = $[1] : (t1 = /* @__PURE__ */ jsx(DashboardTokenRefresh, { children }), $[0] = children, $[1] = t1), t1;
163
74
  }
164
75
  return children;
165
- }, OS_BUS_KEY = Symbol.for("sanity.os.bus");
166
- /**
167
- * Whether this app is running as a federated remote inside the workbench.
168
- *
169
- * Federation shares the host's realm, so the installed bus is visible on
170
- * `globalThis`. This is `false` in a standalone app, where we must never import
171
- * `@sanity/workbench` (it would install a bus and add bundle weight for no
172
- * reason). Note: this is a different embedding model to the Core UI iframe,
173
- * which is detected separately via the dashboard context — that signal is not
174
- * set on the federation path.
175
- *
176
- * @internal
177
- */
178
- function isWorkbenchEnvironment() {
179
- return typeof globalThis == "object" && OS_BUS_KEY in globalThis;
180
- }
181
- /**
182
- * Observes the session token issued by the workbench "OS", tracking the OS auth
183
- * state over time.
184
- *
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
187
- * `auth.token` state topic, emitting the current token — or `null` when the OS
188
- * is signed out — and re-emitting as the OS auth state changes, so sign-in/out
189
- * propagates instead of being captured once. Any bus error is treated as "no
190
- * token" (`null`). The token is used in-memory only and never persisted.
191
- *
192
- * @internal
193
- */
194
- function observeWorkbenchToken() {
195
- if (isWorkbenchEnvironment()) return from(import("@sanity/workbench")).pipe(switchMap$1(({ os }) => os.subscribe("auth.token")), catchError(() => of(null)));
196
- }
197
- /**
198
- * Asks the workbench "OS" to reissue the session token, e.g. after its current
199
- * 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.
202
- *
203
- * @internal
204
- */
205
- function refreshWorkbenchToken() {
206
- isWorkbenchEnvironment() && import("@sanity/workbench").then(({ os }) => os.emit("auth.token.refresh", void 0), () => {});
207
- }
208
- /**
209
- * Keeps the SDK auth token in sync with the workbench "OS".
210
- *
211
- * When running as a federated remote inside the workbench the OS owns the
212
- * session, so we subscribe to its `auth.token` stream and mirror each value into
213
- * the auth store — a token logs us in, `null` logs us out, and later OS
214
- * sign-in/out propagates automatically. When a request is rejected with a 401
215
- * (the token expired), we ask the OS to reissue rather than tearing the session
216
- * down; the new token arrives back through the same subscription.
217
- */
218
- function WorkbenchTokenRefresh(t0) {
219
- let $ = c(8), { children } = t0, instance = useSanityInstance(), authState = useAuthState(), processed401ErrorRef = useRef(null), t1, t2;
220
- $[0] === instance ? (t1 = $[1], t2 = $[2]) : (t1 = () => {
221
- let token$ = observeWorkbenchToken();
222
- if (!token$) return;
223
- let subscription = token$.subscribe((token) => setAuthToken(instance, token));
224
- return () => subscription.unsubscribe();
225
- }, t2 = [instance], $[0] = instance, $[1] = t1, $[2] = t2), useEffect(t1, t2);
226
- let t3;
227
- $[3] !== authState.error || $[4] !== authState.type ? (t3 = () => {
228
- 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);
230
- }, $[3] = authState.error, $[4] = authState.type, $[5] = t3) : t3 = $[5];
231
- let t4;
232
- return $[6] === authState ? t4 = $[7] : (t4 = [authState], $[6] = authState, $[7] = t4), useEffect(t3, t4), children;
233
- }
234
- /**
235
- * Provides workbench OS token refresh. No-op outside the workbench, where the
236
- * app uses its normal auth flow.
237
- * @public
238
- */
239
- const WorkbenchTokenRefreshProvider = (t0) => {
240
- let $ = c(2), { children } = t0;
241
- if (isWorkbenchEnvironment()) {
242
- let t1;
243
- return $[0] === children ? t1 = $[1] : (t1 = /* @__PURE__ */ jsx(WorkbenchTokenRefresh, { children }), $[0] = children, $[1] = t1), t1;
244
- }
245
- return children;
246
76
  };
247
77
  /**
248
78
  * @internal
@@ -699,7 +529,7 @@ function AuthBoundary(t0) {
699
529
  let t4;
700
530
  $[7] === props ? t4 = $[8] : (t4 = /* @__PURE__ */ jsx(AuthSwitch, { ...props }), $[7] = props, $[8] = t4);
701
531
  let t5;
702
- return $[9] !== FallbackComponent || $[10] !== t3 || $[11] !== t4 ? (t5 = /* @__PURE__ */ jsx(ComlinkTokenRefreshProvider, { children: /* @__PURE__ */ jsx(WorkbenchTokenRefreshProvider, { children: /* @__PURE__ */ jsx(ErrorBoundary, {
532
+ return $[9] !== FallbackComponent || $[10] !== t3 || $[11] !== t4 ? (t5 = /* @__PURE__ */ jsx(ComlinkTokenRefreshProvider, { children: /* @__PURE__ */ jsx(DashboardTokenRefreshProvider, { children: /* @__PURE__ */ jsx(ErrorBoundary, {
703
533
  FallbackComponent,
704
534
  resetKeys: t3,
705
535
  children: t4
@@ -712,7 +542,7 @@ function AuthSwitch(t0) {
712
542
  $[6] === instance.config ? t3 = $[7] : (t3 = isStudioConfig(instance.config), $[6] = instance.config, $[7] = t3);
713
543
  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
544
  if ($[8] !== isLoggedOut || $[9] !== isStudio || $[10] !== loginUrl ? (t4 = () => {
715
- isLoggedOut && !isInIframe() && !isStudio && !isWorkbenchEnvironment() && (window.location.href = loginUrl);
545
+ isLoggedOut && !isInIframe() && !isStudio && !isDashboardEnvironment() && (window.location.href = loginUrl);
716
546
  }, t5 = [
717
547
  isLoggedOut,
718
548
  loginUrl,
@@ -771,31 +601,6 @@ SDKStudioContext.displayName = "SDKStudioContext";
771
601
  */
772
602
  const DEFAULT_RESOURCE_NAME = "default";
773
603
  /**
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
604
  * Gets the id of the first resource from a settled request result. The request
800
605
  * is already scoped to a single organization, so the first item is the one we want.
801
606
  */
@@ -813,11 +618,11 @@ async function resolveOrgResources(instance, organizationId) {
813
618
  apiVersion: "v2026-07-09",
814
619
  scope: "global"
815
620
  }).observable), [mediaLibrariesResult, canvasesResult] = await Promise.allSettled([client.request({
816
- uri: "/media-libraries",
621
+ url: "/media-libraries",
817
622
  query: { organizationId },
818
623
  tag: "org-resources.media-libraries"
819
624
  }), client.request({
820
- uri: "/canvases",
625
+ url: "/canvases",
821
626
  query: { organizationId },
822
627
  tag: "org-resources.canvases"
823
628
  })]), mediaLibraryId = getFirstResourceId(mediaLibrariesResult), canvasId = getFirstResourceId(canvasesResult);
@@ -870,7 +675,7 @@ function InferredResourcesProvider(t0) {
870
675
  function OrganizationResourcesProvider(t0) {
871
676
  let $ = c(10), { resources: t1, inferMediaLibraryAndCanvas, children } = t0, t2;
872
677
  $[0] === t1 ? t2 = $[1] : (t2 = t1 === void 0 ? {} : t1, $[0] = t1, $[1] = t2);
873
- let explicitResources = t2, instance = useSanityInstance(), orgId = useDashboardOrganizationId();
678
+ let explicitResources = t2, instance = useSanityInstance(), orgId = useOrganizationId();
874
679
  if (!inferMediaLibraryAndCanvas || !orgId) {
875
680
  let t3;
876
681
  return $[2] !== children || $[3] !== explicitResources ? (t3 = /* @__PURE__ */ jsx(ResourcesContext.Provider, {
@@ -1885,71 +1690,6 @@ function useAgentPatch(resourceHandle) {
1885
1690
  return $[2] !== instance || $[3] !== resource ? (t1 = (options) => firstValueFrom(agentPatch(instance, options, resource)), $[2] = instance, $[3] = resource, $[4] = t1) : t1 = $[4], t1;
1886
1691
  }
1887
1692
  /**
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
1693
  * Returns a single application by id.
1954
1694
  *
1955
1695
  * The hook suspends until the first fetch succeeds, so `data` is always present.
@@ -2300,311 +2040,52 @@ function useResource() {
2300
2040
  return useEffectiveContextResource();
2301
2041
  }
2302
2042
  /**
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.
2312
- *
2313
- * This hook is intended to receive those messages, and takes a function to route to the correct path.
2043
+ * Resolves {@link UseFavoriteProps} into the {@link FavoriteDocumentContext}
2044
+ * shared by the favorites fetcher and mutation, defaulting a studio `resourceId`
2045
+ * from the instance's project and dataset.
2314
2046
  *
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
2047
  * @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
2048
  */
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;
2049
+ function useFavoriteContext(t0) {
2050
+ 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
2051
  if (resourceType === "studio" && (!projectId || !dataset)) throw Error("projectId and dataset are required for studio resources");
2406
2052
  let resourceId = resourceType === "studio" && !paramResourceId ? `${projectId}.${dataset}` : paramResourceId;
2407
2053
  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
- }), [
2054
+ let t1;
2055
+ return $[0] !== documentId || $[1] !== documentType || $[2] !== resourceId || $[3] !== resourceType || $[4] !== schemaName ? (t1 = {
2415
2056
  documentId,
2416
2057
  documentType,
2417
2058
  resourceId,
2418
2059
  resourceType,
2419
2060
  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
- };
2061
+ }, $[0] = documentId, $[1] = documentType, $[2] = resourceId, $[3] = resourceType, $[4] = schemaName, $[5] = t1) : t1 = $[5], t1;
2453
2062
  }
2063
+ const useFavoriteStatus = createFetcherHook(favorites);
2454
2064
  /**
2455
- * Hook that fetches studio workspaces and organizes them by projectId:dataset
2456
2065
  * @internal
2457
2066
  *
2458
- * @example
2459
- * ```tsx
2460
- * import {useStudioWorkspacesByProjectIdDataset} from '@sanity/sdk-react'
2461
- * import {Card, Code, Button} from '@sanity/ui'
2462
- * import {Suspense} from 'react'
2067
+ * Reads whether a document is currently favorited. The write-side counterpart is
2068
+ * {@link useUpdateFavorite}.
2463
2069
  *
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
- * }
2070
+ * The hook suspends until the first favorite status resolves, so wrap the
2071
+ * component in a `<Suspense>` boundary.
2477
2072
  *
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.
2533
- *
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.
2536
- *
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
2073
+ * @param props - The document handle plus the resource it lives in.
2074
+ * @returns `true` when the document is favorited, otherwise `false`.
2546
2075
  *
2547
2076
  * @example
2548
2077
  * ```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
- * }
2078
+ * function FavoriteLabel(props: DocumentActionProps) {
2079
+ * const {documentId, documentType} = props
2080
+ * const isFavorited = useFavorite({documentId, documentType, resourceType: 'studio'})
2562
2081
  *
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
- * )
2082
+ * return <span>{isFavorited ? 'Favorited' : 'Not favorited'}</span>
2570
2083
  * }
2571
2084
  * ```
2572
2085
  */
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;
2086
+ function useFavorite(props) {
2087
+ let context = useFavoriteContext(props), { data } = useFavoriteStatus(context);
2088
+ return data.isFavorited;
2608
2089
  }
2609
2090
  /**
2610
2091
  * @internal
@@ -2682,75 +2163,65 @@ function useRecordDocumentHistoryEvent(t0) {
2682
2163
  let recordEvent = t2, t3;
2683
2164
  return $[8] === recordEvent ? t3 = $[9] : (t3 = { recordEvent }, $[8] = recordEvent, $[9] = t3), t3;
2684
2165
  }
2685
- function resolveAppTitle(resource) {
2686
- return resource.manifest?.title || resource.activeDeployment?.manifest?.title || resource.title;
2687
- }
2166
+ const useSetFavorite = createMutationHook(setFavorite);
2688
2167
  /**
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:
2168
+ * @internal
2694
2169
  *
2695
- * - With a view title: `<viewTitle> | <appTitle>`
2696
- * - Without a view title: `<appTitle>`
2170
+ * Adds or removes a document from favorites. The read-side counterpart is
2171
+ * {@link useFavorite}, which reflects the change once the mutation settles.
2697
2172
  *
2698
- * The Sanity dashboard appends `| Sanity` to produce the final browser tab title.
2173
+ * Unlike {@link useFavorite}, this hook does not suspend.
2699
2174
  *
2700
- * @param viewTitle - An optional view-specific title to prepend to the app title.
2175
+ * @param props - The document handle plus the resource it lives in.
2176
+ * @returns `favorite`/`unfavorite` actions and the `{isPending, error, reset}`
2177
+ * mutation state.
2701
2178
  *
2702
2179
  * @example
2703
2180
  * ```tsx
2704
- * import {useWindowTitle} from '@sanity/sdk-react'
2181
+ * function FavoriteButton(props: DocumentActionProps) {
2182
+ * const {documentId, documentType} = props
2183
+ * const handle = {documentId, documentType, resourceType: 'studio'} as const
2184
+ * const isFavorited = useFavorite(handle)
2185
+ * const {favorite, unfavorite, isPending} = useUpdateFavorite(handle)
2705
2186
  *
2706
- * function MoviesList() {
2707
- * useWindowTitle('Movies')
2708
- * return <div>...</div>
2187
+ * return (
2188
+ * <Button
2189
+ * disabled={isPending}
2190
+ * onClick={() => (isFavorited ? unfavorite() : favorite())}
2191
+ * text={isFavorited ? 'Remove from favorites' : 'Add to favorites'}
2192
+ * />
2193
+ * )
2709
2194
  * }
2710
2195
  *
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 />
2196
+ * // Wrap the component with Suspense since useFavorite suspends
2197
+ * function MyDocumentAction(props: DocumentActionProps) {
2198
+ * return (
2199
+ * <Suspense fallback={<Button text="Loading..." disabled />}>
2200
+ * <FavoriteButton {...props} />
2201
+ * </Suspense>
2202
+ * )
2720
2203
  * }
2721
- *
2722
- * // Browser tab: "My App | Sanity"
2723
2204
  * ```
2724
- *
2725
- * @public
2726
2205
  */
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]);
2206
+ function useUpdateFavorite(props) {
2207
+ let $ = c(12), context = useFavoriteContext(props), { mutate, isPending, error, reset } = useSetFavorite(), t0;
2208
+ $[0] !== context || $[1] !== mutate ? (t0 = () => mutate({
2209
+ ...context,
2210
+ isFavorited: !0
2211
+ }), $[0] = context, $[1] = mutate, $[2] = t0) : t0 = $[2];
2212
+ let favorite = t0, t1;
2213
+ $[3] !== context || $[4] !== mutate ? (t1 = () => mutate({
2214
+ ...context,
2215
+ isFavorited: !1
2216
+ }), $[3] = context, $[4] = mutate, $[5] = t1) : t1 = $[5];
2217
+ let unfavorite = t1, t2;
2218
+ return $[6] !== error || $[7] !== favorite || $[8] !== isPending || $[9] !== reset || $[10] !== unfavorite ? (t2 = {
2219
+ favorite,
2220
+ unfavorite,
2221
+ isPending,
2222
+ error,
2223
+ reset
2224
+ }, $[6] = error, $[7] = favorite, $[8] = isPending, $[9] = reset, $[10] = unfavorite, $[11] = t2) : t2 = $[11], t2;
2754
2225
  }
2755
2226
  /**
2756
2227
  * Resolves the effective `projectId` for project-scoped hooks (`useProject`,
@@ -2773,17 +2244,44 @@ function useResolvedProjectId(options) {
2773
2244
  let { resource } = useNormalizedResourceOptions(t0), contextProjectId = useContext(ProjectContext), t1;
2774
2245
  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
2246
  }
2776
- const useDatasetsBase = createStateSourceHook({
2777
- getState: getDatasetsState,
2778
- shouldSuspend: (instance, projectHandle) => getDatasetsState(instance, projectHandle).getCurrent() === void 0,
2779
- suspender: resolveDatasets
2780
- }), useDatasets = (options) => {
2247
+ const useDatasetsBase = createFetcherHook(datasets);
2248
+ /**
2249
+ * Returns metadata for each dataset the current user has access to.
2250
+ *
2251
+ * @category Datasets
2252
+ * @param options - Optional project/resource to read datasets for. Defaults to
2253
+ * the resource named in `ResourceProvider`/`SDKProvider`.
2254
+ * @returns A {@link FetcherHookResult} whose `data` is the metadata for the
2255
+ * datasets.
2256
+ *
2257
+ * @example
2258
+ * ```tsx
2259
+ * const {data: datasets} = useDatasets()
2260
+ *
2261
+ * return (
2262
+ * <select>
2263
+ * {datasets.map((dataset) => (
2264
+ * <option key={dataset.name}>{dataset.name}</option>
2265
+ * ))}
2266
+ * </select>
2267
+ * )
2268
+ * ```
2269
+ *
2270
+ * @remarks
2271
+ * The `projectId` is resolved in order from:
2272
+ * 1. an explicit `projectId` option
2273
+ * 2. A legacy ProjectContext (e.g. a `<ResourceProvider projectId="…">` with no dataset), then
2274
+ * 3. The active resource (`ResourceProvider`/`SDKProvider`)
2275
+ * 4. `instance.config`.
2276
+ * @public
2277
+ */
2278
+ function useDatasets(options) {
2781
2279
  let $ = c(3), projectId = useResolvedProjectId(options), t0;
2782
2280
  return $[0] !== options || $[1] !== projectId ? (t0 = projectId ? {
2783
2281
  ...options,
2784
2282
  projectId
2785
2283
  } : options, $[0] = options, $[1] = projectId, $[2] = t0) : t0 = $[2], useDatasetsBase(t0);
2786
- };
2284
+ }
2787
2285
  /**
2788
2286
  * @internal
2789
2287
  *
@@ -3043,7 +2541,7 @@ function useCreateDocument(options) {
3043
2541
  trackHookUsage(useSanityInstance(), "useCreateDocument");
3044
2542
  let apply = useApplyDocumentActions(), t0;
3045
2543
  return $[0] !== apply || $[1] !== options ? (t0 = async (initialValue, overrides) => {
3046
- let documentId = overrides?.documentId ?? options.documentId ?? crypto.randomUUID(), handle = {
2544
+ let documentId = overrides?.documentId ?? options.documentId ?? randomUuid(), handle = {
3047
2545
  ...options,
3048
2546
  documentId
3049
2547
  };
@@ -4515,7 +4013,7 @@ function useUsers(options) {
4515
4013
  loadMore
4516
4014
  }, $[20] = data, $[21] = hasMore, $[22] = isPending, $[23] = loadMore, $[24] = t8) : t8 = $[24], t8;
4517
4015
  }
4518
- var version = "3.0.0-rc.2";
4016
+ var version = "3.1.0";
4519
4017
  function getEnv(key) {
4520
4018
  if (import.meta.env) return import.meta.env[key];
4521
4019
  if (typeof process < "u" && process.env) return process.env[key];
@@ -4526,6 +4024,6 @@ function getEnv(key) {
4526
4024
  * @internal
4527
4025
  */
4528
4026
  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 };
4027
+ 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
4028
 
4531
4029
  //# sourceMappingURL=index.js.map