@sanity/sdk 2.7.0 → 3.0.0-rc.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 (99) hide show
  1. package/dist/index.d.ts +228 -239
  2. package/dist/index.js +287 -454
  3. package/dist/index.js.map +1 -1
  4. package/package.json +4 -4
  5. package/src/_exports/index.ts +16 -17
  6. package/src/agent/agentActions.test.ts +60 -16
  7. package/src/agent/agentActions.ts +29 -20
  8. package/src/auth/authMode.test.ts +0 -25
  9. package/src/auth/authMode.ts +3 -6
  10. package/src/auth/authStore.test.ts +129 -66
  11. package/src/auth/authStore.ts +9 -11
  12. package/src/auth/dashboardAuth.ts +2 -2
  13. package/src/auth/getOrganizationVerificationState.test.ts +10 -11
  14. package/src/auth/handleAuthCallback.test.ts +0 -12
  15. package/src/auth/handleAuthCallback.ts +9 -3
  16. package/src/auth/logout.test.ts +0 -6
  17. package/src/auth/refreshStampedToken.test.ts +121 -17
  18. package/src/auth/standaloneAuth.ts +9 -3
  19. package/src/auth/studioAuth.ts +35 -8
  20. package/src/auth/subscribeToStateAndFetchCurrentUser.test.ts +9 -3
  21. package/src/auth/subscribeToStateAndFetchCurrentUser.ts +1 -1
  22. package/src/auth/subscribeToStorageEventsAndSetToken.test.ts +0 -2
  23. package/src/auth/subscribeToStorageEventsAndSetToken.ts +2 -2
  24. package/src/auth/utils.ts +33 -0
  25. package/src/client/clientStore.test.ts +14 -61
  26. package/src/client/clientStore.ts +52 -28
  27. package/src/comlink/controller/actions/destroyController.test.ts +1 -4
  28. package/src/comlink/controller/actions/getOrCreateChannel.test.ts +1 -4
  29. package/src/comlink/controller/actions/getOrCreateController.test.ts +1 -4
  30. package/src/comlink/controller/actions/releaseChannel.test.ts +1 -1
  31. package/src/comlink/controller/comlinkControllerStore.test.ts +1 -4
  32. package/src/comlink/node/actions/getOrCreateNode.test.ts +1 -4
  33. package/src/comlink/node/actions/releaseNode.test.ts +1 -4
  34. package/src/comlink/node/comlinkNodeStore.test.ts +2 -2
  35. package/src/comlink/node/getNodeState.test.ts +1 -1
  36. package/src/config/__tests__/handles.test.ts +12 -18
  37. package/src/config/handles.ts +7 -25
  38. package/src/config/sanityConfig.ts +99 -52
  39. package/src/datasets/datasets.test.ts +2 -2
  40. package/src/datasets/datasets.ts +4 -10
  41. package/src/document/actions.test.ts +33 -4
  42. package/src/document/actions.ts +3 -10
  43. package/src/document/applyDocumentActions.test.ts +17 -18
  44. package/src/document/applyDocumentActions.ts +9 -12
  45. package/src/document/documentStore.test.ts +303 -133
  46. package/src/document/documentStore.ts +70 -61
  47. package/src/document/permissions.test.ts +44 -8
  48. package/src/document/processActions.test.ts +77 -7
  49. package/src/document/reducers.test.ts +35 -3
  50. package/src/document/sharedListener.test.ts +13 -13
  51. package/src/document/sharedListener.ts +8 -3
  52. package/src/favorites/favorites.test.ts +10 -2
  53. package/src/presence/presenceStore.test.ts +34 -9
  54. package/src/presence/presenceStore.ts +29 -13
  55. package/src/preview/previewProjectionUtils.test.ts +192 -0
  56. package/src/preview/previewProjectionUtils.ts +88 -0
  57. package/src/preview/{previewStore.ts → types.ts} +6 -25
  58. package/src/project/project.test.ts +1 -1
  59. package/src/project/project.ts +14 -20
  60. package/src/projection/getProjectionState.test.ts +4 -2
  61. package/src/projection/getProjectionState.ts +2 -21
  62. package/src/projection/projectionQuery.ts +2 -3
  63. package/src/projection/projectionStore.test.ts +3 -3
  64. package/src/projection/resolveProjection.test.ts +2 -1
  65. package/src/projection/resolveProjection.ts +2 -18
  66. package/src/projection/subscribeToStateAndFetchBatches.test.ts +2 -2
  67. package/src/projection/subscribeToStateAndFetchBatches.ts +23 -36
  68. package/src/projection/types.ts +1 -9
  69. package/src/projects/projects.test.ts +1 -1
  70. package/src/query/queryStore.test.ts +86 -28
  71. package/src/query/queryStore.ts +23 -38
  72. package/src/releases/getPerspectiveState.test.ts +14 -13
  73. package/src/releases/getPerspectiveState.ts +6 -6
  74. package/src/releases/releasesStore.test.ts +21 -6
  75. package/src/releases/releasesStore.ts +18 -8
  76. package/src/store/createActionBinder.test.ts +114 -111
  77. package/src/store/createActionBinder.ts +52 -101
  78. package/src/store/createSanityInstance.test.ts +13 -83
  79. package/src/store/createSanityInstance.ts +2 -78
  80. package/src/store/createStateSourceAction.test.ts +2 -2
  81. package/src/store/createStateSourceAction.ts +5 -5
  82. package/src/store/createStoreInstance.test.ts +2 -4
  83. package/src/users/reducers.test.ts +1 -6
  84. package/src/users/reducers.ts +2 -2
  85. package/src/users/types.ts +4 -4
  86. package/src/users/usersStore.test.ts +12 -15
  87. package/src/utils/createFetcherStore.test.ts +1 -1
  88. package/src/utils/logger.test.ts +0 -12
  89. package/src/utils/logger.ts +3 -8
  90. package/src/preview/getPreviewState.test.ts +0 -120
  91. package/src/preview/getPreviewState.ts +0 -91
  92. package/src/preview/previewQuery.test.ts +0 -236
  93. package/src/preview/previewQuery.ts +0 -153
  94. package/src/preview/previewStore.test.ts +0 -36
  95. package/src/preview/resolvePreview.test.ts +0 -47
  96. package/src/preview/resolvePreview.ts +0 -20
  97. package/src/preview/subscribeToStateAndFetchBatches.test.ts +0 -221
  98. package/src/preview/subscribeToStateAndFetchBatches.ts +0 -112
  99. package/src/preview/util.ts +0 -13
