@sanity/sdk 2.10.0 → 2.11.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 (60) hide show
  1. package/dist/_chunks-dts/utils.d.ts +200 -28
  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 +17 -19
  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 +564 -459
  11. package/dist/index.js.map +1 -1
  12. package/package.json +16 -18
  13. package/src/_exports/_internal.ts +1 -0
  14. package/src/_exports/index.ts +25 -2
  15. package/src/agent/agentActions.ts +21 -25
  16. package/src/auth/refreshStampedToken.test.ts +2 -2
  17. package/src/auth/subscribeToStateAndFetchCurrentUser.test.ts +116 -0
  18. package/src/auth/subscribeToStateAndFetchCurrentUser.ts +27 -9
  19. package/src/client/clientStore.test.ts +10 -46
  20. package/src/client/clientStore.ts +7 -14
  21. package/src/comlink/node/actions/getOrCreateNode.test.ts +5 -2
  22. package/src/comlink/node/actions/releaseNode.test.ts +3 -3
  23. package/src/config/sanityConfig.ts +0 -1
  24. package/src/document/documentStore.ts +3 -8
  25. package/src/document/permissions.ts +1 -1
  26. package/src/document/processActions/create.ts +135 -0
  27. package/src/document/processActions/delete.ts +100 -0
  28. package/src/document/processActions/discard.ts +63 -0
  29. package/src/document/processActions/edit.ts +176 -0
  30. package/src/document/processActions/processActions.ts +168 -0
  31. package/src/document/processActions/publish.ts +120 -0
  32. package/src/document/processActions/shared.ts +47 -0
  33. package/src/document/processActions/unpublish.ts +85 -0
  34. package/src/document/processActions.test.ts +1 -1
  35. package/src/document/reducers.ts +1 -1
  36. package/src/document/sharedListener.ts +3 -5
  37. package/src/organization/organization.test-d.ts +102 -0
  38. package/src/organization/organization.test.ts +138 -0
  39. package/src/organization/organization.ts +166 -0
  40. package/src/organizations/organizations.test-d.ts +77 -0
  41. package/src/organizations/organizations.test.ts +150 -0
  42. package/src/organizations/organizations.ts +132 -0
  43. package/src/presence/presenceStore.test.ts +5 -5
  44. package/src/preview/previewProjectionUtils.ts +2 -3
  45. package/src/project/project.test-d.ts +93 -0
  46. package/src/project/project.test.ts +108 -10
  47. package/src/project/project.ts +152 -26
  48. package/src/projection/subscribeToStateAndFetchBatches.ts +4 -9
  49. package/src/projects/projects.test-d.ts +38 -0
  50. package/src/projects/projects.test.ts +104 -38
  51. package/src/projects/projects.ts +74 -14
  52. package/src/query/queryStore.ts +2 -3
  53. package/src/releases/releasesStore.test.ts +1 -1
  54. package/src/releases/releasesStore.ts +2 -2
  55. package/src/store/createSanityInstance.ts +3 -3
  56. package/src/telemetry/devMode.test.ts +8 -0
  57. package/src/telemetry/devMode.ts +10 -9
  58. package/src/telemetry/initTelemetry.test.ts +0 -17
  59. package/src/telemetry/initTelemetry.ts +2 -12
  60. package/src/document/processActions.ts +0 -735
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanity/sdk",
3
- "version": "2.10.0",
3
+ "version": "2.11.1",
4
4
  "private": false,
5
5
  "description": "Sanity SDK",
