@sanity/sdk-react 3.0.0-rc.0 → 3.0.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/README.md +103 -155
  2. package/dist/index.d.ts +1564 -682
  3. package/dist/index.js +1118 -612
  4. package/dist/index.js.map +1 -1
  5. package/package.json +51 -54
  6. package/src/_exports/index.ts +2 -4
  7. package/src/_exports/sdk-react.ts +26 -17
  8. package/src/components/SDKProvider.test.tsx +173 -32
  9. package/src/components/SDKProvider.tsx +87 -28
  10. package/src/components/SanityApp.test.tsx +68 -12
  11. package/src/components/SanityApp.tsx +94 -87
  12. package/src/components/auth/AuthBoundary.recovery.test.tsx +86 -0
  13. package/src/components/auth/AuthBoundary.test.tsx +26 -8
  14. package/src/components/auth/AuthBoundary.tsx +28 -6
  15. package/src/components/auth/DashboardAccessRequest.tsx +37 -0
  16. package/src/components/auth/LoginError.test.tsx +191 -5
  17. package/src/components/auth/LoginError.tsx +103 -59
  18. package/src/components/errors/ChunkLoadError.test.tsx +59 -0
  19. package/src/components/errors/ChunkLoadError.tsx +56 -0
  20. package/src/components/errors/CorsErrorComponent.tsx +2 -2
  21. package/src/components/errors/chunkReloadStorage.ts +57 -0
  22. package/src/config/handles.ts +29 -27
  23. package/src/constants.ts +5 -0
  24. package/src/context/ComlinkTokenRefresh.test.tsx +10 -27
  25. package/src/context/DefaultResourceContext.ts +3 -3
  26. package/src/context/OrganizationResourcesProvider.test.tsx +189 -0
  27. package/src/context/OrganizationResourcesProvider.tsx +111 -0
  28. package/src/context/PerspectiveContext.ts +3 -3
  29. package/src/context/ProjectContext.ts +15 -0
  30. package/src/context/ResourceProvider.test.tsx +72 -90
  31. package/src/context/ResourceProvider.tsx +59 -93
  32. package/src/context/SDKStudioContext.test.tsx +28 -33
  33. package/src/context/SanityInstanceProvider.test.tsx +100 -0
  34. package/src/context/SanityInstanceProvider.tsx +71 -0
  35. package/src/context/WorkbenchTokenRefresh.test.tsx +106 -0
  36. package/src/context/WorkbenchTokenRefresh.tsx +61 -0
  37. package/src/context/renderSanityApp.test.tsx +151 -49
  38. package/src/context/renderSanityApp.tsx +12 -8
  39. package/src/context/workbenchToken.ts +63 -0
  40. package/src/hooks/access/useCheckPermissions.test-d.ts +15 -0
  41. package/src/hooks/access/useCheckPermissions.test.tsx +53 -0
  42. package/src/hooks/access/useCheckPermissions.ts +24 -0
  43. package/src/hooks/agent/agentActions.test.tsx +1 -1
  44. package/src/hooks/agent/agentActions.ts +51 -71
  45. package/src/hooks/applications/useApplication.test-d.ts +30 -0
  46. package/src/hooks/applications/useApplication.ts +22 -0
  47. package/src/hooks/applications/useApplications.test-d.ts +31 -0
  48. package/src/hooks/applications/useApplications.ts +25 -0
  49. package/src/hooks/applications/useCreateUserApplication.test-d.ts +14 -0
  50. package/src/hooks/applications/useCreateUserApplication.ts +11 -0
  51. package/src/hooks/applications/useDeleteApplication.test-d.ts +12 -0
  52. package/src/hooks/applications/useDeleteApplication.ts +11 -0
  53. package/src/hooks/applications/useDeleteUserApplication.test-d.ts +14 -0
  54. package/src/hooks/applications/useDeleteUserApplication.ts +11 -0
  55. package/src/hooks/applications/useUpdateApplication.test-d.ts +12 -0
  56. package/src/hooks/applications/useUpdateApplication.ts +11 -0
  57. package/src/hooks/applications/useUpdateUserApplication.test-d.ts +14 -0
  58. package/src/hooks/applications/useUpdateUserApplication.ts +11 -0
  59. package/src/hooks/applications/useUserApplication.test-d.ts +11 -0
  60. package/src/hooks/applications/useUserApplication.ts +14 -0
  61. package/src/hooks/applications/useUserApplications.test-d.ts +11 -0
  62. package/src/hooks/applications/useUserApplications.ts +14 -0
  63. package/src/hooks/auth/useDashboardOrganizationId.test.tsx +2 -8
  64. package/src/hooks/auth/useVerifyOrgProjects.test.tsx +8 -32
  65. package/src/hooks/auth/useVerifyOrgProjects.tsx +13 -6
  66. package/src/hooks/client/useClient.test.tsx +1 -4
  67. package/src/hooks/client/useClient.ts +1 -0
  68. package/src/hooks/context/useResource.test.tsx +32 -0
  69. package/src/hooks/context/useResource.ts +24 -0
  70. package/src/hooks/context/useSanityInstance.test.tsx +72 -3
  71. package/src/hooks/context/useSanityInstance.ts +30 -8
  72. package/src/hooks/dashboard/useManageFavorite.test.tsx +110 -46
  73. package/src/hooks/dashboard/useManageFavorite.ts +10 -16
  74. package/src/hooks/dashboard/useNavigateToStudioDocument.test.ts +5 -7
  75. package/src/hooks/dashboard/useNavigateToStudioDocument.ts +2 -6
  76. package/src/hooks/dashboard/useRecordDocumentHistoryEvent.test.ts +0 -2
  77. package/src/hooks/dashboard/useRecordDocumentHistoryEvent.ts +1 -2
  78. package/src/hooks/dashboard/useWindowTitle.test.ts +244 -0
  79. package/src/hooks/dashboard/useWindowTitle.ts +114 -0
  80. package/src/hooks/datasets/useDatasets.test.tsx +116 -0
  81. package/src/hooks/datasets/useDatasets.ts +33 -13
  82. package/src/hooks/document/useApplyDocumentActions.test.tsx +48 -0
  83. package/src/hooks/document/useApplyDocumentActions.ts +66 -48
  84. package/src/hooks/document/useCreateDocument.test.tsx +83 -0
  85. package/src/hooks/document/useCreateDocument.ts +117 -0
  86. package/src/hooks/document/useDocument.test.tsx +37 -8
  87. package/src/hooks/document/useDocument.ts +131 -66
  88. package/src/hooks/document/useDocumentEvent.test.tsx +16 -4
  89. package/src/hooks/document/useDocumentEvent.ts +16 -10
  90. package/src/hooks/document/useDocumentPermissions.test.tsx +8 -7
  91. package/src/hooks/document/useDocumentPermissions.ts +71 -39
  92. package/src/hooks/document/useDocumentSyncStatus.test.ts +1 -0
  93. package/src/hooks/document/useDocumentSyncStatus.ts +13 -9
  94. package/src/hooks/document/useEditDocument.test.tsx +21 -14
  95. package/src/hooks/document/useEditDocument.ts +163 -17
  96. package/src/hooks/documents/useDocuments.test.tsx +64 -44
  97. package/src/hooks/documents/useDocuments.ts +36 -20
  98. package/src/hooks/helpers/createCallbackHook.test.tsx +13 -19
  99. package/src/hooks/helpers/createFetcherHook.test.tsx +180 -0
  100. package/src/hooks/helpers/createFetcherHook.ts +69 -0
  101. package/src/hooks/helpers/createMutationHook.test.tsx +125 -0
  102. package/src/hooks/helpers/createMutationHook.tsx +93 -0
  103. package/src/hooks/helpers/createStateSourceHook.test.tsx +10 -10
  104. package/src/hooks/helpers/createStateSourceHook.tsx +2 -1
  105. package/src/hooks/{document/useApplyDocumentActions.test.ts → helpers/useApplyActions.test.tsx} +92 -53
  106. package/src/hooks/helpers/useApplyActions.ts +68 -0
  107. package/src/hooks/helpers/useNormalizedResourceOptions.test.tsx +253 -0
  108. package/src/hooks/helpers/useNormalizedResourceOptions.ts +92 -50
  109. package/src/hooks/helpers/useResolvedProjectId.test.tsx +59 -0
  110. package/src/hooks/helpers/useResolvedProjectId.ts +35 -0
  111. package/src/hooks/helpers/useTrackHookUsage.ts +37 -0
  112. package/src/hooks/installations/useInstallation.test-d.ts +19 -0
  113. package/src/hooks/installations/useInstallation.ts +22 -0
  114. package/src/hooks/installations/useInstallations.test-d.ts +26 -0
  115. package/src/hooks/installations/useInstallations.ts +25 -0
  116. package/src/hooks/organizations/useOrganization.test-d.ts +60 -0
  117. package/src/hooks/organizations/useOrganization.test.ts +63 -0
  118. package/src/hooks/organizations/useOrganization.ts +34 -0
  119. package/src/hooks/organizations/useOrganizations.test-d.ts +68 -0
  120. package/src/hooks/organizations/useOrganizations.test.ts +63 -0
  121. package/src/hooks/organizations/useOrganizations.ts +39 -0
  122. package/src/hooks/paginatedDocuments/usePaginatedDocuments.test.tsx +34 -27
  123. package/src/hooks/paginatedDocuments/usePaginatedDocuments.ts +40 -22
  124. package/src/hooks/presence/usePresence.test.tsx +0 -15
  125. package/src/hooks/presence/usePresence.ts +9 -20
  126. package/src/hooks/preview/useDocumentPreview.test.tsx +0 -1
  127. package/src/hooks/preview/useDocumentPreview.tsx +10 -8
  128. package/src/hooks/projection/useDocumentProjection.test.tsx +78 -9
  129. package/src/hooks/projection/useDocumentProjection.ts +81 -2
  130. package/src/hooks/projects/useProject.test-d.ts +56 -0
  131. package/src/hooks/projects/useProject.test.tsx +120 -0
  132. package/src/hooks/projects/useProject.ts +42 -43
  133. package/src/hooks/projects/useProjects.test-d.ts +60 -0
  134. package/src/hooks/projects/useProjects.test.ts +45 -98
  135. package/src/hooks/projects/useProjects.ts +19 -26
  136. package/src/hooks/query/useQuery.test.tsx +6 -5
  137. package/src/hooks/query/useQuery.ts +91 -9
  138. package/src/hooks/releases/useActiveReleases.test.tsx +13 -7
  139. package/src/hooks/releases/useActiveReleases.ts +28 -24
  140. package/src/hooks/releases/useAllReleases.test.tsx +93 -0
  141. package/src/hooks/releases/useAllReleases.ts +62 -0
  142. package/src/hooks/releases/useApplyReleaseActions.test.tsx +66 -0
  143. package/src/hooks/releases/useApplyReleaseActions.ts +82 -0
  144. package/src/hooks/releases/usePerspective.test.tsx +18 -10
  145. package/src/hooks/releases/usePerspective.ts +13 -17
  146. package/src/hooks/users/useUser.test.tsx +3 -9
  147. package/src/hooks/users/useUser.ts +2 -0
  148. package/src/hooks/users/useUsers.test.tsx +103 -7
  149. package/src/hooks/users/useUsers.ts +37 -3
  150. package/src/utils/resolveOrgResources.test.ts +111 -0
  151. package/src/utils/resolveOrgResources.ts +69 -0
  152. package/src/hooks/context/useDefaultResource.test.tsx +0 -25
  153. package/src/hooks/context/useDefaultResource.ts +0 -30
  154. package/src/hooks/dashboard/useDispatchIntent.test.ts +0 -254
  155. package/src/hooks/dashboard/useDispatchIntent.ts +0 -157
  156. package/src/hooks/dashboard/utils/useResourceIdFromDocumentHandle.test.ts +0 -107
  157. package/src/hooks/dashboard/utils/useResourceIdFromDocumentHandle.ts +0 -39
  158. package/src/hooks/datasets/useDatasets.test.ts +0 -66
  159. package/src/hooks/helpers/useNormalizedResourceOptions.test.ts +0 -65
  160. package/src/hooks/projects/useProject.test.ts +0 -79
