@sanity/sdk 2.9.0 → 2.11.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 (73) hide show
  1. package/dist/_chunks-dts/utils.d.ts +295 -69
  2. package/dist/_chunks-es/_internal.js +3 -14
  3. package/dist/_chunks-es/_internal.js.map +1 -1
  4. package/dist/_chunks-es/createGroqSearchFilter.js +129 -59
  5. package/dist/_chunks-es/createGroqSearchFilter.js.map +1 -1
  6. package/dist/_chunks-es/version.js +1 -1
  7. package/dist/_exports/_internal.d.ts +16 -2
  8. package/dist/_exports/_internal.js +3 -1
  9. package/dist/index.d.ts +2 -2
  10. package/dist/index.js +275 -149
  11. package/dist/index.js.map +1 -1
  12. package/package.json +11 -15
  13. package/src/_exports/_internal.ts +1 -0
  14. package/src/_exports/index.ts +33 -2
  15. package/src/agent/agentActions.ts +21 -25
  16. package/src/client/clientStore.test.ts +24 -60
  17. package/src/client/clientStore.ts +49 -56
  18. package/src/comlink/controller/actions/getOrCreateChannel.ts +2 -2
  19. package/src/comlink/node/actions/getOrCreateNode.test.ts +5 -2
  20. package/src/comlink/node/actions/getOrCreateNode.ts +2 -2
  21. package/src/comlink/node/actions/releaseNode.test.ts +3 -3
  22. package/src/config/sanityConfig.ts +72 -13
  23. package/src/document/applyDocumentActions.test.ts +7 -7
  24. package/src/document/applyDocumentActions.ts +5 -5
  25. package/src/document/documentStore.test.ts +68 -62
  26. package/src/document/documentStore.ts +33 -38
  27. package/src/document/processActions.ts +2 -2
  28. package/src/document/reducers.ts +4 -4
  29. package/src/document/sharedListener.ts +5 -7
  30. package/src/organization/organization.test-d.ts +102 -0
  31. package/src/organization/organization.test.ts +138 -0
  32. package/src/organization/organization.ts +166 -0
  33. package/src/organizations/organizations.test-d.ts +77 -0
  34. package/src/organizations/organizations.test.ts +150 -0
  35. package/src/organizations/organizations.ts +132 -0
  36. package/src/presence/bifurTransport.test.ts +46 -6
  37. package/src/presence/bifurTransport.ts +13 -1
  38. package/src/presence/presenceStore.test.ts +101 -5
  39. package/src/presence/presenceStore.ts +96 -24
  40. package/src/preview/getPreviewState.ts +1 -1
  41. package/src/preview/previewProjectionUtils.test.ts +4 -4
  42. package/src/preview/previewProjectionUtils.ts +6 -7
  43. package/src/preview/resolvePreview.ts +5 -1
  44. package/src/project/project.test-d.ts +93 -0
  45. package/src/project/project.test.ts +108 -10
  46. package/src/project/project.ts +152 -26
  47. package/src/projection/getProjectionState.ts +4 -4
  48. package/src/projection/projectionStore.test.ts +2 -2
  49. package/src/projection/resolveProjection.ts +2 -2
  50. package/src/projection/subscribeToStateAndFetchBatches.test.ts +1 -1
  51. package/src/projection/subscribeToStateAndFetchBatches.ts +11 -15
  52. package/src/projects/projects.test-d.ts +38 -0
  53. package/src/projects/projects.test.ts +104 -38
  54. package/src/projects/projects.ts +74 -14
  55. package/src/query/queryStore.test.ts +12 -12
  56. package/src/query/queryStore.ts +10 -11
  57. package/src/query/reducers.ts +3 -3
  58. package/src/releases/getPerspectiveState.ts +5 -5
  59. package/src/releases/releasesStore.test.ts +6 -6
  60. package/src/releases/releasesStore.ts +9 -9
  61. package/src/store/createActionBinder.test.ts +31 -31
  62. package/src/store/createActionBinder.ts +43 -38
  63. package/src/store/createSanityInstance.ts +5 -6
  64. package/src/telemetry/devMode.test.ts +8 -0
  65. package/src/telemetry/devMode.ts +10 -9
  66. package/src/telemetry/initTelemetry.test.ts +0 -17
  67. package/src/telemetry/initTelemetry.ts +2 -12
  68. package/src/users/reducers.ts +3 -4
  69. package/src/utils/createFetcherStore.ts +6 -4
  70. package/src/utils/isImportError.test.ts +72 -0
  71. package/src/utils/isImportError.ts +34 -0
  72. package/src/utils/object.test.ts +95 -0
  73. package/src/utils/object.ts +142 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanity/sdk",
