@sanity/sdk 2.8.0 → 2.9.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 (92) hide show
  1. package/dist/_chunks-dts/utils.d.ts +2396 -0
  2. package/dist/_chunks-es/_internal.js +129 -0
  3. package/dist/_chunks-es/_internal.js.map +1 -0
  4. package/dist/_chunks-es/createGroqSearchFilter.js +1460 -0
  5. package/dist/_chunks-es/createGroqSearchFilter.js.map +1 -0
  6. package/dist/_chunks-es/telemetryManager.js +87 -0
  7. package/dist/_chunks-es/telemetryManager.js.map +1 -0
  8. package/dist/_chunks-es/version.js +7 -0
  9. package/dist/_chunks-es/version.js.map +1 -0
  10. package/dist/_exports/_internal.d.ts +64 -0
  11. package/dist/_exports/_internal.js +20 -0
  12. package/dist/_exports/_internal.js.map +1 -0
  13. package/dist/index.d.ts +2 -2343
  14. package/dist/index.js +383 -1777
  15. package/dist/index.js.map +1 -1
  16. package/package.json +11 -4
  17. package/src/_exports/_internal.ts +14 -0
  18. package/src/_exports/index.ts +10 -1
  19. package/src/auth/authStore.test.ts +150 -1
  20. package/src/auth/authStore.ts +11 -11
  21. package/src/auth/dashboardAuth.ts +2 -2
  22. package/src/auth/handleAuthCallback.ts +9 -3
  23. package/src/auth/logout.test.ts +1 -1
  24. package/src/auth/logout.ts +1 -1
  25. package/src/auth/refreshStampedToken.test.ts +118 -1
  26. package/src/auth/refreshStampedToken.ts +3 -2
  27. package/src/auth/standaloneAuth.ts +9 -3
  28. package/src/auth/studioAuth.ts +34 -7
  29. package/src/auth/studioModeAuth.ts +2 -1
  30. package/src/auth/subscribeToStateAndFetchCurrentUser.test.ts +10 -2
  31. package/src/auth/subscribeToStateAndFetchCurrentUser.ts +5 -1
  32. package/src/auth/subscribeToStorageEventsAndSetToken.ts +2 -2
  33. package/src/auth/utils.ts +33 -0
  34. package/src/client/clientStore.test.ts +14 -0
  35. package/src/client/clientStore.ts +2 -1
  36. package/src/comlink/node/getNodeState.ts +2 -1
  37. package/src/config/sanityConfig.ts +6 -0
  38. package/src/document/actions.ts +18 -11
  39. package/src/document/applyDocumentActions.test.ts +7 -6
  40. package/src/document/applyDocumentActions.ts +10 -4
  41. package/src/document/documentStore.test.ts +536 -188
  42. package/src/document/documentStore.ts +142 -76
  43. package/src/document/events.ts +7 -2
  44. package/src/document/permissions.test.ts +18 -16
  45. package/src/document/permissions.ts +35 -11
  46. package/src/document/processActions.test.ts +359 -32
  47. package/src/document/processActions.ts +104 -76
  48. package/src/document/reducers.test.ts +117 -29
  49. package/src/document/reducers.ts +43 -36
  50. package/src/document/sharedListener.ts +16 -6
  51. package/src/document/util.ts +14 -0
  52. package/src/favorites/favorites.test.ts +9 -2
  53. package/src/presence/bifurTransport.ts +6 -1
  54. package/src/preview/getPreviewState.test.ts +115 -98
  55. package/src/preview/getPreviewState.ts +38 -60
  56. package/src/preview/previewProjectionUtils.test.ts +179 -0
  57. package/src/preview/previewProjectionUtils.ts +93 -0
  58. package/src/preview/resolvePreview.test.ts +42 -25
  59. package/src/preview/resolvePreview.ts +29 -10
  60. package/src/preview/{previewStore.ts → types.ts} +8 -17
  61. package/src/projection/getProjectionState.test.ts +16 -16
  62. package/src/projection/getProjectionState.ts +2 -1
  63. package/src/projection/projectionQuery.ts +2 -3
  64. package/src/projection/types.ts +1 -1
  65. package/src/query/queryStore.ts +2 -1
  66. package/src/releases/getPerspectiveState.ts +7 -6
  67. package/src/releases/releasesStore.test.ts +20 -5
  68. package/src/releases/releasesStore.ts +20 -8
  69. package/src/store/createStateSourceAction.test.ts +62 -0
  70. package/src/store/createStateSourceAction.ts +34 -39
  71. package/src/telemetry/__telemetry__/sdk.telemetry.ts +42 -0
  72. package/src/telemetry/devMode.test.ts +52 -0
  73. package/src/telemetry/devMode.ts +40 -0
  74. package/src/telemetry/initTelemetry.test.ts +225 -0
  75. package/src/telemetry/initTelemetry.ts +205 -0
  76. package/src/telemetry/telemetryManager.test.ts +263 -0
  77. package/src/telemetry/telemetryManager.ts +187 -0
  78. package/src/users/usersStore.test.ts +1 -0
  79. package/src/users/usersStore.ts +5 -1
  80. package/src/utils/createFetcherStore.test.ts +6 -4
  81. package/src/utils/createFetcherStore.ts +2 -1
  82. package/src/utils/getStagingApiHost.test.ts +21 -0
  83. package/src/utils/getStagingApiHost.ts +14 -0
  84. package/src/utils/ids.test.ts +1 -29
  85. package/src/utils/ids.ts +0 -10
  86. package/src/utils/setCleanupTimeout.ts +24 -0
  87. package/src/preview/previewQuery.test.ts +0 -236
  88. package/src/preview/previewQuery.ts +0 -153
  89. package/src/preview/previewStore.test.ts +0 -36
  90. package/src/preview/subscribeToStateAndFetchBatches.test.ts +0 -221
  91. package/src/preview/subscribeToStateAndFetchBatches.ts +0 -112
  92. package/src/preview/util.ts +0 -13