@@ -1,12 +1,17 @@
1
- import {type ClientConfig, createClient, type SanityClient} from '@sanity/client'
1
+ import {
2
+ type ClientConfig,
3
+ type ClientPerspective,
4
+ createClient,
5
+ type SanityClient,
6
+ } from '@sanity/client'
2
7
  import {pick} from 'lodash-es'
3
8
 
4
9
  import {getAuthMethodState, getTokenState} from '../auth/authStore'
5
10
  import {
6
- type DocumentSource,
7
- isCanvasSource,
8
- isDatasetSource,
9
- isMediaLibrarySource,
11
+ type DocumentResource,
12
+ isCanvasResource,
13
+ isDatasetResource,
14
+ isMediaLibraryResource,
10
15
  } from '../config/sanityConfig'
11
16
  import {bindActionGlobally} from '../store/createActionBinder'
12
17
  import {createStateSourceAction} from '../store/createStateSourceAction'
@@ -45,16 +50,16 @@ const allowedKeys = Object.keys({
45
50
  'requestTagPrefix': null,
46
51
  'useProjectHostname': null,
47
52
  '~experimental_resource': null,
48
- 'source': null,
53
+ 'resource': null,
49
54
  } satisfies Record<keyof ClientOptions, null>) as (keyof ClientOptions)[]
50
55
 
51
- const DEFAULT_CLIENT_CONFIG: ClientConfig = {
56
+ const DEFAULT_CLIENT_CONFIG = {
52
57
  apiVersion: DEFAULT_API_VERSION,
53
58
  useCdn: false,
54
59
  ignoreBrowserTokenWarning: true,
55
60
  allowReconfigure: false,
56
61
  requestTagPrefix: DEFAULT_REQUEST_TAG_PREFIX,
57
- }
62
+ } satisfies ClientConfig
58
63
 
59
64
  /**
60
65
  * States tracked by the client store
@@ -87,7 +92,15 @@ interface ClientResource {
87
92
  *
88
93
  * @public
89
94
  */