3
- "version": "2.9.0",
3
+ "version": "2.11.0",
4
4
  "private": false,
5
5
  "description": "Sanity SDK",
6
6
  "keywords": [
@@ -49,45 +49,41 @@
49
49
  "prettier": "@sanity/prettier-config",
50
50
  "dependencies": {
51
51
  "@sanity/bifur-client": "^0.4.1",
52
- "@sanity/client": "^7.14.1",
53
- "@sanity/comlink": "^3.0.4",
52
+ "@sanity/client": "^7.22.0",
53
+ "@sanity/comlink": "^3.1.1",
54
54
  "@sanity/diff-match-patch": "^3.2.0",
55
55
  "@sanity/diff-patch": "^6.0.0",
56
56
  "@sanity/id-utils": "^1.0.0",
57
57
  "@sanity/image-url": "^2.0.3",
58
58
  "@sanity/json-match": "^1.0.5",
59
- "@sanity/message-protocol": "^0.18.0",
59
+ "@sanity/message-protocol": "^0.23.0",
60
60
  "@sanity/mutate": "^0.16.1",
61
- "@sanity/telemetry": "^1.0.0",
61
+ "@sanity/telemetry": "^1.1.0",
62
62
  "@sanity/types": "^5.2.0",
63
63
  "groq": "3.88.1-typegen-experimental.0",
64
- "groq-js": "^1.19.0",
65
- "lodash-es": "^4.17.21",
64
+ "groq-js": "^1.30.1",
66
65
  "reselect": "^5.1.1",
67
66
  "rxjs": "^7.8.2",
68
- "zustand": "^5.0.4"
67
+ "zustand": "^5.0.12"
69
68
  },
70
69
  "devDependencies": {
71
70
  "@sanity/browserslist-config": "^1.0.5",
72
71
  "@sanity/pkg-utils": "^8.1.29",
73
72
  "@sanity/prettier-config": "^1.0.6",
74
- "@types/lodash-es": "^4.17.12",
75
- "@vitest/coverage-v8": "3.2.4",
73
+ "@types/node": "^22.19.1",
74
+ "@vitest/coverage-v8": "4.1.5",
76
75
  "eslint": "^9.22.0",
77
76
  "prettier": "^3.7.3",
78
77
  "rollup-plugin-visualizer": "^5.14.0",
79
78
  "typescript": "^5.8.3",
80
79
  "vite": "^7.0.0",
81
- "vitest": "^3.2.4",
80
+ "vitest": "^4.1.4",
82
81
  "@repo/config-eslint": "0.0.0",
83
- "@repo/config-test": "0.0.1",
84
82
  "@repo/package.bundle": "3.82.0",
83
+ "@repo/config-test": "0.0.1",
85
84
  "@repo/package.config": "0.0.1",
86
85
  "@repo/tsconfig": "0.0.1"
87
86
  },
88
- "engines": {
89
- "node": ">=20.19"
90
- },
91
87
  "publishConfig": {
92
88
  "access": "public"
93
89
  },
@@ -12,3 +12,4 @@ export {getQueryKey, parseQueryKey} from '../query/queryStore' // only used for
12
12
  export {getTelemetryManager, initTelemetry, trackHookMounted} from '../telemetry/initTelemetry'
13
13
  export {getUsersKey, parseUsersKey} from '../users/reducers' // only used for memoizing in React, not needed for actual functionality
14
14
  export {createGroqSearchFilter} from '../utils/createGroqSearchFilter'
15
+ export {isDeepEqual, pickProperties} from '../utils/object'
@@ -86,15 +86,22 @@ export {
86
86
  type LogNamespace,
87
87
  } from '../config/loggingConfig'
88
88
  export {
89
+ type CanvasResource,
89
90
  type CanvasSource,
90
91
  type DatasetHandle,
92
+ type DatasetResource,
91
93
  type DatasetSource,
92
94
  type DocumentHandle,
95
+ type DocumentResource,
93
96
  type DocumentSource,
94
97
  type DocumentTypeHandle,
98
+ isCanvasResource,
95
99
  isCanvasSource,
100
+ isDatasetResource,
96
101
  isDatasetSource,
102
+ isMediaLibraryResource,
97
103
  isMediaLibrarySource,
104
+ type MediaLibraryResource,
98
105
  type MediaLibrarySource,
99
106
  type PerspectiveHandle,
100
107
  type ProjectHandle,
@@ -150,6 +157,20 @@ export {type JsonMatch} from '../document/patchOperations'
150
157
  export {type DocumentPermissionsResult, type PermissionDeniedReason} from '../document/permissions'
151
158
  export type {FavoriteStatusResponse} from '../favorites/favorites'
152
159
  export {getFavoritesState, resolveFavoritesState} from '../favorites/favorites'
160
+ export {
161
+ getOrganizationState,
162
+ type Organization,
163
+ type OrganizationBase,
164
+ type OrganizationMember,
165
+ type OrganizationOptions,
166
+ resolveOrganization,
167
+ } from '../organization/organization'
168
+ export {
169
+ getOrganizationsState,
170
+ type Organizations,
171
+ type OrganizationsOptions,
172
+ resolveOrganizations,
173
+ } from '../organizations/organizations'
153
174
  export {getPresence} from '../presence/presenceStore'
154
175
  export type {
155
176
  DisconnectEvent,
@@ -171,11 +192,20 @@ export type {
171
192
  ValuePending,
172
193
  } from '../preview/types'
173
194
  export {type OrgVerificationResult} from '../project/organizationVerification'
174
- export {getProjectState, resolveProject} from '../project/project'
195
+ export {
196
+ getProjectState,
197
+ type Project,
198
+ type ProjectBase,
199
+ type ProjectMember,
200
+ type ProjectMemberRole,
201
+ type ProjectMetadata,
202
+ type ProjectOptions,
203
+ resolveProject,
204
+ } from '../project/project'
175
205
  export {getProjectionState} from '../projection/getProjectionState'
176
206
  export {resolveProjection} from '../projection/resolveProjection'
177
207
  export {type ProjectionValuePending, type ValidProjection} from '../projection/types'
178
- export {getProjectsState, resolveProjects} from '../projects/projects'
208
+ export {getProjectsState, type ProjectsOptions, resolveProjects} from '../projects/projects'
179
209
  export {
180
210
  getQueryKey,
181
211
  getQueryState,
@@ -212,6 +242,7 @@ export {type FetcherStore, type FetcherStoreState} from '../utils/createFetcherS
212
242
  export {createGroqSearchFilter} from '../utils/createGroqSearchFilter'
213
243
  export {defineIntent, type Intent, type IntentFilter} from '../utils/defineIntent'
214
244
  export {getCorsErrorProjectId} from '../utils/getCorsErrorProjectId'
245
+ export {isImportError} from '../utils/isImportError'
215
246
  export {CORE_SDK_VERSION} from '../version'
216
247
  export {
217
248
  getIndexForKey,
@@ -2,6 +2,7 @@ import {type SanityClient} from '@sanity/client'
2
2
  import {from, Observable, switchMap} from 'rxjs'
3
3
 
4
4
  import {getClientState} from '../client/clientStore'
5
+ import {type DocumentResource} from '../config/sanityConfig'
5
6
  import {type SanityInstance} from '../store/createSanityInstance'
6
7
 
7
8
  const API_VERSION = 'vX'
@@ -58,12 +59,11 @@ export type AgentPatchResult = Awaited<ReturnType<SanityClient['agent']['action'
58
59
  export function agentGenerate(
59
60
  instance: SanityInstance,
60
61
  options: AgentGenerateOptions,
62
+ resource?: DocumentResource,
61
63
  ): AgentGenerateResult {
62
- return getClientState(instance, {
63
- apiVersion: API_VERSION,
64
- projectId: instance.config.projectId,
65
- dataset: instance.config.dataset,
66
- }).observable.pipe(switchMap((client) => client.observable.agent.action.generate(options)))
64
+ return getClientState(instance, {apiVersion: API_VERSION, resource}).observable.pipe(
65
+ switchMap((client) => client.observable.agent.action.generate(options)),
66
+ )
67
67
  }
68
68
 
69
69
  /**
@@ -76,12 +76,11 @@ export function agentGenerate(
76
76
  export function agentTransform(
77
77
  instance: SanityInstance,
78
78
  options: AgentTransformOptions,
79
+ resource?: DocumentResource,
79
80
  ): AgentTransformResult {
80
- return getClientState(instance, {
81
- apiVersion: API_VERSION,
82
- projectId: instance.config.projectId,
83
- dataset: instance.config.dataset,
84
- }).observable.pipe(switchMap((client) => client.observable.agent.action.transform(options)))
81
+ return getClientState(instance, {apiVersion: API_VERSION, resource}).observable.pipe(
82
+ switchMap((client) => client.observable.agent.action.transform(options)),
83
+ )
85
84
  }
86
85
 
87
86
  /**
@@ -94,12 +93,11 @@ export function agentTransform(
94
93
  export function agentTranslate(
95
94
  instance: SanityInstance,
96
95
  options: AgentTranslateOptions,
96
+ resource?: DocumentResource,
97
97
  ): AgentTranslateResult {
98
- return getClientState(instance, {
99
- apiVersion: API_VERSION,
100
- projectId: instance.config.projectId,
101
- dataset: instance.config.dataset,
102
- }).observable.pipe(switchMap((client) => client.observable.agent.action.translate(options)))
98
+ return getClientState(instance, {apiVersion: API_VERSION, resource}).observable.pipe(
99
+ switchMap((client) => client.observable.agent.action.translate(options)),
100
+ )
103
101
  }
104
102
 
105
103
  /**
@@ -112,12 +110,11 @@ export function agentTranslate(
112
110
  export function agentPrompt(
113
111
  instance: SanityInstance,
114
112
  options: AgentPromptOptions,
113
+ resource?: DocumentResource,
115
114
  ): Observable<AgentPromptResult> {
116
- return getClientState(instance, {
117
- apiVersion: API_VERSION,
118
- projectId: instance.config.projectId,
119
- dataset: instance.config.dataset,
120
- }).observable.pipe(switchMap((client) => from(client.agent.action.prompt(options))))
115
+ return getClientState(instance, {apiVersion: API_VERSION, resource}).observable.pipe(
116
+ switchMap((client) => from(client.agent.action.prompt(options))),
117
+ )
121
118
  }
122
119
 
123
120
  /**
@@ -130,10 +127,9 @@ export function agentPrompt(
130
127
  export function agentPatch(
131
128
  instance: SanityInstance,
132
129
  options: AgentPatchOptions,
130
+ resource?: DocumentResource,
133
131
  ): Observable<AgentPatchResult> {
134
- return getClientState(instance, {
135
- apiVersion: API_VERSION,
136
- projectId: instance.config.projectId,
137
- dataset: instance.config.dataset,
138
- }).observable.pipe(switchMap((client) => from(client.agent.action.patch(options))))
132
+ return getClientState(instance, {apiVersion: API_VERSION, resource}).observable.pipe(
133
+ switchMap((client) => from(client.agent.action.patch(options))),
134
+ )
139
135
  }
@@ -190,17 +190,17 @@ describe('clientStore', () => {
190
190
  })
191
191
  })
192
192
 
193
- describe('source handling', () => {
194
- it('should create client when source is provided', () => {
193
+ describe('resource handling', () => {
194
+ it('should create resource when media library resource is provided and be projectless', () => {
195
195
  const client = getClient(instance, {
196
196
  apiVersion: '2024-11-12',
197
- source: {projectId: 'source-project', dataset: 'source-dataset'},
197
+ resource: {mediaLibraryId: 'media-lib-123'},
198
198
  })
199
199
 
200
200
  expect(vi.mocked(createClient)).toHaveBeenCalledWith(
201
201
  expect.objectContaining({
202
- 'apiVersion': '2024-11-12',
203
- '~experimental_resource': {type: 'dataset', id: 'source-project.source-dataset'},
202
+ resource: {type: 'media-library', id: 'media-lib-123'},
203
+ apiVersion: '2024-11-12',
204
204
  }),
205
205
  )
206
206
  // Client should be projectless - no projectId/dataset in config
@@ -208,21 +208,21 @@ describe('clientStore', () => {
208
208
  expect(client.config()).not.toHaveProperty('dataset')
209
209
  expect(client.config()).toEqual(
210
210
  expect.objectContaining({
211
- '~experimental_resource': {type: 'dataset', id: 'source-project.source-dataset'},
211
+ resource: {type: 'media-library', id: 'media-lib-123'},
212
212
  }),
213
213
  )
214
214
  })
215
215
 
216
- it('should create resource when source has array sourceId and be projectless', () => {
216
+ it('should create resource when canvas resource is provided and be projectless', () => {
217
217
  const client = getClient(instance, {
218
218
  apiVersion: '2024-11-12',
219
- source: {mediaLibraryId: 'media-lib-123'},
219
+ resource: {canvasId: 'canvas-123'},
220
220
  })
221
221
 
222
222
  expect(vi.mocked(createClient)).toHaveBeenCalledWith(
223
223
  expect.objectContaining({
224
- '~experimental_resource': {type: 'media-library', id: 'media-lib-123'},
225
- 'apiVersion': '2024-11-12',
224
+ resource: {type: 'canvas', id: 'canvas-123'},
225
+ apiVersion: '2024-11-12',
226
226
  }),
227
227
  )
228
228
  // Client should be projectless - no projectId/dataset in config
@@ -230,79 +230,43 @@ describe('clientStore', () => {
230
230
  expect(client.config()).not.toHaveProperty('dataset')
231
231
  expect(client.config()).toEqual(
232
232
  expect.objectContaining({
233
- '~experimental_resource': {type: 'media-library', id: 'media-lib-123'},
233
+ resource: {type: 'canvas', id: 'canvas-123'},
234
234
  }),
235
235
  )
236
236
  })
237
237
 
238
- it('should create resource when canvas source is provided and be projectless', () => {
238
+ it('should transform dataset resource to project-based config for now', () => {
239
239
  const client = getClient(instance, {
240
240
  apiVersion: '2024-11-12',
241
- source: {canvasId: 'canvas-123'},
241
+ resource: {projectId: 'source-project', dataset: 'source-dataset'},
242
242
  })
243
243
 
244
244
  expect(vi.mocked(createClient)).toHaveBeenCalledWith(
245
245
  expect.objectContaining({
246
- '~experimental_resource': {type: 'canvas', id: 'canvas-123'},
247
- 'apiVersion': '2024-11-12',
246
+ projectId: 'source-project',
247
+ dataset: 'source-dataset',
248
248
  }),
249
249
  )
250
- // Client should be projectless - no projectId/dataset in config
251
- expect(client.config()).not.toHaveProperty('projectId')
252
- expect(client.config()).not.toHaveProperty('dataset')
253
- expect(client.config()).toEqual(
254
- expect.objectContaining({
255
- '~experimental_resource': {type: 'canvas', id: 'canvas-123'},
256
- }),
257
- )
258
- })
259
-
260
- it('should create projectless client when source is provided, ignoring instance config', () => {
261
- const client = getClient(instance, {
262
- apiVersion: '2024-11-12',
263
- source: {projectId: 'source-project', dataset: 'source-dataset'},
264
- })
265
-
266
- // Client should be projectless - source takes precedence, instance config is ignored
267
- expect(client.config()).not.toHaveProperty('projectId')
268
- expect(client.config()).not.toHaveProperty('dataset')
269
250
  expect(client.config()).toEqual(
270
251
  expect.objectContaining({
271
- '~experimental_resource': {type: 'dataset', id: 'source-project.source-dataset'},
252
+ projectId: 'source-project',
253
+ dataset: 'source-dataset',
272
254
  }),
273
255
  )
274
256
  })
275
257
 
276
- it('should warn when both source and explicit projectId/dataset are provided', () => {
277
- const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
278
- const client = getClient(instance, {
279
- apiVersion: '2024-11-12',
280
- source: {projectId: 'source-project', dataset: 'source-dataset'},
281
- projectId: 'explicit-project',
282
- dataset: 'explicit-dataset',
283
- })
284
-
285
- expect(consoleSpy).toHaveBeenCalledWith(
286
- 'Both source and explicit projectId/dataset are provided. The source will be used and projectId/dataset will be ignored.',
287
- )
288
- // Client should still be projectless despite explicit projectId/dataset
289
- expect(client.config()).not.toHaveProperty('projectId')
290
- expect(client.config()).not.toHaveProperty('dataset')
291
- consoleSpy.mockRestore()
292
- })
293
-
294
- it('should create different clients for different sources', () => {
258
+ it('should create different clients for different resources', () => {
295
259
  const client1 = getClient(instance, {
296
260
  apiVersion: '2024-11-12',
297
- source: {projectId: 'source-project', dataset: 'source-dataset'},
261
+ resource: {projectId: 'source-project', dataset: 'source-dataset'},
298
262
  })
299
263
  const client2 = getClient(instance, {
300
264
  apiVersion: '2024-11-12',
301
- source: {mediaLibraryId: 'media-lib-123'},
265
+ resource: {mediaLibraryId: 'media-lib-123'},
302
266
  })
303
267
  const client3 = getClient(instance, {
304
268
  apiVersion: '2024-11-12',
305
- source: {canvasId: 'canvas-123'},
269
+ resource: {canvasId: 'canvas-123'},
306
270
  })
307
271
 
308
272
  expect(client1).not.toBe(client2)
@@ -311,14 +275,14 @@ describe('clientStore', () => {
311
275
  expect(vi.mocked(createClient)).toHaveBeenCalledTimes(3)
312
276
  })
313
277
 
314
- it('should reuse clients with identical source configurations', () => {
278
+ it('should reuse clients with identical resource configurations', () => {
315
279
  const client1 = getClient(instance, {
316
280
  apiVersion: '2024-11-12',
317
- source: {projectId: 'source-project', dataset: 'source-dataset'},
281
+ resource: {projectId: 'source-project', dataset: 'source-dataset'},
318
282
  })
319
283
  const client2 = getClient(instance, {
320
284
  apiVersion: '2024-11-12',
321
- source: {projectId: 'source-project', dataset: 'source-dataset'},
285
+ resource: {projectId: 'source-project', dataset: 'source-dataset'},
322
286
  })
323
287
 
324
288
  expect(client1).toBe(client2)
@@ -1,17 +1,17 @@
1
1
  import {type ClientConfig, createClient, type SanityClient} from '@sanity/client'
2
- import {pick} from 'lodash-es'
3
2
 
4
3
  import {getAuthMethodState, getTokenState} from '../auth/authStore'
5
4
  import {
6
- type DocumentSource,
7
- isCanvasSource,
8
- isDatasetSource,
9
- isMediaLibrarySource,
5
+ type DocumentResource,
6
+ isCanvasResource,
7
+ isDatasetResource,
8
+ isMediaLibraryResource,
10
9
  } from '../config/sanityConfig'
11
10
  import {bindActionGlobally} from '../store/createActionBinder'
12
11
  import {createStateSourceAction} from '../store/createStateSourceAction'
13
12
  import {defineStore, type StoreContext} from '../store/defineStore'
14
13
  import {getStagingApiHost} from '../utils/getStagingApiHost'
14
+ import {pickProperties} from '../utils/object'
15
15
 
16
16
  const DEFAULT_API_VERSION = '2024-11-12'
17
17
  const DEFAULT_REQUEST_TAG_PREFIX = 'sanity.sdk'
@@ -31,22 +31,21 @@ type AllowedClientConfigKey =
31
31
  | 'useProjectHostname'
32
32
 
33
33
  const allowedKeys = Object.keys({
34
- 'apiHost': null,
35
- 'useCdn': null,
36
- 'token': null,
37
- 'perspective': null,
38
- 'proxy': null,
39
- 'withCredentials': null,
40
- 'timeout': null,
41
- 'maxRetries': null,
42
- 'dataset': null,
43
- 'projectId': null,
44
- 'scope': null,
45
- 'apiVersion': null,
46
- 'requestTagPrefix': null,
47
- 'useProjectHostname': null,
48
- '~experimental_resource': null,
49
- 'source': null,
34
+ apiHost: null,
35
+ useCdn: null,
36
+ token: null,
37
+ perspective: null,
38
+ proxy: null,
39
+ withCredentials: null,
40
+ timeout: null,
41
+ maxRetries: null,
42
+ dataset: null,
43
+ projectId: null,
44
+ scope: null,
45
+ apiVersion: null,
46
+ requestTagPrefix: null,
47
+ useProjectHostname: null,
48
+ resource: null,
50
49
  } satisfies Record<keyof ClientOptions, null>) as (keyof ClientOptions)[]
51
50
 
52
51
  const DEFAULT_CLIENT_CONFIG: ClientConfig = {
@@ -67,11 +66,6 @@ export interface ClientStoreState {
67
66
  authMethod?: 'localstorage' | 'cookie'
68
67
  }
69
68
 
70
- interface ClientResource {
71
- type: 'dataset' | 'media-library' | 'canvas'
72
- id: string
73
- }
74
-
75
69
  /**
76
70
  * Options used when retrieving a client instance from the client store.
77
71
  *
@@ -94,20 +88,17 @@ export interface ClientOptions extends Pick<ClientConfig, AllowedClientConfigKey
94
88
  * and the global client ('global'). When set to `'global'`, the global client
95
89
  * is used.
96
90
  */
97
- 'scope'?: 'default' | 'global'
91
+ scope?: 'default' | 'global'
98
92
  /**
99
93
  * A required string indicating the API version for the client.
100
94
  */
101
- 'apiVersion': string
102
- /**
103
- * @internal
104
- */
105
- '~experimental_resource'?: ClientConfig['~experimental_resource']
95
+ apiVersion: string
106
96
 
107
97
  /**
108
98
  * @internal
99
+ * The SDK resource to use for the client -- this will get transformed into a ClientConfig resource.
109
100
  */
110
- 'source'?: DocumentSource
101
+ resource?: DocumentResource
111
102
  }
112
103
 
113
104
  const clientStore = defineStore<ClientStoreState>({
@@ -144,7 +135,13 @@ const listenToAuthMethod = ({instance, state}: StoreContext<ClientStoreState>) =
144
135
  })
145
136
  }
146
137
 
147
- const getClientConfigKey = (options: ClientOptions) => JSON.stringify(pick(options, ...allowedKeys))
138
+ type ClientInstanceCacheKeyInput = ClientConfig &
139
+ Partial<Pick<ClientOptions, 'scope'>> & {
140
+ apiVersion: string
141
+ }
142
+
143
+ const getClientConfigKey = (options: ClientInstanceCacheKeyInput) =>
144
+ JSON.stringify(pickProperties(options, allowedKeys))
148
145
 
149
146
  /**
150
147
  * Retrieves a Sanity client instance configured with the provided options.
@@ -181,44 +178,40 @@ export const getClient = bindActionGlobally(
181
178
 
182
179
  const tokenFromState = state.get().token
183
180
  const {clients, authMethod} = state.get()
184
-
185
- let resource: ClientResource | undefined
186
-
187
- if (options.source) {
188
- if (isDatasetSource(options.source)) {
189
- resource = {type: 'dataset', id: `${options.source.projectId}.${options.source.dataset}`}
190
- } else if (isMediaLibrarySource(options.source)) {
191
- resource = {type: 'media-library', id: options.source.mediaLibraryId}
192
- } else if (isCanvasSource(options.source)) {
193
- resource = {type: 'canvas', id: options.source.canvasId}
181
+ let projectId = options.projectId ?? instance.config.projectId
182
+ let dataset = options.dataset ?? instance.config.dataset
183
+
184
+ let resource: ClientConfig['resource'] | undefined
185
+
186
+ if (options.resource) {
187
+ if (isMediaLibraryResource(options.resource)) {
188
+ resource = {type: 'media-library', id: options.resource.mediaLibraryId}
189
+ } else if (isCanvasResource(options.resource)) {
190
+ resource = {type: 'canvas', id: options.resource.canvasId}
191
+ } else if (isDatasetResource(options.resource)) {
192
+ // use project-based routes for datasets to avoid existing CORS and Studio auth cookie issues
193
+ projectId = options.resource.projectId
194
+ dataset = options.resource.dataset
194
195
  }
195
196
  }
196
197
 
197
- const projectId = options.projectId ?? instance.config.projectId
198
- const dataset = options.dataset ?? instance.config.dataset
199
198
  const apiHost = options.apiHost ?? instance.config.auth?.apiHost ?? getStagingApiHost()
200
199
 
201
- const effectiveOptions: ClientOptions = {
200
+ const effectiveOptions: ClientConfig & {apiVersion: string} = {
202
201
  ...DEFAULT_CLIENT_CONFIG,
203
202
  ...((options.scope === 'global' || !projectId || resource) && {useProjectHostname: false}),
204
203
  token: authMethod === 'cookie' ? undefined : (tokenFromState ?? undefined),
205
204
  ...options,
206
205
  ...(projectId && {projectId}),
207
206
  ...(dataset && {dataset}),
207
+ ...(resource ? {resource} : {resource: undefined}),
208
208
  ...(apiHost && {apiHost}),
209
- ...(resource && {'~experimental_resource': resource}),
210
209
  }
211
210
 
212
- // When a source is provided, don't use projectId/dataset - the client should be "projectless"
213
- // The client code itself will ignore the non-source config, so we do this to prevent confusing the user.
211
+ // When a resource is provided, don't use projectId/dataset - the client should be "projectless"
212
+ // The client code itself will ignore the non-resource config, so we do this to prevent confusing the user.
214
213
  // (ref: https://github.com/sanity-io/client/blob/5c23f81f5ab93a53f5b22b39845c867988508d84/src/data/dataMethods.ts#L691)
215
214
  if (resource) {
216
- if (options.projectId || options.dataset) {
217
- // eslint-disable-next-line no-console
218
- console.warn(
219
- 'Both source and explicit projectId/dataset are provided. The source will be used and projectId/dataset will be ignored.',
220
- )
221
- }
222
215
  delete effectiveOptions.projectId
223
216
  delete effectiveOptions.dataset
224
217
  }
@@ -1,7 +1,7 @@
1
1
  import {type ChannelInput, type ChannelInstance} from '@sanity/comlink'
2
- import {isEqual} from 'lodash-es'
3
2
 
4
3
  import {type StoreContext} from '../../../store/defineStore'
4
+ import {isDeepEqual} from '../../../utils/object'
5
5
  import {type FrameMessage, type WindowMessage} from '../../types'
6
6
  import {type ComlinkControllerState} from '../comlinkControllerStore'
7
7
 
@@ -25,7 +25,7 @@ export const getOrCreateChannel = (
25
25
 
26
26
  // limit channels to one per name
27
27
  if (existing) {
28
- if (!isEqual(existing.options, options)) {
28
+ if (!isDeepEqual(existing.options, options)) {
29
29
  throw new Error(`Channel "${options.name}" already exists with different options`)
30
30
  }
31
31
 
@@ -27,14 +27,17 @@ describe('getOrCreateNode', () => {
27
27
  dataset: 'test-dataset',
28
28
  })
29
29
  let state: ReturnType<typeof createStoreState<ComlinkNodeState>>
30
- let mockNode: Partial<Node<WindowMessage, FrameMessage>> & {
30
+ let mockNode: {
31
31
  start: ReturnType<typeof vi.fn>
32
32
  stop: ReturnType<typeof vi.fn>
33
+ onStatus: ReturnType<typeof vi.fn>
33
34
  }
34
35
 
35
36
  beforeEach(() => {
36
37
  mockNode = {start: vi.fn(), stop: vi.fn(), onStatus: vi.fn()}
37
- vi.mocked(comlink.createNode).mockReturnValue(mockNode as Node<WindowMessage, FrameMessage>)
38
+ vi.mocked(comlink.createNode).mockReturnValue(
39
+ mockNode as unknown as Node<WindowMessage, FrameMessage>,
40
+ )
38
41
  state = createStoreState<ComlinkNodeState>({nodes: new Map(), subscriptions: new Map()})
39
42
  vi.clearAllMocks()
40
43
  })
@@ -1,7 +1,7 @@
1
1
  import {createNode, type Node, type NodeInput} from '@sanity/comlink'
2
- import {isEqual} from 'lodash-es'
3
2
 
4
3
  import {type StoreContext} from '../../../store/defineStore'
4
+ import {isDeepEqual} from '../../../utils/object'
5
5
  import {type FrameMessage, type WindowMessage} from '../../types'
6
6
  import {type ComlinkNodeState} from '../comlinkNodeStore'
7
7
 
@@ -14,7 +14,7 @@ export const getOrCreateNode = (
14
14
 
15
15
  // limit nodes to one per name
16
16
  if (existing) {
17
- if (!isEqual(existing.options, options)) {
17
+ if (!isDeepEqual(existing.options, options)) {
18
18
  throw new Error(`Node "${options.name}" already exists with different options`)
19
19
  }
20
20
 
@@ -15,7 +15,7 @@ const nodeConfig = {
15
15
  describe('releaseNode', () => {
16
16
  let instance: SanityInstance
17
17
  let state: ReturnType<typeof createStoreState<ComlinkNodeState>>
18
- let mockNode: Partial<Node<WindowMessage, FrameMessage>> & {
18
+ let mockNode: {
19
19
  start: ReturnType<typeof vi.fn>
20
20
  stop: ReturnType<typeof vi.fn>
21
21
  onStatus: ReturnType<typeof vi.fn>
@@ -39,7 +39,7 @@ describe('releaseNode', () => {
39
39
  // Set up a node in the state
40
40
  const nodes = new Map()
41
41
  nodes.set('test-node', {
42
- node: mockNode as Node<WindowMessage, FrameMessage>,
42
+ node: mockNode as unknown as Node<WindowMessage, FrameMessage>,
43
43
  options: nodeConfig,
44
44
  })
45
45
  state.set('setup', {nodes})
@@ -58,7 +58,7 @@ describe('releaseNode', () => {
58
58
  const statusUnsub = vi.fn()
59
59
  const nodes = new Map()
60
60
  nodes.set('test-node', {
61
- node: mockNode as Node<WindowMessage, FrameMessage>,
61
+ node: mockNode as unknown as Node<WindowMessage, FrameMessage>,
62
62
  options: nodeConfig,
63
63
  statusUnsub,
64
64
  })