package/package.json CHANGED
@@ -1,30 +1,37 @@
1
1
  {
2
2
  "name": "@sanity/sdk-react",
3
- "version": "3.0.0-rc.0",
3
+ "version": "3.0.0-rc.1",
4
4
  "private": false,
5
5
  "description": "Sanity SDK React toolkit for Content OS",
6
6
  "keywords": [
7
- "sanity",
8
- "sdk",
9
- "content operating system",
10
7
  "cms",
8
+ "content",
9
+ "content operating system",
11
10
  "headless",
12
11
  "realtime",
13
- "content"
12
+ "sanity",
13
+ "sdk"
14
14
  ],
15
15
  "homepage": "https://github.com/sanity-io/sdk/tree/main/packages/react/README.md",
16
16
  "bugs": {
17
17
  "url": "https://github.com/sanity-io/sdk/issues"
18
18
  },
19
+ "license": "MIT",
20
+ "author": "Sanity <developers@sanity.io>",
19
21
  "repository": {
20
22
  "type": "git",
21
23
  "url": "git+https://github.com/sanity-io/sdk.git",
22
24
  "directory": "packages/react"
23
25
  },
24
- "license": "MIT",
25
- "author": "Sanity <developers@sanity.io>",
26
- "sideEffects": false,
26
+ "files": [
27
+ "dist",
28
+ "src"
29
+ ],
27
30
  "type": "module",
31
+ "sideEffects": false,
32
+ "main": "./dist/index.js",
33
+ "module": "./dist/index.js",
34
+ "types": "./dist/index.d.ts",
28
35
  "exports": {
29
36
  ".": {
30
37
  "source": "./src/_exports/index.ts",
@@ -33,71 +40,61 @@
33
40
  },
34
41
  "./package.json": "./package.json"
35
42
  },
36
- "main": "./dist/index.js",
37
- "module": "./dist/index.js",
38
- "types": "./dist/index.d.ts",
39
- "files": [
40
- "dist",
41
- "src"
42
- ],
43
- "browserslist": "extends @sanity/browserslist-config",
44
- "prettier": "@sanity/prettier-config",
43
+ "publishConfig": {
44
+ "access": "public"
45
+ },
45
46
  "dependencies": {
46
- "@sanity/client": "^7.14.1",
47
- "@sanity/message-protocol": "^0.18.0",
48
- "@sanity/types": "^5.2.0",
49
- "@types/lodash-es": "^4.17.12",
50
- "lodash-es": "^4.17.21",
51
- "react-error-boundary": "^5.0.0",
47
+ "@sanity/client": "^7.23.2",
48
+ "@sanity/message-protocol": "^0.23.0",
49
+ "@sanity/types": "^6.5.0",
50
+ "@sanity/workbench": "0.1.0-alpha.24",
51
+ "groq": "3.88.1-typegen-experimental.0",
52
+ "react-compiler-runtime": "^1.0.0",
53
+ "react-error-boundary": "^6.1.2",
52
54
  "rxjs": "^7.8.2",
53
- "@sanity/sdk": "3.0.0-rc.0"
55
+ "xstate": "^5.31.0",
56
+ "@sanity/sdk": "3.0.0-rc.1"
54
57
  },
55
58
  "devDependencies": {
56
59
  "@sanity/browserslist-config": "^1.0.5",
57
- "@sanity/comlink": "^3.1.1",
58
- "@sanity/pkg-utils": "^8.1.29",
59
- "@sanity/prettier-config": "^1.0.6",
60
+ "@sanity/comlink": "^4.0.1",
61
+ "@sanity/pkg-utils": "^10.9.2",
60
62
  "@testing-library/jest-dom": "^6.9.1",
61
- "@testing-library/react": "^16.3.0",
62
- "@types/node": "^22.19.1",
63
- "@types/react": "^19.2.7",
63
+ "@testing-library/react": "^16.3.2",
64
+ "@types/node": "^24.13.3",
65
+ "@types/react": "^19.2.17",
64
66
  "@types/react-dom": "^19.2.3",
65
- "@vitejs/plugin-react": "^4.7.0",
66
- "@vitest/coverage-v8": "3.2.4",
67
- "babel-plugin-react-compiler": "19.1.0-rc.1",
68
- "eslint": "^9.22.0",
69
- "groq-js": "^1.22.0",
70
- "jsdom": "^25.0.1",
71
- "prettier": "^3.7.3",
72
- "react": "^19.2.1",
73
- "react-dom": "^19.2.1",
74
- "rollup-plugin-visualizer": "^5.14.0",
75
- "typescript": "^5.8.3",
76
- "vite": "^6.3.4",
77
- "vitest": "^3.2.4",
67
+ "@vitejs/plugin-react": "^6.0.3",
68
+ "@vitest/coverage-v8": "^4.1.10",
69
+ "babel-plugin-react-compiler": "^1.0.0",
70
+ "eslint": "^10.6.0",
71
+ "groq-js": "^2.0.0",
72
+ "jsdom": "^29.1.1",
73
+ "oxfmt": "^0.58.0",
74
+ "react": "^19.2.7",
75
+ "react-dom": "^19.2.7",
76
+ "rollup-plugin-visualizer": "^7.0.1",
77
+ "typescript": "^6.0.3",
78
+ "vite": "^8.1.5",
79
+ "vitest": "^4.1.10",
78
80
  "@repo/config-eslint": "0.0.0",
79
81
  "@repo/config-test": "0.0.1",
80
82
  "@repo/package.bundle": "3.82.0",
81
- "@repo/tsconfig": "0.0.1",
82
- "@repo/package.config": "0.0.1"
83
+ "@repo/package.config": "0.0.1",
84
+ "@repo/tsconfig": "0.0.1"
83
85
  },
84
86
  "peerDependencies": {
85
- "react": "^19.2.0",
86
- "react-dom": "^19.2.0"
87
- },
88
- "engines": {
89
- "node": ">=20.19"
90
- },
91
- "publishConfig": {
92
- "access": "public"
87
+ "react": "^18.0.0 || ^19.0.0",
88
+ "react-dom": "^18.0.0 || ^19.0.0"
93
89
  },
90
+ "browserslist": "extends @sanity/browserslist-config",
94
91
  "scripts": {
95
92
  "build": "pkg build --strict --clean --check",
96
93
  "build:bundle": "vite build --configLoader runner --config package.bundle.ts",
97
94
  "clean": "rimraf dist",
98
95
  "dev": "pkg watch",
99
96
  "docs": "typedoc --json docs/typedoc.json --tsconfig ./tsconfig.dist.json",
100
- "format": "prettier --write --cache --ignore-unknown .",
97
+ "format": "oxfmt",
101
98
  "lint": "eslint .",
102
99
  "test": "vitest run",
103
100
  "test:coverage": "vitest run --coverage",
@@ -1,6 +1,4 @@
1
1
  export * from './sdk-react.ts'
2
2
  export * from '@sanity/sdk'
3
-
4
- // Explicitly re-export React-layer handle types to resolve ambiguity with @sanity/sdk versions.
5
- // These shadow the core (strict) versions for @sanity/sdk-react consumers.
6
- export {type DocumentHandle, type DocumentTypeHandle, type ResourceHandle} from './sdk-react.ts'
3
+ // React-layer handles shadow core equivalents when imported from @sanity/sdk-react
4
+ export type {DocumentHandle, DocumentTypeHandle, ResourceHandle} from './sdk-react.ts'
@@ -1,21 +1,19 @@
1
1
  /**
2
2
  * @module exports
3
3
  */
4
-
5
- // React-layer handle types (relaxed) — shadow core versions for @sanity/sdk-react consumers.
6
- // resource is optional and resolved from context when not provided.
7
4
  export {AuthBoundary, type AuthBoundaryProps} from '../components/auth/AuthBoundary'
8
5
  export {SanityApp, type SanityAppProps} from '../components/SanityApp'
9
6
  export {SDKProvider, type SDKProviderProps} from '../components/SDKProvider'
10
- export {
11
- type DocumentHandle as DocumentHandle,
12
- type DocumentTypeHandle as DocumentTypeHandle,
13
- type ResourceHandle as ResourceHandle,
14
- } from '../config/handles'
7
+ export {type DocumentHandle, type DocumentTypeHandle, type ResourceHandle} from '../config/handles'
15
8
  export {ComlinkTokenRefreshProvider} from '../context/ComlinkTokenRefresh'
16
9
  export {renderSanityApp} from '../context/renderSanityApp'
17
10
  export {ResourceProvider, type ResourceProviderProps} from '../context/ResourceProvider'
11
+ export {
12
+ SanityInstanceProvider,
13
+ type SanityInstanceProviderProps,
14
+ } from '../context/SanityInstanceProvider'
18
15
  export {SDKStudioContext, type StudioWorkspaceHandle} from '../context/SDKStudioContext'
16
+ export {useCheckPermissions} from '../hooks/access/useCheckPermissions'
19
17
  export {
20
18
  useAgentGenerate,
21
19
  useAgentPatch,
@@ -27,6 +25,15 @@ export {
27
25
  type AgentResourceContextOptions,
28
26
  useAgentResourceContext,
29
27
  } from '../hooks/agent/useAgentResourceContext'
28
+ export {useApplication} from '../hooks/applications/useApplication'
29
+ export {useApplications} from '../hooks/applications/useApplications'
30
+ export {useCreateUserApplication} from '../hooks/applications/useCreateUserApplication'
31
+ export {useDeleteApplication} from '../hooks/applications/useDeleteApplication'
32
+ export {useDeleteUserApplication} from '../hooks/applications/useDeleteUserApplication'
33
+ export {useUpdateApplication} from '../hooks/applications/useUpdateApplication'
34
+ export {useUpdateUserApplication} from '../hooks/applications/useUpdateUserApplication'
35
+ export {useUserApplication} from '../hooks/applications/useUserApplication'
36
+ export {useUserApplications} from '../hooks/applications/useUserApplications'
30
37
  export {useAuthState} from '../hooks/auth/useAuthState'
31
38
  export {useAuthToken} from '../hooks/auth/useAuthToken'
32
39
  export {useCurrentUser} from '../hooks/auth/useCurrentUser'
@@ -48,10 +55,9 @@ export {
48
55
  type WindowConnection,
49
56
  type WindowMessageHandler,
50
57
  } from '../hooks/comlink/useWindowConnection'
51
- export {useResource} from '../hooks/context/useDefaultResource'
58
+ export {useResource} from '../hooks/context/useResource'
52
59
  export {useSanityInstance} from '../hooks/context/useSanityInstance'
53
60
  export {useDashboardNavigate} from '../hooks/dashboard/useDashboardNavigate'
54
- export {useDispatchIntent} from '../hooks/dashboard/useDispatchIntent'
55
61
  export {useManageFavorite} from '../hooks/dashboard/useManageFavorite'
56
62
  export {
57
63
  type NavigateToStudioResult,
@@ -59,8 +65,10 @@ export {
59
65
  } from '../hooks/dashboard/useNavigateToStudioDocument'
60
66
  export {useRecordDocumentHistoryEvent} from '../hooks/dashboard/useRecordDocumentHistoryEvent'
61
67
  export {useStudioWorkspacesByProjectIdDataset} from '../hooks/dashboard/useStudioWorkspacesByProjectIdDataset'
68
+ export {useWindowTitle} from '../hooks/dashboard/useWindowTitle'
62
69
  export {useDatasets} from '../hooks/datasets/useDatasets'
63
70
  export {useApplyDocumentActions} from '../hooks/document/useApplyDocumentActions'
71
+ export {type CreateDocumentOverrides, useCreateDocument} from '../hooks/document/useCreateDocument'
64
72
  export {useDocument} from '../hooks/document/useDocument'
65
73
  export {useDocumentEvent} from '../hooks/document/useDocumentEvent'
66
74
  export {useDocumentPermissions} from '../hooks/document/useDocumentPermissions'
@@ -71,6 +79,12 @@ export {
71
79
  type DocumentsResponse,
72
80
  useDocuments,
73
81
  } from '../hooks/documents/useDocuments'
82
+ export {type FetcherHookResult} from '../hooks/helpers/createFetcherHook'
83
+ export {type MutationHookResult} from '../hooks/helpers/createMutationHook'
84
+ export {useInstallation} from '../hooks/installations/useInstallation'
85
+ export {useInstallations} from '../hooks/installations/useInstallations'
86
+ export {useOrganization} from '../hooks/organizations/useOrganization'
87
+ export {useOrganizations} from '../hooks/organizations/useOrganizations'
74
88
  export {
75
89
  type PaginatedDocumentsOptions,
76
90
  type PaginatedDocumentsResponse,
@@ -91,17 +105,12 @@ export {useProject} from '../hooks/projects/useProject'
91
105
  export {type ProjectWithoutMembers, useProjects} from '../hooks/projects/useProjects'
92
106
  export {useQuery} from '../hooks/query/useQuery'
93
107
  export {useActiveReleases} from '../hooks/releases/useActiveReleases'
108
+ export {useAllReleases} from '../hooks/releases/useAllReleases'
109
+ export {useApplyReleaseActions} from '../hooks/releases/useApplyReleaseActions'
94
110
  export {usePerspective} from '../hooks/releases/usePerspective'
95
111
  export {type UserResult, useUser} from '../hooks/users/useUser'
96
112
  export {type UsersResult, useUsers} from '../hooks/users/useUsers'
97
113
  export {REACT_SDK_VERSION} from '../version'
98
114
  export {type DatasetsResponse, type SanityProjectMember} from '@sanity/client'
99
115
  export type {Status as ComlinkStatus} from '@sanity/comlink'
100
-
101
- // Strict core versions — available for users who need explicit resource
102
- export {
103
- type DocumentHandle as StrictDocumentHandle,
104
- type DocumentTypeHandle as StrictDocumentTypeHandle,
105
- type ResourceHandle as StrictResourceHandle,
106
- } from '@sanity/sdk'
107
116
  export {type SanityDocument, type SortOrderingItem} from '@sanity/types'
@@ -1,68 +1,209 @@
1
+ import {createSanityInstance, type SanityConfig} from '@sanity/sdk'
1
2
  import {render} from '@testing-library/react'
2
3
  import React from 'react'
3
- import {describe, expect, it, vi} from 'vitest'
4
+ import {beforeEach, describe, expect, it, vi} from 'vitest'
4
5
 
6
+ import {useDashboardOrganizationId} from '../hooks/auth/useDashboardOrganizationId'
7
+ import {resolveOrgResources} from '../utils/resolveOrgResources'
5
8
  import {SDKProvider} from './SDKProvider'
6
9
 
10
+ const instance = createSanityInstance()
11
+
12
+ // Returns the same object on every call so the module-level WeakMap cache works across renders.
13
+ vi.mock('../hooks/context/useSanityInstance', () => {
14
+ return {useSanityInstance: () => instance}
15
+ })
16
+
17
+ vi.mock('../hooks/auth/useDashboardOrganizationId', () => ({
18
+ useDashboardOrganizationId: vi.fn(),
19
+ }))
20
+
21
+ vi.mock('../utils/resolveOrgResources', () => ({
22
+ resolveOrgResources: vi.fn(),
23
+ }))
24
+
7
25
  vi.mock('../context/ResourceProvider', () => ({
8
- ResourceProvider: ({children, resource}: {children: React.ReactNode; resource?: unknown}) => {
9
- return (
10
- <div data-testid="resource-provider" data-resource={JSON.stringify(resource ?? null)}>
11
- {children}
12
- </div>
13
- )
14
- },
26
+ ResourceProvider: ({
27
+ children,
28
+ // exclude fallback: it's a real ResourceProvider prop but not part of config
29
+ fallback: _fallback,
30
+ resource,
31
+ ...config
32
+ }: {
33
+ children: React.ReactNode
34
+ fallback?: React.ReactNode
35
+ resource?: {projectId?: string; dataset?: string}
36
+ }) => (
37
+ <div
38
+ data-testid="resource-provider"
39
+ data-config={JSON.stringify(config)}
40
+ data-resource={JSON.stringify(resource ?? null)}
41
+ >
42
+ {children}
43
+ </div>
44
+ ),
15
45
  }))
16
46
 
17
47
  vi.mock('./auth/AuthBoundary', () => ({
18
- AuthBoundary: ({children}: {children: React.ReactNode}) => {
19
- return <div data-testid="auth-boundary">{children}</div>
20
- },
48
+ AuthBoundary: ({children, projectIds}: {children: React.ReactNode; projectIds?: string[]}) => (
49
+ <div data-testid="auth-boundary" data-project-ids={JSON.stringify(projectIds ?? [])}>
50
+ {children}
51
+ </div>
52
+ ),
21
53
  }))
22
54
 
55
+ const mockResolveOrgResources = vi.mocked(resolveOrgResources)
56
+ const mockUseDashboardOrganizationId = vi.mocked(useDashboardOrganizationId)
57
+
23
58
  describe('SDKProvider', () => {
24
- it('renders a single ResourceProvider with AuthBoundary', () => {
25
- const config = {}
26
- const resources = {
27
- default: {projectId: 'test-project', dataset: 'production'},
59
+ beforeEach(() => {
60
+ vi.clearAllMocks()
61
+ mockResolveOrgResources.mockResolvedValue({})
62
+ mockUseDashboardOrganizationId.mockReturnValue(undefined)
63
+ })
64
+
65
+ it('renders single ResourceProvider with AuthBoundary for a single config', () => {
66
+ const config = {
67
+ projectId: 'test-project',
68
+ dataset: 'production',
28
69
  }
29
70
 
30
- const {getByTestId} = render(
31
- <SDKProvider config={config} resources={resources} fallback={<div>Loading...</div>}>
71
+ const {getAllByTestId, getByTestId} = render(
72
+ <SDKProvider config={[config]} fallback={<div>Loading...</div>}>
32
73
  <div>Child Content</div>
33
74
  </SDKProvider>,
34
75
  )
35
76
 
36
- const provider = getByTestId('resource-provider')
37
- expect(provider).toBeInTheDocument()
77
+ // Should create a single ResourceProvider
78
+ const providers = getAllByTestId('resource-provider')
79
+ expect(providers.length).toBe(1)
80
+
81
+ // Should create an AuthBoundary inside
38
82
  expect(getByTestId('auth-boundary')).toBeInTheDocument()
39
83
 
40
- // resource prop should be the default resource
41
- expect(JSON.parse(provider.getAttribute('data-resource') || 'null')).toEqual({
84
+ // Verify provider has the correct config
85
+ expect(JSON.parse(providers[0].getAttribute('data-config') || '{}')).toEqual({
42
86
  projectId: 'test-project',
43
87
  dataset: 'production',
44
88
  })
45
89
  })
46
90
 
47
- it('renders with multiple named resources', () => {
48
- const config = {}
49
- const resources = {
50
- default: {projectId: 'project-1', dataset: 'production'},
51
- secondary: {projectId: 'project-2', dataset: 'staging'},
52
- }
91
+ it('renders a single ResourceProvider using the first config when multiple configs are provided', () => {
92
+ const configs = [
93
+ {
94
+ projectId: 'project-1',
95
+ dataset: 'production',
96
+ },
97
+ {
98
+ projectId: 'project-2',
99
+ dataset: 'staging',
100
+ },
101
+ ]
53
102
 
54
- const {getByTestId} = render(
55
- <SDKProvider config={config} resources={resources} fallback={<div>Loading...</div>}>
103
+ const {getAllByTestId, getByTestId} = render(
104
+ <SDKProvider config={configs} fallback={<div>Loading...</div>}>
56
105
  <div>Child Content</div>
57
106
  </SDKProvider>,
58
107
  )
59
108
 
60
- const provider = getByTestId('resource-provider')
61
- expect(provider).toBeInTheDocument()
109
+ // Should create a single ResourceProvider using the first config
110
+ const providers = getAllByTestId('resource-provider')
111
+ expect(providers.length).toBe(1)
62
112
 
63
- expect(JSON.parse(provider.getAttribute('data-resource') || 'null')).toEqual({
113
+ // Should create an AuthBoundary inside
114
+ expect(getByTestId('auth-boundary')).toBeInTheDocument()
115
+
116
+ // Verify the provider uses the first config
117
+ expect(JSON.parse(providers[0].getAttribute('data-config') || '{}')).toEqual({
64
118
  projectId: 'project-1',
65
119
  dataset: 'production',
66
120
  })
67
121
  })
122
+
123
+ it('preserves non-resource config fields (e.g. studio auth, perspective) on the root provider', () => {
124
+ const tokenSource = {subscribe: () => ({unsubscribe: () => {}})}
125
+ const config: SanityConfig = {
126
+ projectId: 'test-project',
127
+ dataset: 'production',
128
+ perspective: 'published',
129
+ studio: {authenticated: true, auth: {token: tokenSource}},
130
+ }
131
+
132
+ const {getAllByTestId} = render(
133
+ <SDKProvider config={config} fallback={<div>Loading...</div>}>
134
+ <div>Child Content</div>
135
+ </SDKProvider>,
136
+ )
137
+
138
+ const providers = getAllByTestId('resource-provider')
139
+ // The full first config is spread onto the root ResourceProvider so the
140
+ // root SanityInstance retains auth/perspective/etc. (The token source is a
141
+ // function and doesn't survive JSON serialization, so assert on the
142
+ // serializable fields and that the studio config object is carried through.)
143
+ const spreadConfig = JSON.parse(providers[0].getAttribute('data-config') || '{}')
144
+ expect(spreadConfig).toMatchObject({
145
+ projectId: 'test-project',
146
+ dataset: 'production',
147
+ perspective: 'published',
148
+ studio: {authenticated: true},
149
+ })
150
+ })
151
+
152
+ it('synthesizes the default resource from the first config when none is explicit', () => {
153
+ const config = {projectId: 'test-project', dataset: 'production'}
154
+
155
+ const {getAllByTestId} = render(
156
+ <SDKProvider config={config} fallback={<div>Loading...</div>}>
157
+ <div>Child Content</div>
158
+ </SDKProvider>,
159
+ )
160
+
161
+ const providers = getAllByTestId('resource-provider')
162
+ expect(JSON.parse(providers[0].getAttribute('data-resource') || 'null')).toEqual({
163
+ projectId: 'test-project',
164
+ dataset: 'production',
165
+ })
166
+ })
167
+
168
+ it('uses an explicitly provided "default" resource over the config-derived one', () => {
169
+ const config = {projectId: 'config-project', dataset: 'production'}
170
+ const resources = {default: {projectId: 'explicit-project', dataset: 'staging'}}
171
+
172
+ const {getAllByTestId} = render(
173
+ <SDKProvider config={config} resources={resources} fallback={<div>Loading...</div>}>
174
+ <div>Child Content</div>
175
+ </SDKProvider>,
176
+ )
177
+
178
+ const providers = getAllByTestId('resource-provider')
179
+ // Config fields still flow through for instance creation…
180
+ expect(JSON.parse(providers[0].getAttribute('data-config') || '{}')).toEqual(config)
181
+ // …but the explicit default resource wins as the resource context value.
182
+ expect(JSON.parse(providers[0].getAttribute('data-resource') || 'null')).toEqual(
183
+ resources.default,
184
+ )
185
+ })
186
+
187
+ it('collects deduped projectIds from both config and explicit resources', () => {
188
+ const configs = [
189
+ {projectId: 'project-1', dataset: 'production'},
190
+ {projectId: 'project-2', dataset: 'production'},
191
+ ]
192
+ const resources = {
193
+ // duplicates project-1 (should be deduped)
194
+ default: {projectId: 'project-1', dataset: 'production'},
195
+ other: {projectId: 'project-3', dataset: 'production'},
196
+ }
197
+
198
+ const {getByTestId} = render(
199
+ <SDKProvider config={configs} resources={resources} fallback={<div>Loading...</div>}>
200
+ <div>Child Content</div>
201
+ </SDKProvider>,
202
+ )
203
+
204
+ const projectIds = JSON.parse(
205
+ getByTestId('auth-boundary').getAttribute('data-project-ids') || '[]',
206
+ )
207
+ expect([...projectIds].sort()).toEqual(['project-1', 'project-2', 'project-3'])
208
+ })
68
209
  })
@@ -1,65 +1,124 @@
1
1
  import {
2
- DEFAULT_RESOURCE_NAME,
3
2
  type DocumentResource,
4
3
  isDatasetResource,
4
+ isImportError,
5
5
  type SanityConfig,
6
6
  } from '@sanity/sdk'
7
- import {type ReactElement, type ReactNode, useMemo} from 'react'
7
+ import {type ReactElement, type ReactNode, useEffect, useMemo} from 'react'
8
+ import {ErrorBoundary, type FallbackProps} from 'react-error-boundary'
8
9
 
10
+ import {DEFAULT_RESOURCE_NAME} from '../constants'
11
+ import {OrganizationResourcesProvider} from '../context/OrganizationResourcesProvider'
9
12
  import {ResourceProvider} from '../context/ResourceProvider'
10
- import {ResourcesContext} from '../context/ResourcesContext'
11
13
  import {AuthBoundary, type AuthBoundaryProps} from './auth/AuthBoundary'
14
+ import {ChunkLoadError} from './errors/ChunkLoadError'
15
+ import {clearChunkReloadFlag} from './errors/chunkReloadStorage'
12
16
 
13
17
  /**
14
18
  * @internal
15
19
  */
16
20
  export interface SDKProviderProps extends AuthBoundaryProps {
17
21
  children: ReactNode
18
- config: SanityConfig
19
- /**
20
- * Named document resources map. Provided to `ResourcesContext` for
21
- * name-based resource resolution in hooks.
22
- */
23
- resources?: Record<string, DocumentResource>
22
+ config: SanityConfig | SanityConfig[]
24
23
  fallback: ReactNode
24
+ resources?: Record<string, DocumentResource>
25
+ /** When set, automatically fetches and registers the organization's media library and canvas as named resources. */
26
+ inferMediaLibraryAndCanvas?: boolean
25
27
  }
26
28
 
27
29
  /**
28
- * Collects unique project IDs from a resources map.
30
+ * Clears the chunk-reload flag once children render successfully past the
31
+ * top-level boundary, so a future incident in the same session can trigger
32
+ * another automatic reload.
29
33
  */
30
- function collectProjectIds(resources: Record<string, DocumentResource>): string[] {
31
- const ids = new Set<string>()
32
- for (const res of Object.values(resources)) {
33
- if (isDatasetResource(res)) ids.add(res.projectId)
34
- }
35
- return [...ids]
34
+ function ResetChunkReloadFlagOnMount(): null {
35
+ useEffect(() => {
36
+ clearChunkReloadFlag()
37
+ }, [])
38
+ return null
36
39
  }
37
40
 
38
41
  /**
39
42
  * @internal
40
43
  *
41
44
  * Top-level context provider that provides access to the Sanity SDK.
42
- *
43
- * Creates a single `ResourceProvider` (and therefore a single `SanityInstance`)
44
- * for the given config. Resource resolution is handled by `ResourcesContext`
45
- * and the `"default"` named resource.
46
45
  */
47
46
  export function SDKProvider({
48
47
  children,
49
48
  config,
50
- resources = {},
51
49
  fallback,
50
+ inferMediaLibraryAndCanvas,
52
51
  ...props
53
52
  }: SDKProviderProps): ReactElement {
54
- const projectIds = useMemo(() => collectProjectIds(resources), [resources])
53
+ // For legacy config support, extract the first config object as a default resource,
54
+ // matching the legacy behavior nesting ResourceProviders with the first config closest to users' application code.
55
+ // This should be removed when we remove legacy config support.
56
+ const defaultConfig = Array.isArray(config) ? config[0] : config
57
+ const defaultProjectId = defaultConfig?.projectId
58
+ const defaultDataset = defaultConfig?.dataset
55
59
 
56
- const rootResource = useMemo(() => resources[DEFAULT_RESOURCE_NAME], [resources])
60
+ const resourcesValue = useMemo(() => {
61
+ const explicit = props.resources ?? {}
62
+ if (defaultProjectId && defaultDataset && !Object.hasOwn(explicit, DEFAULT_RESOURCE_NAME)) {
63
+ return {
64
+ [DEFAULT_RESOURCE_NAME]: {
65
+ projectId: defaultProjectId,
66
+ dataset: defaultDataset,
67
+ },
68
+ ...explicit,
69
+ }
70
+ }
71
+ return explicit
72
+ }, [defaultProjectId, defaultDataset, props.resources])
73
+
74
+ // Collect all projectIds from both resources and config, deduped so
75
+ // AuthBoundary doesn't verify the same project more than once.
76
+ const projectIds = useMemo(
77
+ () =>
78
+ Array.from(
79
+ new Set(
80
+ [
81
+ ...Object.values(resourcesValue)
82
+ .filter(isDatasetResource)
83
+ .map((resource) => resource.projectId),
84
+ ...(Array.isArray(config) ? config : [config]).map((configObj) => configObj.projectId),
85
+ ].filter((id): id is string => !!id),
86
+ ),
87
+ ),
88
+ [resourcesValue, config],
89
+ )
57
90
 
58
91
  return (
59
- <ResourceProvider {...config} resource={rootResource} fallback={fallback}>
60
- <AuthBoundary {...props} projectIds={projectIds}>
61
- <ResourcesContext.Provider value={resources}>{children}</ResourcesContext.Provider>
62
- </AuthBoundary>
63
- </ResourceProvider>
92
+ <ErrorBoundary FallbackComponent={ChunkAwareFallback}>
93
+ <ResetChunkReloadFlagOnMount />
94
+ {/* Spread the first config so SanityInstance can use the auth,
95
+ perspective, and other config fields; `resource` lets an explicitly
96
+ provided `default` resource override the config-derived resource. */}
97
+ <ResourceProvider
98
+ {...defaultConfig}
99
+ resource={resourcesValue[DEFAULT_RESOURCE_NAME]}
100
+ fallback={fallback}
101
+ >
102
+ <AuthBoundary {...props} projectIds={projectIds}>
103
+ {/* OrganizationResourcesProvider wraps ResourcesContext.
104
+ It merges explicit resources with lazily inferred org resources (media
105
+ library, canvas) and provides the combined map to the subtree. */}
106
+ <OrganizationResourcesProvider
107
+ resources={resourcesValue}
108
+ inferMediaLibraryAndCanvas={inferMediaLibraryAndCanvas}
109
+ >
110
+ {children}
111
+ </OrganizationResourcesProvider>
112
+ </AuthBoundary>
113
+ </ResourceProvider>
114
+ </ErrorBoundary>
64
115
  )
65
116
  }
117
+
118
+ function ChunkAwareFallback(fallbackProps: FallbackProps): ReactElement {
119
+ if (isImportError(fallbackProps.error)) {
120
+ return <ChunkLoadError {...fallbackProps} />
121
+ }
122
+ // Re-throw so downstream boundaries (e.g. AuthBoundary) handle other errors.
123
+ throw fallbackProps.error
124
+ }