90
- export interface ClientOptions extends Pick<ClientConfig, AllowedClientConfigKey> {
95
+ export interface ClientOptions extends Omit<
96
+ Pick<ClientConfig, AllowedClientConfigKey>,
97
+ 'resource'
98
+ > {
99
+ /**
100
+ * Narrows the inherited `perspective` to exclude stackable perspectives,
101
+ * which are not supported by the SDK.
102
+ */
103
+ 'perspective'?: Exclude<ClientPerspective, readonly unknown[]>
91
104
  /**
92
105
  * An optional flag to choose between the default client (typically project-level)
93
106
  * and the global client ('global'). When set to `'global'`, the global client
@@ -106,7 +119,7 @@ export interface ClientOptions extends Pick<ClientConfig, AllowedClientConfigKey
106
119
  /**
107
120
  * @internal
108
121
  */
109
- 'source'?: DocumentSource
122
+ 'resource'?: DocumentResource
110
123
  }
111
124
 
112
125
  const clientStore = defineStore<ClientStoreState>({
@@ -182,22 +195,27 @@ export const getClient = bindActionGlobally(
182
195
  const {clients, authMethod} = state.get()
183
196
 
184
197
  let resource: ClientResource | undefined
198
+ let projectId: string | undefined = options.projectId
199
+ let dataset: string | undefined = options.dataset
185
200
 
186
- if (options.source) {
187
- if (isDatasetSource(options.source)) {
188
- resource = {type: 'dataset', id: `${options.source.projectId}.${options.source.dataset}`}
189
- } else if (isMediaLibrarySource(options.source)) {
190
- resource = {type: 'media-library', id: options.source.mediaLibraryId}
191
- } else if (isCanvasSource(options.source)) {
192
- resource = {type: 'canvas', id: options.source.canvasId}
201
+ if (options.resource) {
202
+ if (isMediaLibraryResource(options.resource)) {
203
+ resource = {type: 'media-library', id: options.resource.mediaLibraryId}
204
+ } else if (isCanvasResource(options.resource)) {
205
+ resource = {type: 'canvas', id: options.resource.canvasId}
206
+ } else if (isDatasetResource(options.resource)) {
207
+ projectId = options.resource.projectId
208
+ dataset = options.resource.dataset
193
209
  }
210
+ // temporarily excluding dataset resource as a resource for now. Many of the global API endpoints require vX api version.
211
+ // When ready, remove the above check and uncomment the below check.
212
+ // } else if (isDatasetResource(options.resource)) {
213
+ // resource = {type: 'dataset', id: `${options.resource.projectId}.${options.resource.dataset}`}
194
214
  }
195
215
 
196
- const projectId = options.projectId ?? instance.config.projectId
197
- const dataset = options.dataset ?? instance.config.dataset
198
216
  const apiHost = options.apiHost ?? instance.config.auth?.apiHost
199
217
 
200
- const effectiveOptions: ClientOptions = {
218
+ const effectiveOptions = {
201
219
  ...DEFAULT_CLIENT_CONFIG,
202
220
  ...((options.scope === 'global' || !projectId || resource) && {useProjectHostname: false}),
203
221
  token: authMethod === 'cookie' ? undefined : (tokenFromState ?? undefined),
@@ -206,20 +224,23 @@ export const getClient = bindActionGlobally(
206
224
  ...(dataset && {dataset}),
207
225
  ...(apiHost && {apiHost}),
208
226
  ...(resource && {'~experimental_resource': resource}),
209
- }
227
+ } as ClientOptions
210
228
 
211
- // When a source is provided, don't use projectId/dataset - the client should be "projectless"
212
- // The client code itself will ignore the non-source config, so we do this to prevent confusing the user.
229
+ // When a MediaLibrary or Canvas resource is provided, don't use projectId/dataset - the client should be "projectless"
230
+ // The client code itself will ignore the non-resource config, so we do this to prevent confusing the user.
213
231
  // (ref: https://github.com/sanity-io/client/blob/5c23f81f5ab93a53f5b22b39845c867988508d84/src/data/dataMethods.ts#L691)
214
- if (resource) {
232
+ // Note: DatasetResource is handled differently - it extracts projectId/dataset from the resource and uses those
233
+ if (options.resource) {
215
234
  if (options.projectId || options.dataset) {
216
235
  // eslint-disable-next-line no-console
217
236
  console.warn(
218
- 'Both source and explicit projectId/dataset are provided. The source will be used and projectId/dataset will be ignored.',
237
+ 'Both resource and explicit projectId/dataset are provided. The resource will be used and projectId/dataset will be ignored.',
219
238
  )
220
239
  }
221
- delete effectiveOptions.projectId
222
- delete effectiveOptions.dataset
240
+ if (resource) {
241
+ delete effectiveOptions.projectId
242
+ delete effectiveOptions.dataset
243
+ }
223
244
  }
224
245
 
225
246
  if (effectiveOptions.token === null || typeof effectiveOptions.token === 'undefined') {
@@ -231,11 +252,14 @@ export const getClient = bindActionGlobally(
231
252
  delete effectiveOptions.withCredentials
232
253
  }
233
254
 
255
+ // Don't pass our DocumentResource to createClient - we've already derived projectId/dataset or ~experimental_resource
256
+ const {resource: _omitResource, ...clientConfig} = effectiveOptions
257
+
234
258
  const key = getClientConfigKey(effectiveOptions)
235
259
 
236
260
  if (clients[key]) return clients[key]
237
261
 
238
- const client = createClient(effectiveOptions)
262
+ const client = createClient(clientConfig as ClientConfig)
239
263
  state.set('addClient', (prev) => ({clients: {...prev.clients, [key]: client}}))
240
264
 
241
265
  return client
@@ -7,10 +7,7 @@ import {type ComlinkControllerState} from '../comlinkControllerStore'
7
7
  import {destroyController} from './destroyController'
8
8
 
9
9
  describe('destroyController', () => {
10
- const instance = createSanityInstance({
11
- projectId: 'test-project-id',
12
- dataset: 'test-dataset',
13
- })
10
+ const instance = createSanityInstance()
14
11
  let state: ReturnType<typeof createStoreState<ComlinkControllerState>>
15
12
  let mockController: {destroy: ReturnType<typeof vi.fn>}
16
13
 
@@ -13,10 +13,7 @@ const channelConfig = {
13
13
  }
14
14
 
15
15
  describe('getOrCreateChannel', () => {
16
- const instance = createSanityInstance({
17
- projectId: 'test-project-id',
18
- dataset: 'test-dataset',
19
- })
16
+ const instance = createSanityInstance()
20
17
  let state: ReturnType<typeof createStoreState<ComlinkControllerState>>
21
18
  let mockController: Partial<Controller>
22
19
  let mockChannel: Partial<ChannelInstance<FrameMessage, WindowMessage>>
@@ -21,10 +21,7 @@ describe('getOrCreateController', () => {
21
21
  let state: ReturnType<typeof createStoreState<ComlinkControllerState>>
22
22
 
23
23
  beforeEach(() => {
24
- instance = createSanityInstance({
25
- projectId: 'test-project-id',
26
- dataset: 'test-dataset',
27
- })
24
+ instance = createSanityInstance()
28
25
 
29
26
  state = createStoreState<ComlinkControllerState>({
30
27
  controller: null,
@@ -29,7 +29,7 @@ describe('releaseChannel', () => {
29
29
  let releaseChannel: (inst: SanityInstance, channelName: string) => void
30
30
 
31
31
  beforeEach(() => {
32
- instance = createSanityInstance({projectId: 'test-project-id', dataset: 'test-dataset'})
32
+ instance = createSanityInstance()
33
33
  store = createStoreInstance(instance, key, comlinkControllerStore)
34
34
 
35
35
  const bind =
@@ -16,10 +16,7 @@ describe('comlinkControllerStore', () => {
16
16
  let instance: SanityInstance
17
17
  beforeEach(() => {
18
18
  vi.resetModules()
19
- instance = createSanityInstance({
20
- projectId: 'test-project-id',
21
- dataset: 'test-dataset',
22
- })
19
+ instance = createSanityInstance()
23
20
  })
24
21
 
25
22
  afterEach(() => {
@@ -22,10 +22,7 @@ const nodeConfig = {
22
22
  }
23
23
 
24
24
  describe('getOrCreateNode', () => {
25
- const instance = createSanityInstance({
26
- projectId: 'test-project-id',
27
- dataset: 'test-dataset',
28
- })
25
+ const instance = createSanityInstance()
29
26
  let state: ReturnType<typeof createStoreState<ComlinkNodeState>>
30
27
  let mockNode: Partial<Node<WindowMessage, FrameMessage>> & {
31
28
  start: ReturnType<typeof vi.fn>
@@ -22,10 +22,7 @@ describe('releaseNode', () => {
22
22
  }
23
23
 
24
24
  beforeEach(() => {
25
- instance = createSanityInstance({
26
- projectId: 'test-project-id',
27
- dataset: 'test-dataset',
28
- })
25
+ instance = createSanityInstance()
29
26
  mockNode = {start: vi.fn(), stop: vi.fn(), onStatus: vi.fn()}
30
27
  state = createStoreState<ComlinkNodeState>({nodes: new Map(), subscriptions: new Map()})
31
28
  vi.clearAllMocks()
@@ -1,5 +1,5 @@
1
1
  import {type Node} from '@sanity/comlink'
2
- import {beforeEach, describe, expect, it} from 'vitest'
2
+ import {beforeEach, describe, expect, it, vi} from 'vitest'
3
3
 
4
4
  import {createSanityInstance, type SanityInstance} from '../../store/createSanityInstance'
5
5
  import {createStoreState} from '../../store/createStoreState'
@@ -10,7 +10,7 @@ describe('nodeStore', () => {
10
10
  let instance: SanityInstance
11
11
 
12
12
  beforeEach(() => {
13
- instance = createSanityInstance({projectId: 'test-project-id', dataset: 'test-dataset'})
13
+ instance = createSanityInstance()
14
14
  })
15
15
 
16
16
  it('should have correct initial state', () => {
@@ -23,7 +23,7 @@ describe('getNodeState', () => {
23
23
  let instance: ReturnType<typeof createSanityInstance>
24
24
 
25
25
  beforeEach(() => {
26
- instance = createSanityInstance({projectId: 'test', dataset: 'test'})
26
+ instance = createSanityInstance()
27
27
  })
28
28
 
29
29
  afterEach(() => {
@@ -1,30 +1,24 @@
1
1
  import {describe, expect, it} from 'vitest'
2
2
 
3
- import {
4
- createDatasetHandle,
5
- createDocumentHandle,
6
- createDocumentTypeHandle,
7
- createProjectHandle,
8
- } from '../handles'
3
+ import {createDocumentHandle, createDocumentTypeHandle, createResourceHandle} from '../handles'
9
4
 
10
5
  describe('handle creation functions', () => {
11
- it('createProjectHandle returns input', () => {
12
- const input = {projectId: 'test'}
13
- expect(createProjectHandle(input)).toBe(input)
14
- })
15
-
16
- it('createDatasetHandle returns input', () => {
17
- const input = {projectId: 'test', dataset: 'prod'}
18
- expect(createDatasetHandle(input)).toBe(input)
19
- })
20
-
21
6
  it('createDocumentTypeHandle returns input', () => {
22
- const input = {documentType: 'movie'}
7
+ const input = {documentType: 'movie', resource: {projectId: 'p', dataset: 'd'}}
23
8
  expect(createDocumentTypeHandle(input)).toBe(input)
24
9
  })
25
10
 
26
11
  it('createDocumentHandle returns input', () => {
27
- const input = {documentType: 'movie', documentId: '123'}
12
+ const input = {
13
+ documentType: 'movie',
14
+ documentId: '123',
15
+ resource: {projectId: 'p', dataset: 'd'},
16
+ }
28
17
  expect(createDocumentHandle(input)).toBe(input)
29
18
  })
19
+
20
+ it('createResourceHandle returns input', () => {
21
+ const input = {resource: {projectId: 'p', dataset: 'd'}}
22
+ expect(createResourceHandle(input)).toBe(input)
23
+ })
30
24
  })
@@ -1,9 +1,4 @@
1
- import {
2
- type DatasetHandle,
3
- type DocumentHandle,
4
- type DocumentTypeHandle,
5
- type ProjectHandle,
6
- } from './sanityConfig'
1
+ import {type DocumentHandle, type DocumentTypeHandle, type ResourceHandle} from './sanityConfig'
7
2
 
8
3
  /**
9
4
  * Creates or validates a `DocumentHandle` object.
@@ -40,28 +35,15 @@ export function createDocumentTypeHandle<
40
35
  }
41
36
 
42
37
  /**
43
- * Creates or validates a `ProjectHandle` object.
44
- * Ensures the provided object conforms to the `ProjectHandle` interface.
45
- * @param handle - The object containing project identification properties.
46
- * @returns The validated `ProjectHandle` object.
38
+ * Creates or validates a `ResourceHandle` object.
39
+ * Ensures the provided object conforms to the `ResourceHandle` interface.
40
+ * @param handle - The object containing resource identification properties.
41
+ * @returns The validated `ResourceHandle` object.
47
42
  * @public
48
43
  */
49
- export function createProjectHandle<TProjectId extends string = string>(
50
- handle: ProjectHandle<TProjectId>,
51
- ): ProjectHandle<TProjectId> {
52
- return handle
53
- }
54
-
55
- /**
56
- * Creates or validates a `DatasetHandle` object.
57
- * Ensures the provided object conforms to the `DatasetHandle` interface.
58
- * @param handle - The object containing dataset identification properties.
59
- * @returns The validated `DatasetHandle` object.
60
- * @public
61
- */
62
- export function createDatasetHandle<
44
+ export function createResourceHandle<
63
45
  TDataset extends string = string,
64
46
  TProjectId extends string = string,
65
- >(handle: DatasetHandle<TDataset, TProjectId>): DatasetHandle<TDataset, TProjectId> {
47
+ >(handle: ResourceHandle<TProjectId, TDataset>): ResourceHandle<TProjectId, TDataset> {
66
48
  return handle
67
49
  }
@@ -23,6 +23,12 @@ export interface TokenSource {
23
23
  * @public
24
24
  */
25
25
  export interface StudioConfig {
26
+ /**
27
+ * Whether the Studio has already determined the user is authenticated.
28
+ * When `true` and the token source emits `null`, the SDK infers
29
+ * cookie-based auth is in use rather than transitioning to logged-out.
30
+ */
31
+ authenticated?: boolean
26
32
  /** Reactive auth token source from the Studio's auth store. */
27
33
  auth?: {
28
34
  /**
@@ -34,14 +40,12 @@ export interface StudioConfig {
34
40
  */
35
41
  token?: TokenSource
36
42
  }
37
- }
38
-
39
- /**
40
- * Represents the minimal configuration required to identify a Sanity project.
41
- * @public
42
- */
43
- export interface ProjectHandle<TProjectId extends string = string> {
44
- projectId?: TProjectId
43
+ /**
44
+ * The project ID for this Studio workspace.
45
+ * Used to derive the localStorage key for studio auth token discovery
46
+ * (`__studio_auth_token_<projectId>`) and for project-specific API hostname requests.
47
+ */
48
+ projectId?: string
45
49
  }
46
50
 
47
51
  /**
@@ -56,20 +60,27 @@ export type ReleasePerspective = {
56
60
  * @public
57
61
  */
58
62
  export interface PerspectiveHandle {
59
- perspective?: ClientPerspective | ReleasePerspective
63
+ /**
64
+ * The perspective to use for this operation.
65
+ * Note that the SDK stacks perspectives for you when querying.
66
+ * The SDK automatically fetches all of your content releases, and orders them the way the Sanity Studio does: usually by scheduled date, with ASAP releases coming first.
67
+ * @public
68
+ */
69
+ perspective?: Exclude<ClientPerspective, readonly unknown[]> | ReleasePerspective
60
70
  }
61
71
 
62
72
  /**
63
73
  * @public
64
74
  */
65
- export interface DatasetHandle<TDataset extends string = string, TProjectId extends string = string>
66
- extends ProjectHandle<TProjectId>, PerspectiveHandle {
67
- dataset?: TDataset
75
+ export interface ResourceHandle<
76
+ TProjectId extends string = string,
77
+ TDataset extends string = string,
78
+ > extends PerspectiveHandle {
68
79
  /**
69
- * @beta
70
- * Explicit source object to use for this operation.
80
+ * Explicit resource object to use for this operation.
81
+ * @public
71
82
  */
72
- source?: DocumentSource
83
+ resource: DocumentResource<TProjectId, TDataset>
73
84
  }
74
85
 
75
86
  /**
@@ -82,7 +93,7 @@ export interface DocumentTypeHandle<
82
93
  TDocumentType extends string = string,
83
94
  TDataset extends string = string,
84
95
  TProjectId extends string = string,
85
- > extends DatasetHandle<TDataset, TProjectId> {
96
+ > extends ResourceHandle<TDataset, TProjectId> {
86
97
  documentId?: string
87
98
  documentType: TDocumentType
88
99
  /**
@@ -108,13 +119,44 @@ export interface DocumentHandle<
108
119
  }
109
120
 
110
121
  /**
111
- * Represents the complete configuration for a Sanity SDK instance
122
+ * The key used to identify the default resource in a resources map.
123
+ * When no `resource` or `resourceName` is specified, the SDK resolves
124
+ * the resource registered under this name.
125
+ *
112
126
  * @public
113
127
  */
114
- export interface SanityConfig extends DatasetHandle, PerspectiveHandle {
128
+ export const DEFAULT_RESOURCE_NAME = 'default'
129
+
130
+ /**
131
+ * Represents the complete configuration for a Sanity SDK instance.
132
+ *
133
+ * Most apps configure resources via the `resources` prop on `SanityApp`:
134
+ *
135
+ * @example Typical React usage
136
+ * ```tsx
137
+ * <SanityApp
138
+ * resources={{ default: { projectId: 'abc123', dataset: 'production' } }}
139
+ * fallback={<Loading />}
140
+ * >
141
+ * <App />
142
+ * </SanityApp>
143
+ * ```
144
+ *
145
+ * The `defaultResource` field is set automatically by the React layer from
146
+ * `resources['default']`. It can also be set directly when using the core
147
+ * SDK without React (e.g. in a Node.js script):
148
+ *
149
+ * @example Direct core usage (without React)
150
+ * ```ts
151
+ * const instance = createSanityInstance({
152
+ * defaultResource: { projectId: 'abc123', dataset: 'production' },
153
+ * })
154
+ * ```
155
+ * @public
156
+ */
157
+ export interface SanityConfig extends PerspectiveHandle {
115
158
  /**
116
159
  * Authentication configuration for the instance
117
- * @remarks Merged with parent configurations when using createChild
118
160
  */
119
161
  auth?: AuthConfig
120
162
  /**
@@ -126,64 +168,69 @@ export interface SanityConfig extends DatasetHandle, PerspectiveHandle {
126
168
  * `SDKStudioContext` provider. Can also be set explicitly for programmatic use.
127
169
  */
128
170
  studio?: StudioConfig
129
-
130
- /**
131
- * Studio mode configuration for use of the SDK in a Sanity Studio.
132
- * @remarks Controls whether studio mode features are enabled.
133
- * @deprecated Use `studio` instead, which provides richer integration
134
- * with the Studio's workspace (auth token sync, etc.).
135
- */
136
- studioMode?: {
137
- enabled: boolean
138
- }
139
-
140
171
  /**
141
- * @beta
142
- * A list of named sources to use for this instance.
172
+ * The default document resource for this instance. Used by bound actions
173
+ * when no explicit resource is provided.
174
+ *
175
+ * @public
143
176
  */
144
- sources?: Record<string, DocumentSource>
177
+ defaultResource?: DocumentResource
145
178
  }
146
179
 
147
180
  /**
148
- * A document source can be used for querying.
149
- * This will soon be the default way to identify where you are querying from.
181
+ * A document resource identifies where data is stored and queried from.
182
+ * Can be a dataset (project + dataset pair), a media library, or a canvas.
150
183
  *
151
- * @beta
184
+ * @public
152
185
  */
153
- export type DocumentSource = DatasetSource | MediaLibrarySource | CanvasSource
186
+ export type DocumentResource<
187
+ TProjectId extends string = string,
188
+ TDataset extends string = string,
189
+ > = DatasetResource<TProjectId, TDataset> | MediaLibraryResource | CanvasResource
154
190
 
155
191
  /**
156
- * @beta
192
+ * A resource that targets a specific project and dataset.
193
+ * @public
157
194
  */
158
- export type DatasetSource = {projectId: string; dataset: string}
195
+ export type DatasetResource<
196
+ TProjectId extends string = string,
197
+ TDataset extends string = string,
198
+ > = {projectId: TProjectId; dataset: TDataset}
159
199
 
160
200
  /**
161
- * @beta
201
+ * A resource that targets a media library.
202
+ * @public
162
203
  */
163
- export type MediaLibrarySource = {mediaLibraryId: string}
204
+ export type MediaLibraryResource = {mediaLibraryId: string}
164
205
 
165
206
  /**
166
- * @beta
207
+ * A resource that targets a canvas.
208
+ * @public
167
209
  */
168
- export type CanvasSource = {canvasId: string}
210
+ export type CanvasResource = {canvasId: string}
169
211
 
170
212
  /**
171
- * @beta
213
+ * Type guard that checks whether a {@link DocumentResource} is a {@link DatasetResource}.
214
+ * @public
172
215
  */
173
- export function isDatasetSource(source: DocumentSource): source is DatasetSource {
174
- return 'projectId' in source && 'dataset' in source
216
+ export function isDatasetResource(resource: DocumentResource): resource is DatasetResource {
217
+ return 'projectId' in resource && 'dataset' in resource
175
218
  }
176
219
 
177
220
  /**
178
- * @beta
221
+ * Type guard that checks whether a {@link DocumentResource} is a {@link MediaLibraryResource}.
222
+ * @public
179
223
  */
180
- export function isMediaLibrarySource(source: DocumentSource): source is MediaLibrarySource {
181
- return 'mediaLibraryId' in source
224
+ export function isMediaLibraryResource(
225
+ resource: DocumentResource,
226
+ ): resource is MediaLibraryResource {
227
+ return 'mediaLibraryId' in resource
182
228
  }
183
229
 
184
230
  /**
185
- * @beta
231
+ * Type guard that checks whether a {@link DocumentResource} is a {@link CanvasResource}.
232
+ * @public
186
233
  */
187
- export function isCanvasSource(source: DocumentSource): source is CanvasSource {
188
- return 'canvasId' in source
234
+ export function isCanvasResource(resource: DocumentResource): resource is CanvasResource {
235
+ return 'canvasId' in resource
189
236
  }
@@ -11,7 +11,7 @@ vi.mock('../client/clientStore')
11
11
 
12
12
  describe('datasets', () => {
13
13
  it('calls the `client.observable.datasets.list` method on the client and returns the result', async () => {
14
- const instance = createSanityInstance({projectId: 'p', dataset: 'd'})
14
+ const instance = createSanityInstance()
15
15
  const datasets = [{id: 'a'}, {id: 'b'}]
16
16
  const list = vi.fn().mockReturnValue(of(datasets))
17
17
 
@@ -25,7 +25,7 @@ describe('datasets', () => {
25
25
  observable: of(mockClient),
26
26
  } as StateSource<SanityClient>)
27
27
 
28
- const result = await resolveDatasets(instance)
28
+ const result = await resolveDatasets(instance, {projectId: 'p'})
29
29
  expect(result).toEqual(datasets)
30
30
  expect(list).toHaveBeenCalled()
31
31
  })
@@ -1,7 +1,6 @@
1
1
  import {switchMap} from 'rxjs'
2
2
 
3
3
  import {getClientState} from '../client/clientStore'
4
- import {type ProjectHandle} from '../config/sanityConfig'
5
4
  import {createFetcherStore} from '../utils/createFetcherStore'
6
5
 
7
6
  const API_VERSION = 'v2025-02-19'
@@ -9,18 +8,13 @@ const API_VERSION = 'v2025-02-19'
9
8
  /** @public */
10
9
  export const datasets = createFetcherStore({
11
10
  name: 'Datasets',
12
- getKey: (instance, options?: ProjectHandle) => {
13
- const projectId = options?.projectId ?? instance.config.projectId
14
- if (!projectId) {
15
- throw new Error('A projectId is required to use the project API.')
16
- }
17
- return projectId
11
+ getKey: (_instance, options: {projectId: string}) => {
12
+ return options.projectId
18
13
  },
19
- fetcher: (instance) => (options?: ProjectHandle) => {
14
+ fetcher: (instance) => (options: {projectId: string}) => {
20
15
  return getClientState(instance, {
21
16
  apiVersion: API_VERSION,
22
- // non-null assertion is fine because we check above
23
- projectId: (options?.projectId ?? instance.config.projectId)!,
17
+ projectId: options.projectId,
24
18
  useProjectHostname: true,
25
19
  }).observable.pipe(switchMap((client) => client.observable.datasets.list()))
26
20
  },