6
6
  "keywords": [
@@ -48,43 +48,41 @@
48
48
  "browserslist": "extends @sanity/browserslist-config",
49
49
  "prettier": "@sanity/prettier-config",
50
50
  "dependencies": {
51
- "@sanity/bifur-client": "^0.4.1",
51
+ "@sanity/bifur-client": "^1.0.0",
52
52
  "@sanity/client": "^7.22.0",
53
- "@sanity/comlink": "^3.1.1",
53
+ "@sanity/comlink": "^4.0.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
- "@sanity/image-url": "^2.0.3",
57
+ "@sanity/image-url": "^2.1.1",
58
58
  "@sanity/json-match": "^1.0.5",
59
59
  "@sanity/message-protocol": "^0.23.0",
60
60
  "@sanity/mutate": "^0.16.1",
61
61
  "@sanity/telemetry": "^1.1.0",
62
- "@sanity/types": "^5.2.0",
62
+ "@sanity/types": "^5.24.0",
63
63
  "groq": "3.88.1-typegen-experimental.0",
64
- "groq-js": "^1.19.0",
64
+ "groq-js": "^1.30.1",
65
65
  "reselect": "^5.1.1",
66
66
  "rxjs": "^7.8.2",
67
- "zustand": "^5.0.12"
67
+ "zustand": "^5.0.13"
68
68
  },
69
69
  "devDependencies": {
70
70
  "@sanity/browserslist-config": "^1.0.5",
71
71
  "@sanity/pkg-utils": "^8.1.29",
72
72
  "@sanity/prettier-config": "^1.0.6",
73
- "@vitest/coverage-v8": "3.2.4",
74
- "eslint": "^9.22.0",
75
- "prettier": "^3.7.3",
73
+ "@types/node": "^24.12.3",
74
+ "@vitest/coverage-v8": "4.1.5",
75
+ "eslint": "^9.39.4",
76
+ "prettier": "^3.8.3",
76
77
  "rollup-plugin-visualizer": "^5.14.0",
77
- "typescript": "^5.8.3",
78
- "vite": "^7.0.0",
79
- "vitest": "^3.2.4",
78
+ "typescript": "^5.9.3",
79
+ "vite": "^7.3.3",
80
+ "vitest": "^4.1.5",
80
81
  "@repo/config-eslint": "0.0.0",
81
- "@repo/tsconfig": "0.0.1",
82
82
  "@repo/package.bundle": "3.82.0",
83
+ "@repo/config-test": "0.0.1",
83
84
  "@repo/package.config": "0.0.1",
84
- "@repo/config-test": "0.0.1"
85
- },
86
- "engines": {
87
- "node": ">=20.19"
85
+ "@repo/tsconfig": "0.0.1"
88
86
  },
89
87
  "publishConfig": {
90
88
  "access": "public"
@@ -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'
@@ -157,6 +157,20 @@ export {type JsonMatch} from '../document/patchOperations'
157
157
  export {type DocumentPermissionsResult, type PermissionDeniedReason} from '../document/permissions'
158
158
  export type {FavoriteStatusResponse} from '../favorites/favorites'
159
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'
160
174
  export {getPresence} from '../presence/presenceStore'
161
175
  export type {
162
176
  DisconnectEvent,
@@ -178,11 +192,20 @@ export type {
178
192
  ValuePending,
179
193
  } from '../preview/types'
180
194
  export {type OrgVerificationResult} from '../project/organizationVerification'
181
- 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'
182
205
  export {getProjectionState} from '../projection/getProjectionState'
183
206
  export {resolveProjection} from '../projection/resolveProjection'
184
207
  export {type ProjectionValuePending, type ValidProjection} from '../projection/types'
185
- export {getProjectsState, resolveProjects} from '../projects/projects'
208
+ export {getProjectsState, type ProjectsOptions, resolveProjects} from '../projects/projects'
186
209
  export {
187
210
  getQueryKey,
188
211
  getQueryState,
@@ -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
  }
@@ -59,8 +59,8 @@ describe('refreshStampedToken', () => {
59
59
  request: vi.fn(
60
60
  async (
61
61
  _name: string,
62
- _options: LockOptions | LockGrantedCallback,
63
- callback?: LockGrantedCallback,
62
+ _options: LockOptions | LockGrantedCallback<unknown>,
63
+ callback?: LockGrantedCallback<unknown>,
64
64
  ) => {
65
65
  const actualCallback = typeof _options === 'function' ? _options : callback
66
66
  if (!actualCallback) return false
@@ -112,4 +112,120 @@ describe('subscribeToStateAndFetchCurrentUser', () => {
112
112
 
113
113
  subscription.unsubscribe()
114
114
  })
115
+
116
+ it('recovers from a fetch error when a new token is set', () => {
117
+ const error = new Error('Unauthorized')
118
+ const mockUser = {id: 'recovered-user'} as CurrentUser
119
+ const mockRequest = vi
120
+ .fn()
121
+ .mockReturnValueOnce(throwError(() => error))
122
+ .mockReturnValueOnce(of(mockUser))
123
+ const mockClient = {observable: {request: mockRequest}}
124
+ const clientFactory = vi.fn().mockReturnValue(mockClient)
125
+ const instance = createSanityInstance({projectId: 'p', dataset: 'd', auth: {clientFactory}})
126
+
127
+ const state = createStoreState(authStore.getInitialState(instance, null))
128
+ const subscription = subscribeToStateAndFetchCurrentUser({state, instance, key: null})
129
+
130
+ // First token causes a 401 — state should transition to ERROR
131
+ state.set('setLoggedIn', {
132
+ authState: {type: AuthStateType.LOGGED_IN, token: 'expired-token', currentUser: null},
133
+ })
134
+ expect(state.get()).toMatchObject({authState: {type: AuthStateType.ERROR, error}})
135
+
136
+ // Simulate comlink providing a fresh token (setAuthToken sets LOGGED_IN with new token)
137
+ state.set('setNewToken', {
138
+ authState: {type: AuthStateType.LOGGED_IN, token: 'fresh-token', currentUser: null},
139
+ })
140
+
141
+ // Subscription should still be alive — re-fetches /users/me with the new token
142
+ expect(state.get()).toMatchObject({
143
+ authState: {type: AuthStateType.LOGGED_IN, token: 'fresh-token', currentUser: mockUser},
144
+ })
145
+ expect(mockRequest).toHaveBeenCalledTimes(2)
146
+
147
+ subscription.unsubscribe()
148
+ })
149
+
150
+ it('recovers from multiple consecutive fetch errors', () => {
151
+ const error1 = new Error('Unauthorized')
152
+ const error2 = new Error('Unauthorized again')
153
+ const mockUser = {id: 'finally-recovered'} as CurrentUser
154
+ const mockRequest = vi
155
+ .fn()
156
+ .mockReturnValueOnce(throwError(() => error1))
157
+ .mockReturnValueOnce(throwError(() => error2))
158
+ .mockReturnValueOnce(of(mockUser))
159
+ const mockClient = {observable: {request: mockRequest}}
160
+ const clientFactory = vi.fn().mockReturnValue(mockClient)
161
+ const instance = createSanityInstance({projectId: 'p', dataset: 'd', auth: {clientFactory}})
162
+
163
+ const state = createStoreState(authStore.getInitialState(instance, null))
164
+ const subscription = subscribeToStateAndFetchCurrentUser({state, instance, key: null})
165
+
166
+ // First attempt fails
167
+ state.set('setLoggedIn', {
168
+ authState: {type: AuthStateType.LOGGED_IN, token: 'token-1', currentUser: null},
169
+ })
170
+ expect(state.get()).toMatchObject({authState: {type: AuthStateType.ERROR, error: error1}})
171
+
172
+ // Second attempt also fails
173
+ state.set('setNewToken', {
174
+ authState: {type: AuthStateType.LOGGED_IN, token: 'token-2', currentUser: null},
175
+ })
176
+ expect(state.get()).toMatchObject({authState: {type: AuthStateType.ERROR, error: error2}})
177
+
178
+ // Third attempt succeeds
179
+ state.set('setNewToken', {
180
+ authState: {type: AuthStateType.LOGGED_IN, token: 'token-3', currentUser: null},
181
+ })
182
+ expect(state.get()).toMatchObject({
183
+ authState: {type: AuthStateType.LOGGED_IN, token: 'token-3', currentUser: mockUser},
184
+ })
185
+ expect(mockRequest).toHaveBeenCalledTimes(3)
186
+
187
+ subscription.unsubscribe()
188
+ })
189
+
190
+ it('does not re-fetch with the same token but recovers with a different token', () => {
191
+ const error = new Error('Unauthorized')
192
+ const mockUser = {id: 'recovered-user'} as CurrentUser
193
+ const mockRequest = vi
194
+ .fn()
195
+ .mockReturnValueOnce(throwError(() => error))
196
+ .mockReturnValueOnce(of(mockUser))
197
+ const mockClient = {observable: {request: mockRequest}}
198
+ const clientFactory = vi.fn().mockReturnValue(mockClient)
199
+ const instance = createSanityInstance({projectId: 'p', dataset: 'd', auth: {clientFactory}})
200
+
201
+ const state = createStoreState(authStore.getInitialState(instance, null))
202
+ const subscription = subscribeToStateAndFetchCurrentUser({state, instance, key: null})
203
+
204
+ // First attempt fails
205
+ state.set('setLoggedIn', {
206
+ authState: {type: AuthStateType.LOGGED_IN, token: 'same-token', currentUser: null},
207
+ })
208
+ expect(state.get()).toMatchObject({authState: {type: AuthStateType.ERROR, error}})
209
+
210
+ // Same token should be blocked by distinctUntilChanged — no re-fetch
211
+ state.set('setNewToken', {
212
+ authState: {type: AuthStateType.LOGGED_IN, token: 'same-token', currentUser: null},
213
+ })
214
+ expect(mockRequest).toHaveBeenCalledTimes(1)
215
+
216
+ // A different token should pass distinctUntilChanged and trigger recovery
217
+ state.set('setNewToken', {
218
+ authState: {type: AuthStateType.LOGGED_IN, token: 'different-token', currentUser: null},
219
+ })
220
+ expect(state.get()).toMatchObject({
221
+ authState: {
222
+ type: AuthStateType.LOGGED_IN,
223
+ token: 'different-token',
224
+ currentUser: mockUser,
225
+ },
226
+ })
227
+ expect(mockRequest).toHaveBeenCalledTimes(2)
228
+
229
+ subscription.unsubscribe()
230
+ })
115
231
  })
@@ -1,5 +1,13 @@
1
1
  import {type CurrentUser} from '@sanity/types'
2
- import {distinctUntilChanged, filter, map, type Subscription, switchMap} from 'rxjs'
2
+ import {
3
+ catchError,
4
+ distinctUntilChanged,
5
+ EMPTY,
6
+ filter,
7
+ map,
8
+ type Subscription,
9
+ switchMap,
10
+ } from 'rxjs'
3
11
 
4
12
  import {type StoreContext} from '../store/defineStore'
5
13
  import {DEFAULT_API_VERSION, REQUEST_TAG_PREFIX} from './authConstants'
@@ -60,11 +68,24 @@ export const subscribeToStateAndFetchCurrentUser = (
60
68
  }),
61
69
  ),
62
70
  switchMap((client) =>
63
- client.observable.request<CurrentUser>({
64
- uri: '/users/me',
65
- method: 'GET',
66
- tag: 'users.get-current',
67
- }),
71
+ client.observable
72
+ .request<CurrentUser>({
73
+ uri: '/users/me',
74
+ method: 'GET',
75
+ tag: 'users.get-current',
76
+ })
77
+ .pipe(
78
+ /**
79
+ * Catch inside switchMap so the outer subscription survives.
80
+ * Without this, a 401 terminates the subscription permanently
81
+ * and subsequent token refreshes via comlink never re-fetch /users/me.
82
+ * @see SDK-1409
83
+ */
84
+ catchError((error) => {
85
+ state.set('setError', {authState: {type: AuthStateType.ERROR, error}})
86
+ return EMPTY
87
+ }),
88
+ ),
68
89
  ),
69
90
  )
70
91
 
@@ -77,8 +98,5 @@ export const subscribeToStateAndFetchCurrentUser = (
77
98
  : prev.authState,
78
99
  }))