@@ -9,7 +9,7 @@ import {refreshStampedToken} from './refreshStampedToken'
9
9
  import {checkForCookieAuth, getStudioTokenFromLocalStorage} from './studioModeAuth'
10
10
  import {subscribeToStateAndFetchCurrentUser} from './subscribeToStateAndFetchCurrentUser'
11
11
  import {subscribeToStorageEventsAndSetToken} from './subscribeToStorageEventsAndSetToken'
12
- import {getDefaultStorage} from './utils'
12
+ import {createLoggedInAuthState, getDefaultStorage} from './utils'
13
13
 
14
14
  /**
15
15
  * Resolves the initial auth state for Studio mode.
@@ -55,7 +55,7 @@ export function getStudioInitialState(options: AuthStrategyOptions): AuthStrateg
55
55
 
56
56
  if (providedToken) {
57
57
  return {
58
- authState: {type: AuthStateType.LOGGED_IN, token: providedToken, currentUser: null},
58
+ authState: createLoggedInAuthState(providedToken, null),
59
59
  storageKey: studioStorageKey,
60
60
  storageArea,
61
61
  authMethod,
@@ -65,7 +65,7 @@ export function getStudioInitialState(options: AuthStrategyOptions): AuthStrateg
65
65
 
66
66
  if (token) {
67
67
  return {
68
- authState: {type: AuthStateType.LOGGED_IN, token, currentUser: null},
68
+ authState: createLoggedInAuthState(token, null),
69
69
  storageKey: studioStorageKey,
70
70
  storageArea,
71
71
  authMethod: 'localstorage',
@@ -115,14 +115,29 @@ export function initializeStudioAuth(
115
115
 
116
116
  /**
117
117
  * Subscribe to a reactive token source from the Studio workspace.
118
- * The Studio is the single authority for auth — the SDK does not run
119
- * its own token refresher or cookie auth checks.
118
+ *
119
+ * When the token source emits a non-null token, the SDK uses it directly.
120
+ * When it emits `null`, the behavior depends on the `authenticated` flag
121
+ * from the Studio's workspace config:
122
+ *
123
+ * - `authenticated: true` — the Studio has already verified the user is
124
+ * logged in (e.g. via cookie auth). The SDK treats the null token as
125
+ * cookie-based auth and stays in the LOGGED_IN state.
126
+ *
127
+ * - `authenticated` absent/false — the user is genuinely not authenticated;
128
+ * transition to LOGGED_OUT.
129
+ *
130
+ * No async cookie probing is needed here because this code path only runs
131
+ * when a Studio provides SDKStudioContext, and the Studio's Workspace type
132
+ * always includes `authenticated`. The async `checkForCookieAuth` fallback
133
+ * remains in `initializeWithFallback` for the non-Studio path.
120
134
  */