79
100
  },
80
- error: (error) => {
81
- state.set('setError', {authState: {type: AuthStateType.ERROR, error}})
82
- },
83
101
  })
84
102
  }
@@ -191,29 +191,7 @@ describe('clientStore', () => {
191
191
  })
192
192
 
193
193
  describe('resource handling', () => {
194
- it('should create client when resource is provided', () => {
195
- const client = getClient(instance, {
196
- apiVersion: '2024-11-12',
197
- resource: {projectId: 'source-project', dataset: 'source-dataset'},
198
- })
199
-
200
- expect(vi.mocked(createClient)).toHaveBeenCalledWith(
201
- expect.objectContaining({
202
- apiVersion: '2024-11-12',
203
- resource: {type: 'dataset', id: 'source-project.source-dataset'},
204
- }),
205
- )
206
- // Client should be projectless - no projectId/dataset in config
207
- expect(client.config()).not.toHaveProperty('projectId')
208
- expect(client.config()).not.toHaveProperty('dataset')
209
- expect(client.config()).toEqual(
210
- expect.objectContaining({
211
- resource: {type: 'dataset', id: 'source-project.source-dataset'},
212
- }),
213
- )
214
- })
215
-
216
- it('should create resource when resource has array resourceId and be projectless', () => {
194
+ it('should create resource when media library resource is provided and be projectless', () => {
217
195
  const client = getClient(instance, {
218
196
  apiVersion: '2024-11-12',
219
197
  resource: {mediaLibraryId: 'media-lib-123'},
@@ -257,38 +235,24 @@ describe('clientStore', () => {
257
235
  )
258
236
  })
259
237
 
260
- it('should create projectless client when resource is provided, ignoring instance config', () => {
238
+ it('should transform dataset resource to project-based config for now', () => {
261
239
  const client = getClient(instance, {
262
240
  apiVersion: '2024-11-12',
263
241
  resource: {projectId: 'source-project', dataset: 'source-dataset'},
264
242
  })
265
243
 
266
- // Client should be projectless - resource takes precedence, instance config is ignored
267
- expect(client.config()).not.toHaveProperty('projectId')
268
- expect(client.config()).not.toHaveProperty('dataset')
269
- expect(client.config()).toEqual(
244
+ expect(vi.mocked(createClient)).toHaveBeenCalledWith(
270
245
  expect.objectContaining({
271
- resource: {type: 'dataset', id: 'source-project.source-dataset'},
246
+ projectId: 'source-project',
247
+ dataset: 'source-dataset',
272
248
  }),
273
249
  )
274
- })
275
-
276
- it('should warn when both resource 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
- resource: {projectId: 'source-project', dataset: 'source-dataset'},
281
- projectId: 'explicit-project',
282
- dataset: 'explicit-dataset',
283
- })
284
-
285
- expect(consoleSpy).toHaveBeenCalledWith(
286
- 'Both resource and explicit projectId/dataset are provided. The resource will be used and projectId/dataset will be ignored.',
250
+ expect(client.config()).toEqual(
251
+ expect.objectContaining({
252
+ projectId: 'source-project',
253
+ dataset: 'source-dataset',
254
+ }),
287
255
  )
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
256
  })
293
257
 
294
258
  it('should create different clients for different resources', () => {
@@ -178,24 +178,23 @@ export const getClient = bindActionGlobally(
178
178
 
179
179
  const tokenFromState = state.get().token
180
180
  const {clients, authMethod} = state.get()
181
+ let projectId = options.projectId ?? instance.config.projectId
182
+ let dataset = options.dataset ?? instance.config.dataset
181
183
 
182
184
  let resource: ClientConfig['resource'] | undefined
183
185
 
184
186
  if (options.resource) {
185
- if (isDatasetResource(options.resource)) {
186
- resource = {
187
- type: 'dataset',
188
- id: `${options.resource.projectId}.${options.resource.dataset}`,
189
- }
190
- } else if (isMediaLibraryResource(options.resource)) {
187
+ if (isMediaLibraryResource(options.resource)) {
191
188
  resource = {type: 'media-library', id: options.resource.mediaLibraryId}
192
189
  } else if (isCanvasResource(options.resource)) {
193
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
200
  const effectiveOptions: ClientConfig & {apiVersion: string} = {
@@ -213,12 +212,6 @@ export const getClient = bindActionGlobally(
213
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 resource and explicit projectId/dataset are provided. The resource will be used and projectId/dataset will be ignored.',
220
- )
221
- }
222
215
  delete effectiveOptions.projectId
223
216
  delete effectiveOptions.dataset
224
217
  }
@@ -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
  })
@@ -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
  })
@@ -125,7 +125,6 @@ export interface DocumentHandle<
125
125
  export interface SanityConfig extends DatasetHandle, PerspectiveHandle {
126
126
  /**
127
127
  * Authentication configuration for the instance
128
- * @remarks Merged with parent configurations when using createChild
129
128
  */
130
129
  auth?: AuthConfig
131
130
  /**
@@ -59,7 +59,7 @@ import {
59
59
  type DocumentPermissionsResult,
60
60
  type Grant,
61
61
  } from './permissions'
62
- import {ActionError} from './processActions'
62
+ import {ActionError} from './processActions/processActions'
63
63
  import {
64
64
  type AppliedTransaction,
65
65
  applyFirstQueuedTransaction,
@@ -396,8 +396,7 @@ const subscribeToAppliedAndSubmitNextTransaction = ({
396
396
  withLatestFrom(
397
397
  getClientState(instance, {
398
398
  apiVersion: API_VERSION,
399
- // TODO: remove in v3 when we're ready for everything to be queried via resource
400
- resource: resource && !isDatasetResource(resource) ? resource : undefined,
399
+ resource,
401
400
  }).observable,
402
401
  ),
403
402
  concatMap(([outgoing, client]) => {
@@ -520,11 +519,7 @@ const subscribeToClientAndFetchDatasetAcl = ({
520
519
  state,
521
520
  key: {resource},
522
521
  }: StoreContext<DocumentStoreState, BoundResourceKey>) => {
523
- const clientOptions: ClientOptions = {apiVersion: API_VERSION}
524
- // TODO: remove in v3 when we're ready for everything to be queried via resource
525
- if (resource && !isDatasetResource(resource)) {
526
- clientOptions.resource = resource
527
- }
522
+ const clientOptions: ClientOptions = {apiVersion: API_VERSION, resource}
528
523
 
529
524
  let uri: string
530
525
  if (resource && isDatasetResource(resource)) {
@@ -7,7 +7,7 @@ import {isReleasePerspective} from '../releases/utils/isReleasePerspective'
7
7
  import {type SelectorContext} from '../store/createStateSourceAction'
8
8
  import {MultiKeyWeakMap} from '../utils/MultiKeyWeakMap'
9
9
  import {type DocumentAction} from './actions'
10
- import {ActionError, PermissionActionError, processActions} from './processActions'
10
+ import {ActionError, PermissionActionError, processActions} from './processActions/processActions'
11
11
  import {type DocumentSet} from './processMutations'
12
12
  import {type SyncTransactionState} from './reducers'
13
13