121
135
  function initializeWithTokenSource(
122
136
  context: StoreContext<AuthStoreState>,
123
137
  tokenSource: TokenSource,
124
138
  ): {dispose: () => void; tokenRefresherStarted: boolean} {
125
139
  const subscriptions: Subscription[] = []
140
+ const studioAuthenticated = context.instance.config.studio?.authenticated === true
126
141
 
127
142
  // Subscribe to the current user fetcher — runs whenever auth state changes
128
143
  subscriptions.push(subscribeToStateAndFetchCurrentUser(context, {useProjectHostname: true}))
@@ -132,11 +147,23 @@ function initializeWithTokenSource(
132
147
  next: (token) => {
133
148
  const {state} = context
134
149
  if (token) {
150
+ // Studio provided a real token — use it directly
135
151
  state.set('studioTokenSource', (prev) => ({
136
152
  options: {...prev.options, authMethod: undefined},
137
- authState: {type: AuthStateType.LOGGED_IN, token, currentUser: null},
153
+ authState: createLoggedInAuthState(token, null),
154
+ }))
155
+ } else if (studioAuthenticated) {
156
+ // The Studio says the user is authenticated — null token means
157
+ // cookie-based auth is in use. Stay logged in with cookie method.
158
+ state.set('studioTokenSourceCookieAuth', (prev) => ({
159
+ options: {...prev.options, authMethod: 'cookie'},
160
+ authState:
161
+ prev.authState.type === AuthStateType.LOGGED_IN
162
+ ? prev.authState
163
+ : createLoggedInAuthState('', null),
138
164
  }))
139
165
  } else {
166
+ // No token and Studio doesn't confirm authentication — logged out
140
167
  state.set('studioTokenSourceLoggedOut', (prev) => ({
141
168
  options: {...prev.options, authMethod: undefined},
142
169
  authState: {type: AuthStateType.LOGGED_OUT, isDestroyingSession: false},
@@ -193,7 +220,7 @@ function initializeWithFallback(
193
220
  authState:
194
221
  prev.authState.type === AuthStateType.LOGGED_IN
195
222
  ? prev.authState
196
- : {type: AuthStateType.LOGGED_IN, token: '', currentUser: null},
223
+ : createLoggedInAuthState('', null),
197
224
  }))
198
225
  })
199
226
  }
@@ -1,5 +1,6 @@
1
1
  import {type ClientConfig, type SanityClient} from '@sanity/client'
2
2
 
3
+ import {REQUEST_TAG_PREFIX} from './authConstants'
3
4
  import {getTokenFromStorage} from './utils'
4
5
 
5
6
  /**
@@ -25,7 +26,7 @@ export async function checkForCookieAuth(
25
26
  const client = clientFactory({
26
27
  projectId,
27
28
  useCdn: false,
28
- requestTagPrefix: 'sdk',
29
+ requestTagPrefix: REQUEST_TAG_PREFIX,
29
30
  timeout: COOKIE_AUTH_TIMEOUT_MS,
30
31
  })
31
32
  const user = await client.request({
@@ -40,7 +40,11 @@ describe('subscribeToStateAndFetchCurrentUser', () => {
40
40
  useProjectHostname: false,
41
41
  useCdn: false,
42
42
  })
43
- expect(mockRequest).toHaveBeenCalledWith({method: 'GET', uri: '/users/me'})
43
+ expect(mockRequest).toHaveBeenCalledWith({
44
+ method: 'GET',
45
+ uri: '/users/me',
46
+ tag: 'users.get-current',
47
+ })
44
48
 
45
49
  subscription.unsubscribe()
46
50
  })
@@ -76,7 +80,11 @@ describe('subscribeToStateAndFetchCurrentUser', () => {
76
80
  useProjectHostname: false,
77
81
  useCdn: false,
78
82
  })
79
- expect(mockRequest).toHaveBeenCalledWith({method: 'GET', uri: '/users/me'})
83
+ expect(mockRequest).toHaveBeenCalledWith({
84
+ method: 'GET',
85
+ uri: '/users/me',
86
+ tag: 'users.get-current',
87
+ })
80
88
 
81
89
  subscription.unsubscribe()
82
90
  })
@@ -60,7 +60,11 @@ export const subscribeToStateAndFetchCurrentUser = (
60
60
  }),
61
61
  ),
62
62
  switchMap((client) =>
63
- client.observable.request<CurrentUser>({uri: '/users/me', method: 'GET'}),
63
+ client.observable.request<CurrentUser>({
64
+ uri: '/users/me',
65
+ method: 'GET',
66
+ tag: 'users.get-current',
67
+ }),
64
68
  ),
65
69
  )
66
70
 
@@ -3,7 +3,7 @@ import {defer, distinctUntilChanged, filter, map, type Subscription} from 'rxjs'
3
3
  import {type StoreContext} from '../store/defineStore'
4
4
  import {AuthStateType} from './authStateType'
5
5
  import {type AuthStoreState} from './authStore'
6
- import {getStorageEvents, getTokenFromStorage} from './utils'
6
+ import {createLoggedInAuthState, getStorageEvents, getTokenFromStorage} from './utils'
7
7
 
8
8
  export const subscribeToStorageEventsAndSetToken = ({
9
9
  state,
@@ -22,7 +22,7 @@ export const subscribeToStorageEventsAndSetToken = ({
22
22
  return tokenFromStorage$.subscribe((token) => {
23
23
  state.set('updateTokenFromStorageEvent', {
24
24
  authState: token
25
- ? {type: AuthStateType.LOGGED_IN, token, currentUser: null}
25
+ ? createLoggedInAuthState(token, null)
26
26
  : {type: AuthStateType.LOGGED_OUT, isDestroyingSession: false},
27
27
  })
28
28
  })
package/src/auth/utils.ts CHANGED
@@ -1,7 +1,40 @@
1
1
  import {type ClientError} from '@sanity/client'
2
+ import {type CurrentUser} from '@sanity/types'
2
3
  import {EMPTY, fromEvent, Observable} from 'rxjs'
3
4
 
4
5
  import {AUTH_CODE_PARAM, DEFAULT_BASE} from './authConstants'
6
+ import {AuthStateType} from './authStateType'
7
+ import {type LoggedInAuthState} from './authStore'
8
+
9
+ /**
10
+ * Creates a properly initialized {@link LoggedInAuthState}.
11
+ *
12
+ * For stamped tokens (containing `"-st"`), `lastTokenRefresh` is set to
13
+ * `Date.now()` so that the visibility-change handler in
14
+ * {@link refreshStampedToken} does not trigger an unnecessary refresh the
15
+ * first time the tab becomes visible.
16
+ *
17
+ * @param token - The auth token.
18
+ * @param currentUser - The current user, or `null` if not yet fetched.
19
+ * @param existingLastTokenRefresh - An existing timestamp to preserve
20
+ * (e.g. when updating a token while keeping the previous refresh time).
21
+ * @internal
22
+ */
23
+ export function createLoggedInAuthState(
24
+ token: string,
25
+ currentUser: CurrentUser | null,
26
+ existingLastTokenRefresh?: number,
27
+ ): LoggedInAuthState {
28
+ const isStampedToken = token.includes('-st')
29
+ const lastTokenRefresh = existingLastTokenRefresh ?? (isStampedToken ? Date.now() : undefined)
30
+
31
+ return {
32
+ type: AuthStateType.LOGGED_IN,
33
+ token,
34
+ currentUser,
35
+ ...(lastTokenRefresh !== undefined && {lastTokenRefresh}),
36
+ }
37
+ }
5
38
 
6
39
  export function getAuthCode(callbackUrl: string | undefined, locationHref: string): string | null {
7
40
  const loc = new URL(locationHref, DEFAULT_BASE)
@@ -68,6 +68,20 @@ describe('clientStore', () => {
68
68
  })
69
69
  })
70
70
 
71
+ it('should pass staging apiHost when __SANITY_STAGING__ is true and no explicit apiHost', () => {
72
+ vi.stubGlobal('__SANITY_STAGING__', true)
73
+
74
+ getClient(instance, {apiVersion: '2024-11-12'})
75
+
76
+ expect(vi.mocked(createClient)).toHaveBeenCalledWith(
77
+ expect.objectContaining({
78
+ apiHost: 'https://api.sanity.work',
79
+ }),
80
+ )
81
+
82
+ vi.unstubAllGlobals()
83
+ })
84
+
71
85
  it('should throw when using disallowed configuration keys', () => {
72
86
  expect(() =>
73
87
  getClient(instance, {
@@ -11,6 +11,7 @@ import {
11
11
  import {bindActionGlobally} from '../store/createActionBinder'
12
12
  import {createStateSourceAction} from '../store/createStateSourceAction'
13
13
  import {defineStore, type StoreContext} from '../store/defineStore'
14
+ import {getStagingApiHost} from '../utils/getStagingApiHost'
14
15
 
15
16
  const DEFAULT_API_VERSION = '2024-11-12'
16
17
  const DEFAULT_REQUEST_TAG_PREFIX = 'sanity.sdk'
@@ -195,7 +196,7 @@ export const getClient = bindActionGlobally(
195
196
 
196
197
  const projectId = options.projectId ?? instance.config.projectId
197
198
  const dataset = options.dataset ?? instance.config.dataset
198
- const apiHost = options.apiHost ?? instance.config.auth?.apiHost
199
+ const apiHost = options.apiHost ?? instance.config.auth?.apiHost ?? getStagingApiHost()
199
200
 
200
201
  const effectiveOptions: ClientOptions = {
201
202
  ...DEFAULT_CLIENT_CONFIG,
@@ -3,6 +3,7 @@ import {createSelector} from 'reselect'
3
3
 
4
4
  import {bindActionGlobally} from '../../store/createActionBinder'
5
5
  import {createStateSourceAction, type SelectorContext} from '../../store/createStateSourceAction'
6
+ import {setCleanupTimeout} from '../../utils/setCleanupTimeout'
6
7
  import {type FrameMessage, type WindowMessage} from '../types'
7
8
  import {
8
9
  type ComlinkNodeState,
@@ -57,7 +58,7 @@ export const getNodeState = bindActionGlobally(
57
58
  subs.add(subscriberId)
58
59
 
59
60
  return () => {
60
- setTimeout(() => {
61
+ setCleanupTimeout(() => {
61
62
  const activeSubs = state.get().subscriptions.get(nodeName)
62
63
  if (activeSubs) {
63
64
  activeSubs.delete(subscriberId)
@@ -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
  /**
@@ -4,7 +4,7 @@ import {type PatchMutation, type PatchOperations} from '@sanity/types'
4
4
  import {type SanityDocument} from 'groq'
5
5
 
6
6
  import {type DocumentHandle, type DocumentTypeHandle} from '../config/sanityConfig'
7
- import {getPublishedId} from '../utils/ids'
7
+ import {getEffectiveDocumentId} from './util'
8
8
 
9
9
  const isSanityMutatePatch = (value: unknown): value is SanityMutatePatchMutation => {
10
10
  if (typeof value !== 'object' || !value) return false
@@ -140,12 +140,15 @@ export function createDocument<
140
140
  >
141
141
  >,
142
142
  ): CreateDocumentAction<TDocumentType, TDataset, TProjectId> {
143
+ // users may pass in an explicit documentId -- make sure we format it correctly for the action
144
+ let effectiveDocumentId
145
+ if (typeof doc.documentId === 'string') {
146
+ effectiveDocumentId = getEffectiveDocumentId({...doc, documentId: doc.documentId})
147
+ }
143
148
  return {
144
149
  type: 'document.create',
145
150
  ...doc,
146
- ...(doc.documentId && {
147
- documentId: doc.liveEdit ? doc.documentId : getPublishedId(doc.documentId),
148
- }),
151
+ ...(effectiveDocumentId && {documentId: effectiveDocumentId}),
149
152
  ...(initialValue && {initialValue}),
150
153
  }
151
154
  }
@@ -163,10 +166,11 @@ export function deleteDocument<
163
166
  >(
164
167
  doc: DocumentHandle<TDocumentType, TDataset, TProjectId>,
165
168
  ): DeleteDocumentAction<TDocumentType, TDataset, TProjectId> {
169
+ const effectiveDocumentId = getEffectiveDocumentId(doc)
166
170
  return {
167
171
  type: 'document.delete',
168
172
  ...doc,
169
- documentId: doc.liveEdit ? doc.documentId : getPublishedId(doc.documentId),
173
+ documentId: effectiveDocumentId,
170
174
  }
171
175
  }
172
176
 
@@ -230,14 +234,14 @@ export function editDocument<
230
234
  doc: DocumentHandle<TDocumentType, TDataset, TProjectId>,
231
235
  patches?: PatchOperations | PatchOperations[] | SanityMutatePatchMutation,
232
236
  ): EditDocumentAction<TDocumentType, TDataset, TProjectId> {
233
- const documentId = doc.liveEdit ? doc.documentId : getPublishedId(doc.documentId)
237
+ const effectiveDocumentId = getEffectiveDocumentId(doc)
234
238
 
235
239
  if (isSanityMutatePatch(patches)) {
236
240
  const converted = convertSanityMutatePatch(patches) ?? []
237
241
  return {
238
242
  ...doc,
239
243
  type: 'document.edit',
240
- documentId,
244
+ documentId: effectiveDocumentId,
241
245
  patches: converted,
242
246
  }
243
247
  }
@@ -245,7 +249,7 @@ export function editDocument<
245
249
  return {
246
250
  ...doc,
247
251
  type: 'document.edit',
248
- documentId,
252
+ documentId: effectiveDocumentId,
249
253
  ...(patches && {patches: Array.isArray(patches) ? patches : [patches]}),
250
254
  }
251
255
  }
@@ -263,10 +267,11 @@ export function publishDocument<
263
267
  >(
264
268
  doc: DocumentHandle<TDocumentType, TDataset, TProjectId>,
265
269
  ): PublishDocumentAction<TDocumentType, TDataset, TProjectId> {
270
+ const effectiveDocumentId = getEffectiveDocumentId(doc)
266
271
  return {
267
272
  type: 'document.publish',
268
273
  ...doc,
269
- documentId: doc.liveEdit ? doc.documentId : getPublishedId(doc.documentId),
274
+ documentId: effectiveDocumentId,
270
275
  }
271
276
  }
272
277
 
@@ -283,10 +288,11 @@ export function unpublishDocument<
283
288
  >(
284
289
  doc: DocumentHandle<TDocumentType, TDataset, TProjectId>,
285
290
  ): UnpublishDocumentAction<TDocumentType, TDataset, TProjectId> {
291
+ const effectiveDocumentId = getEffectiveDocumentId(doc)
286
292
  return {
287
293
  type: 'document.unpublish',
288
294
  ...doc,
289
- documentId: doc.liveEdit ? doc.documentId : getPublishedId(doc.documentId),
295
+ documentId: effectiveDocumentId,
290
296
  }
291
297
  }
292
298
 
@@ -303,9 +309,10 @@ export function discardDocument<
303
309
  >(
304
310
  doc: DocumentHandle<TDocumentType, TDataset, TProjectId>,
305
311
  ): DiscardDocumentAction<TDocumentType, TDataset, TProjectId> {
312
+ const effectiveDocumentId = getEffectiveDocumentId(doc)
306
313
  return {
307
314
  type: 'document.discard',
308
315
  ...doc,
309
- documentId: doc.liveEdit ? doc.documentId : getPublishedId(doc.documentId),
316
+ documentId: effectiveDocumentId,
310
317
  }
311
318
  }
@@ -1,11 +1,9 @@
1
- // applyDocumentActions.test.ts
2
1
  import {type SanityDocument} from '@sanity/types'
3
2
  import {Subject} from 'rxjs'
4
3
  import {describe, expect, it} from 'vitest'
5
4
 
6
- import {bindActionByDataset} from '../store/createActionBinder'
5
+ import {bindActionBySource} from '../store/createActionBinder'
7
6
  import {createSanityInstance, type SanityInstance} from '../store/createSanityInstance'
8
- import {} from '../store/createStateSourceAction'
9
7
  import {createStoreState, type StoreState} from '../store/createStoreState'
10
8
  import {type DocumentAction} from './actions'
11
9
  import {type DocumentStoreState} from './documentStore'
@@ -14,7 +12,7 @@ import {type AppliedTransaction, type OutgoingTransaction} from './reducers'
14
12
 
15
13
  vi.mock('../store/createActionBinder', async (importOriginal) => ({
16
14
  ...(await importOriginal<typeof import('../store/createActionBinder')>()),
17
- bindActionByDataset: vi.fn(),
15
+ bindActionBySource: vi.fn(),
18
16
  }))
19
17
 
20
18
  type TestState = Pick<
@@ -48,9 +46,9 @@ describe('applyDocumentActions', () => {
48
46
  }
49
47
  state = createStoreState(initialState)
50
48
  instance = createSanityInstance({projectId: 'p', dataset: 'd'})
51
- const key = {name: 'p.d', projectId: 'p', dataset: 'd'}
49
+ const key = {name: 'p.d', source: {projectId: 'p', dataset: 'd'}}
52
50
 
53
- vi.mocked(bindActionByDataset).mockImplementation(
51
+ vi.mocked(bindActionBySource).mockImplementation(
54
52
  (_storeDef, action) => (instanceParam: SanityInstance, options) =>
55
53
  action({instance: instanceParam, state, key}, options),
56
54
  )
@@ -75,6 +73,7 @@ describe('applyDocumentActions', () => {
75
73
  const applyPromise = applyDocumentActions(instance, {
76
74
  actions: [action],
77
75
  transactionId: 'txn-success',
76
+ source: {projectId: 'p', dataset: 'd'},
78
77
  })
79
78
 
80
79
  const appliedTx: AppliedTransaction = {
@@ -132,6 +131,7 @@ describe('applyDocumentActions', () => {
132
131
  const applyPromise = applyDocumentActions(instance, {
133
132
  actions: [action],
134
133
  transactionId: 'txn-error',
134
+ source: {projectId: 'p', dataset: 'd'},
135
135
  })
136
136
 
137
137
  const errorEvent: DocumentEvent = {
@@ -165,6 +165,7 @@ describe('applyDocumentActions', () => {
165
165
  const applyPromise = applyDocumentActions(childInstance, {
166
166
  actions: [action],
167
167
  transactionId: 'txn-child-match',
168
+ source: {projectId: 'p', dataset: 'd'},
168
169
  })
169
170
 
170
171
  // Simulate an applied transaction on the parent's instance
@@ -1,12 +1,13 @@
1
- import {type SanityClient} from '@sanity/client'
2
1
  import {type SanityDocument} from 'groq'
3
2
  import {distinctUntilChanged, filter, first, firstValueFrom, map, race} from 'rxjs'
4
3
 
5
- import {bindActionByDataset} from '../store/createActionBinder'
4
+ import {type DocumentSource} from '../config/sanityConfig'
5
+ import {bindActionBySource} from '../store/createActionBinder'
6
6
  import {type SanityInstance} from '../store/createSanityInstance'
7
7
  import {type StoreContext} from '../store/defineStore'
8
8
  import {type DocumentAction} from './actions'
9
9
  import {documentStore, type DocumentStoreState} from './documentStore'
10
+ import {type DocumentTransactionSubmissionResult} from './events'
10
11
  import {type DocumentSet} from './processMutations'
11
12
  import {type AppliedTransaction, type QueuedTransaction, queueTransaction} from './reducers'
12
13
 
@@ -19,7 +20,7 @@ export interface ActionsResult<TDocument extends SanityDocument = SanityDocument
19
20
  appeared: string[]
20
21
  updated: string[]
21
22
  disappeared: string[]
22
- submitted: () => ReturnType<SanityClient['action']>
23
+ submitted: () => Promise<DocumentTransactionSubmissionResult>
23
24
  }
24
25
 
25
26
  /** @beta */
@@ -29,6 +30,11 @@ export interface ApplyDocumentActionsOptions {
29
30
  */
30
31
  actions: DocumentAction[]
31
32
 
33
+ /**
34
+ * The source to which the documents being acted on belong.
35
+ */
36
+ source?: DocumentSource
37
+
32
38
  /**
33
39
  * Optionally provide an ID to be used as this transaction ID
34
40
  */
@@ -61,7 +67,7 @@ export function applyDocumentActions(
61
67
  return boundApplyDocumentActions(...args)
62
68
  }
63
69
 
64
- const boundApplyDocumentActions = bindActionByDataset(documentStore, _applyDocumentActions)
70
+ const boundApplyDocumentActions = bindActionBySource(documentStore, _applyDocumentActions)
65
71
 
66
72
  /** @internal */
67
73
  async function _applyDocumentActions(