@sanity/sdk 3.0.0 → 3.2.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 (132) hide show
  1. package/dist/_exports/_internal.d.ts +72 -19
  2. package/dist/_exports/_internal.d.ts.map +1 -1
  3. package/dist/_exports/_internal.js +130 -11
  4. package/dist/_exports/_internal.js.map +1 -1
  5. package/dist/_exports/agent.d.ts +7 -7
  6. package/dist/_exports/agent.d.ts.map +1 -1
  7. package/dist/_exports/agent.js +1 -1
  8. package/dist/_exports/comlink.d.ts +9 -9
  9. package/dist/_exports/comlink.d.ts.map +1 -1
  10. package/dist/_exports/comlink.js +2 -2
  11. package/dist/_exports/dashboard.d.ts +2 -0
  12. package/dist/_exports/dashboard.js +2 -0
  13. package/dist/{authStore-C0o1NOxW.d.ts → authStore-BO2vch3I.d.ts} +516 -63
  14. package/dist/authStore-BO2vch3I.d.ts.map +1 -0
  15. package/dist/{authStore-CtE9bLHW.js → clientStore-BI4D8HhJ.js} +390 -15
  16. package/dist/clientStore-BI4D8HhJ.js.map +1 -0
  17. package/dist/{createSanityInstance-DUI1bGIg.d.ts → createSanityInstance-BQIqyGLX.d.ts} +17 -2
  18. package/dist/createSanityInstance-BQIqyGLX.d.ts.map +1 -0
  19. package/dist/{createStateSourceAction-BOKtqFNm.js → createStateSourceAction-DdAhEfnZ.js} +7 -3
  20. package/dist/createStateSourceAction-DdAhEfnZ.js.map +1 -0
  21. package/dist/defineProperty-Bho9Ngsi.js +31 -0
  22. package/dist/{getNodeState-CR3__B2D.js → getNodeState-B4nG0sjm.js} +2 -2
  23. package/dist/{getNodeState-CR3__B2D.js.map → getNodeState-B4nG0sjm.js.map} +1 -1
  24. package/dist/index.d.ts +3 -3
  25. package/dist/index.js +370 -90
  26. package/dist/index.js.map +1 -1
  27. package/dist/topicStore-Ct7IFNtY.js +565 -0
  28. package/dist/topicStore-Ct7IFNtY.js.map +1 -0
  29. package/dist/topicStore-D0WqhQGF.d.ts +435 -0
  30. package/dist/topicStore-D0WqhQGF.d.ts.map +1 -0
  31. package/dist/{queryStore-C7yMHPUI.js → version-BcRmCiYi.js} +48 -16
  32. package/dist/version-BcRmCiYi.js.map +1 -0
  33. package/package.json +19 -18
  34. package/src/_exports/_internal.ts +13 -0
  35. package/src/_exports/dashboard.ts +36 -0
  36. package/src/_exports/index.ts +17 -1
  37. package/src/access/checkPermissions.test.ts +1 -1
  38. package/src/access/checkPermissions.ts +1 -1
  39. package/src/applications/applications.test-d.ts +28 -4
  40. package/src/applications/applications.test.ts +4 -4
  41. package/src/applications/applications.ts +43 -18
  42. package/src/auth/authMode.test.ts +24 -0
  43. package/src/auth/authMode.ts +8 -3
  44. package/src/auth/authStore.ts +12 -0
  45. package/src/auth/authStrategy.ts +2 -0
  46. package/src/auth/handleAuthCallback.test.ts +2 -2
  47. package/src/auth/handleAuthCallback.ts +1 -1
  48. package/src/auth/logout.test.ts +46 -1
  49. package/src/auth/logout.ts +2 -1
  50. package/src/auth/oauth/oauthActions.test.ts +457 -0
  51. package/src/auth/oauth/oauthActions.ts +389 -0
  52. package/src/auth/oauth/oauthAuth.test.ts +206 -0
  53. package/src/auth/oauth/oauthAuth.ts +168 -0
  54. package/src/auth/oauth/pkce.test.ts +44 -0
  55. package/src/auth/oauth/pkce.ts +60 -0
  56. package/src/auth/oauth/types.ts +12 -0
  57. package/src/auth/refreshStampedToken.ts +1 -1
  58. package/src/auth/studioModeAuth.test.ts +1 -1
  59. package/src/auth/studioModeAuth.ts +1 -1
  60. package/src/auth/subscribeToStateAndFetchCurrentUser.test.ts +2 -2
  61. package/src/auth/subscribeToStateAndFetchCurrentUser.ts +1 -1
  62. package/src/comments/addonDatasetStore.test.ts +2 -2
  63. package/src/comments/addonDatasetStore.ts +2 -2
  64. package/src/comments/commentActions.ts +6 -5
  65. package/src/comments/commentsStore.ts +4 -4
  66. package/src/config/authConfig.ts +18 -0
  67. package/src/dashboard/messageBus/__fixtures__/test-topics.ts +18 -0
  68. package/src/dashboard/messageBus/bus.test.ts +1033 -0
  69. package/src/dashboard/messageBus/bus.ts +1246 -0
  70. package/src/dashboard/messageBus/store.test.ts +142 -0
  71. package/src/dashboard/messageBus/store.ts +74 -0
  72. package/src/dashboard/messageBus/topicStore.test.ts +172 -0
  73. package/src/dashboard/messageBus/topicStore.ts +184 -0
  74. package/src/dashboard/messageBus/topics.test.ts +71 -0
  75. package/src/dashboard/messageBus/topics.ts +260 -0
  76. package/src/document/actions.ts +3 -3
  77. package/src/document/applyDocumentActions.ts +5 -9
  78. package/src/document/documentStore.concurrency.test.ts +2 -1
  79. package/src/document/documentStore.test.ts +19 -18
  80. package/src/document/documentStore.ts +17 -17
  81. package/src/document/permissions.ts +2 -1
  82. package/src/document/processActions.test.ts +2 -1
  83. package/src/document/processMutations.ts +61 -24
  84. package/src/document/reducers.ts +2 -2
  85. package/src/installations/installations.test-d.ts +3 -3
  86. package/src/installations/installations.test.ts +2 -2
  87. package/src/installations/installations.ts +4 -21
  88. package/src/organization/organization.test.ts +2 -2
  89. package/src/organization/organization.ts +1 -1
  90. package/src/organizations/organizations.test.ts +2 -2
  91. package/src/organizations/organizations.ts +1 -1
  92. package/src/presence/presenceStore.test.ts +15 -22
  93. package/src/presence/presenceStore.ts +3 -2
  94. package/src/project/project.test.ts +3 -3
  95. package/src/project/project.ts +1 -1
  96. package/src/projection/getProjectionState.test.ts +6 -4
  97. package/src/projection/getProjectionState.ts +4 -4
  98. package/src/projection/resolveProjection.ts +2 -2
  99. package/src/projects/projects.test.ts +2 -2
  100. package/src/projects/projects.ts +1 -1
  101. package/src/query/queryStore.ts +6 -6
  102. package/src/store/createSanityInstance.test.ts +1 -1
  103. package/src/store/createSanityInstance.ts +4 -4
  104. package/src/store/createStoreInstance.test.ts +0 -5
  105. package/src/telemetry/telemetryManager.test.ts +1 -1
  106. package/src/telemetry/telemetryManager.ts +2 -2
  107. package/src/typegen/defineProjection.test.ts +17 -0
  108. package/src/typegen/defineProjection.ts +19 -0
  109. package/src/typegen/groqCompat.ts +122 -0
  110. package/src/typegen/resolve.ts +37 -0
  111. package/src/users/usersStore.test.ts +1 -1
  112. package/src/users/usersStore.ts +5 -5
  113. package/src/utils/createGroqSearchFilter.test.ts +8 -0
  114. package/src/utils/createGroqSearchFilter.ts +6 -3
  115. package/src/utils/ids.test.ts +40 -15
  116. package/src/utils/ids.ts +47 -2
  117. package/dist/authStore-C0o1NOxW.d.ts.map +0 -1
  118. package/dist/authStore-CtE9bLHW.js.map +0 -1
  119. package/dist/clientStore-BtE2-q2i.js +0 -90
  120. package/dist/clientStore-BtE2-q2i.js.map +0 -1
  121. package/dist/createSanityInstance-DUI1bGIg.d.ts.map +0 -1
  122. package/dist/createStateSourceAction-BOKtqFNm.js.map +0 -1
  123. package/dist/getEnv-D5RGkQLu.js +0 -8
  124. package/dist/getEnv-D5RGkQLu.js.map +0 -1
  125. package/dist/logger-DAtJb5RQ.js +0 -173
  126. package/dist/logger-DAtJb5RQ.js.map +0 -1
  127. package/dist/queryStore-C7yMHPUI.js.map +0 -1
  128. package/dist/rolldown-runtime-vyAXikos.js +0 -9
  129. package/dist/telemetryManager-UIEiMtC0.js +0 -127
  130. package/dist/telemetryManager-UIEiMtC0.js.map +0 -1
  131. package/dist/version-D8RP_Q59.js +0 -9
  132. package/dist/version-D8RP_Q59.js.map +0 -1
@@ -0,0 +1,260 @@
1
+ import {type CurrentUser} from '@sanity/types'
2
+
3
+ import {type Application, type ApplicationInclude} from '../../applications/applications'
4
+ import {type OrganizationBase} from '../../organization/organization'
5
+
6
+ /**
7
+ * Identifies a module federation expose and the manifest that serves it.
8
+ * @public
9
+ */
10
+ export interface RemoteModuleRef {
11
+ readonly entry: string
12
+ readonly moduleId: string
13
+ readonly version: string
14
+ }
15
+
16
+ /**
17
+ * Application types that can receive configuration modules.
18
+ *
19
+ * The dashboard supplies this value over the message bus, so any string is accepted; known
20
+ * types are listed for autocomplete. `& {}` keeps the literal from collapsing into `string`.
21
+ * @public
22
+ */
23
+ export type ApplicationConfigAppType = 'media-library' | (string & {})
24
+
25
+ /**
26
+ * Identifies a configuration module for an application or application type.
27
+ * @public
28
+ */
29
+ export interface ApplicationConfig extends RemoteModuleRef {
30
+ readonly appId?: Application['id']
31
+ readonly appType: ApplicationConfigAppType
32
+ }
33
+
34
+ /**
35
+ * Declares a topic that stores and replays its current value.
36
+ * @public
37
+ */
38
+ export type StateTopicDef<T> = {kind: 'state'; value: T}
39
+
40
+ /**
41
+ * Declares a topic that delivers events and an optional reply.
42
+ * @public
43
+ */
44
+ export type EventTopicDef<P, R = never> = {
45
+ kind: 'event'
46
+ payload: P
47
+ reply?: R
48
+ }
49
+
50
+ /**
51
+ * Represents a successful topic value or a failed topic operation.
52
+ * @public
53
+ */
54
+ export type TopicResult<T> = {ok: true; value: T} | {ok: false}
55
+
56
+ /**
57
+ * Identifies a dashboard application and a route within it.
58
+ * @public
59
+ */
60
+ export type NavigationTarget = {
61
+ /** The application ID, or `null` for dashboard-level routes. */
62
+ appId: Application['id'] | null
63
+ /** The route path, including its query string and fragment. */
64
+ path: string
65
+ }
66
+
67
+ /**
68
+ * Describes the current dashboard location and an active navigation.
69
+ * @public
70
+ */
71
+ export type NavigationLocation = NavigationTarget & {
72
+ /** The active navigation, or `null` when navigation is idle. */
73
+ transition: {
74
+ /** Whether the navigation pushes or replaces browser history. */
75
+ navigationType: 'push' | 'replace'
76
+ /** The requested destination. */
77
+ to: NavigationTarget
78
+ } | null
79
+ }
80
+
81
+ /**
82
+ * Declares the topics provided by the dashboard.
83
+ * @public
84
+ */
85
+ export interface DashboardTopics {
86
+ /** The available application configuration modules. */
87
+ 'applications.config': StateTopicDef<ApplicationConfig[] | null>
88
+ /** The foreground application ID, or `null` on dashboard-level routes. */
89
+ 'applications.foreground': StateTopicDef<Application['id'] | null>
90
+ /** The dashboard applications available to the current user. */
91
+ 'applications.list': StateTopicDef<TopicResult<Application<ApplicationInclude>[]> | null>
92
+ /**
93
+ * The session token for the reading connection, or `null` while signed out. The host
94
+ * writes it to each connection separately, so one application never sees another's token.
95
+ */
96
+ 'auth.token': StateTopicDef<string | null>
97
+ /** Requests a dashboard session token. */
98
+ 'auth.token.refresh': EventTopicDef<void, string>
99
+ /** The current dashboard location and active navigation. */
100
+ 'navigation.location': StateTopicDef<NavigationLocation | null>
101
+ /**
102
+ * Requests navigation and replies when the location commits.
103
+ *
104
+ * `ok: false` reasons:
105
+ * - `not-navigable`: the URL cannot be handled by a dashboard application
106
+ * - `interrupted`: another navigation superseded the request
107
+ * - `failed`: the router rejected or did not commit the request
108
+ */
109
+ 'navigation.location.update': EventTopicDef<
110
+ {
111
+ url: string
112
+ history?: 'push' | 'replace'
113
+ },
114
+ {ok: true} | {ok: false; reason: 'not-navigable' | 'interrupted' | 'failed'}
115
+ >
116
+ /** The current organization, or `null` without an active organization. */
117
+ 'organizations.current': StateTopicDef<Pick<OrganizationBase, 'id' | 'name' | 'slug'> | null>
118
+ /** The open panel, its application, display mode, and optional width in pixels. */
119
+ 'panels.mode': StateTopicDef<TopicResult<
120
+ | {appId: string; name: string; mode: 'aside'; size?: number}
121
+ | {appId: string; name: string; mode: 'full'}
122
+ | null
123
+ > | null>
124
+ /** Opens, updates, resizes, or closes an application's panel. */
125
+ 'panels.mode.set': EventTopicDef<
126
+ {name: string; mode: 'aside' | 'full'} | {name: string; size: number} | null
127
+ >
128
+ /** The resolved dashboard color scheme. */
129
+ 'preferences.color-scheme': StateTopicDef<'light' | 'dark'>
130
+ /** Whether the dashboard dock is pinned open. */
131
+ 'preferences.dock-locked': StateTopicDef<boolean>
132
+ /** The signed-in user, or `null` while signed out. */
133
+ 'users.current': StateTopicDef<Pick<CurrentUser, 'id' | 'name' | 'email' | 'profileImage'> | null>
134
+ }
135
+
136
+ /**
137
+ * Declares every topic available through the message bus.
138
+ * @public
139
+ */
140
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type -- Declaration merging extends the SDK manifest.
141
+ export interface Topics extends DashboardTopics {}
142
+
143
+ /**
144
+ * Every declared topic name.
145
+ * @public
146
+ */
147
+ export type TopicName = keyof Topics
148
+
149
+ type StateTopicsOf<T> = {
150
+ [K in keyof T]: T[K] extends {kind: 'state'} ? K : never
151
+ }[keyof T]
152
+
153
+ /**
154
+ * Names of all declared state topics.
155
+ * @public
156
+ */
157
+ export type StateTopic = StateTopicsOf<Topics>
158
+
159
+ type TopicOwnership = {readonly type: 'same_app'} | {readonly type: 'any_app'}
160
+
161
+ // State is only ever written by the host, one connection at a time, so it carries no ownership.
162
+ type TopicManifestEntry<T> = T extends {kind: 'state'; value: infer V}
163
+ ? {readonly kind: 'state'; readonly seed: V | undefined}
164
+ : {readonly kind: 'event'; readonly ownership: TopicOwnership}
165
+
166
+ // The value a connection's copy of the topic starts with; `undefined` means unpublished.
167
+ const stateTopic = <const V>(seed: V) => ({kind: 'state', seed}) as const
168
+
169
+ // `same_app` restricts responding to the application that installed the bus.
170
+ const dashboardEvent = {kind: 'event', ownership: {type: 'same_app'}} as const
171
+
172
+ /**
173
+ * Defines the runtime kind, ownership, and initial value of dashboard topics.
174
+ * @internal
175
+ */
176
+ export const DASHBOARD_TOPIC_MANIFEST: {
177
+ readonly [K in keyof DashboardTopics]: TopicManifestEntry<DashboardTopics[K]>
178
+ } = {
179
+ 'applications.config': stateTopic(undefined),
180
+ 'applications.foreground': stateTopic(undefined),
181
+ 'applications.list': stateTopic(undefined),
182
+ 'auth.token': stateTopic(undefined),
183
+ 'auth.token.refresh': dashboardEvent,
184
+ 'navigation.location': stateTopic(undefined),
185
+ 'navigation.location.update': dashboardEvent,
186
+ 'organizations.current': stateTopic(undefined),
187
+ 'panels.mode': stateTopic({ok: true, value: null}),
188
+ 'panels.mode.set': dashboardEvent,
189
+ 'preferences.color-scheme': stateTopic(undefined),
190
+ 'preferences.dock-locked': stateTopic(undefined),
191
+ 'users.current': stateTopic(undefined),
192
+ }
193
+
194
+ /**
195
+ * Defines the runtime manifest accepted from any message bus version.
196
+ * @internal
197
+ */
198
+ export type TopicManifest = Readonly<
199
+ Record<
200
+ string,
201
+ | {readonly kind: 'state'; readonly seed: unknown}
202
+ | {readonly kind: 'event'; readonly ownership: TopicOwnership}
203
+ >
204
+ >
205
+
206
+ /**
207
+ * Names of all declared event topics.
208
+ * @public
209
+ */
210
+ export type EventTopic = {
211
+ [K in TopicName]: Topics[K] extends {kind: 'event'} ? K : never
212
+ }[TopicName]
213
+
214
+ /**
215
+ * The value type of a state topic.
216
+ * @public
217
+ */
218
+ export type ValueOf<K extends StateTopic> = Topics[K] extends StateTopicDef<infer T> ? T : never
219
+
220
+ /**
221
+ * The payload type of an event topic.
222
+ * @public
223
+ */
224
+ export type PayloadOf<K extends EventTopic> =
225
+ Topics[K] extends EventTopicDef<infer P, infer _R> ? P : never
226
+
227
+ /**
228
+ * The reply type of an event topic (`never` if it declares none).
229
+ * @public
230
+ */
231
+ export type ReplyOf<K extends EventTopic> =
232
+ Topics[K] extends EventTopicDef<infer _P, infer R> ? R : never
233
+
234
+ /**
235
+ * Converts a topic value between 2 adjacent versions.
236
+ * @internal
237
+ */
238
+ export interface TopicMigration {
239
+ /** The older version. */
240
+ readonly from: number
241
+ /** The newer version. */
242
+ readonly to: number
243
+ /** Converts an older state value or event payload to the newer version. */
244
+ up(older: unknown): unknown
245
+ /** Converts a newer state value or event payload to the older version. */
246
+ down(newer: unknown): unknown
247
+ /** Converts event replies between these versions. */
248
+ readonly reply?: {
249
+ /** Converts an older reply to the newer version. */
250
+ up(older: unknown): unknown
251
+ /** Converts a newer reply to the older version. */
252
+ down(newer: unknown): unknown
253
+ }
254
+ }
255
+
256
+ /**
257
+ * Defines the bundled migration chain for each topic.
258
+ * @internal
259
+ */
260
+ export const topicMigrations: Partial<Record<TopicName, readonly TopicMigration[]>> = {}
@@ -2,13 +2,13 @@ import {type ReleaseDocument} from '@sanity/client'
2
2
  import {SanityEncoder} from '@sanity/mutate'
3
3
  import {type PatchMutation as SanityMutatePatchMutation} from '@sanity/mutate/_unstable_store'
4
4
  import {type PatchMutation, type PatchOperations} from '@sanity/types'
5
- import {type SanityDocument} from 'groq'
6
5
 
7
6
  import {
8
7
  type DocumentHandle,
9
8
  type DocumentTypeHandle,
10
9
  type ReleaseHandle,
11
10
  } from '../config/sanityConfig'
11
+ import {type ResolveDocument} from '../typegen/resolve'
12
12
  import {getEffectiveDocumentId} from './util'
13
13
 
14
14
  const isSanityMutatePatch = (value: unknown): value is SanityMutatePatchMutation => {
@@ -37,7 +37,7 @@ export interface CreateDocumentAction<
37
37
  */
38
38
  initialValue?: Partial<
39
39
  Omit<
40
- SanityDocument<TDocumentType, `${TProjectId}.${TDataset}`>,
40
+ ResolveDocument<TDocumentType, `${TProjectId}.${TDataset}`>,
41
41
  '_id' | '_type' | '_rev' | '_createdAt' | '_updatedAt'
42
42
  >
43
43
  >
@@ -166,7 +166,7 @@ export function createDocument<
166
166
  doc: DocumentTypeHandle<TDocumentType, TDataset, TProjectId>,
167
167
  initialValue?: Partial<
168
168
  Omit<
169
- SanityDocument<TDocumentType, `${TProjectId}.${TDataset}`>,
169
+ ResolveDocument<TDocumentType, `${TProjectId}.${TDataset}`>,
170
170
  '_id' | '_type' | '_rev' | '_createdAt' | '_updatedAt'
171
171
  >
172
172
  >,
@@ -1,10 +1,11 @@
1
- import {type SanityDocument} from 'groq'
2
1
  import {distinctUntilChanged, filter, first, firstValueFrom, map, race} from 'rxjs'
3
2
 
4
3
  import {type DocumentResource} from '../config/sanityConfig'
5
4
  import {bindActionByResource} from '../store/createActionBinder'
6
5
  import {type SanityInstance} from '../store/createSanityInstance'
7
6
  import {type StoreContext} from '../store/defineStore'
7
+ import {type ResolveDocument} from '../typegen/resolve'
8
+ import {randomUuid} from '../utils/ids'
8
9
  import {type Action} from './actions'
9
10
  import {documentStore, type DocumentStoreState} from './documentStore'
10
11
  import {type DocumentTransactionSubmissionResult} from './events'
@@ -13,7 +14,7 @@ import {type AppliedTransaction, type QueuedTransaction, queueTransaction} from
13
14
  import {normalizeActionsForResource} from './resourceRules'
14
15
 
15
16
  /** @beta */
16
- export interface ActionsResult<TDocument extends SanityDocument = SanityDocument> {
17
+ export interface ActionsResult<TDocument extends ResolveDocument = ResolveDocument> {
17
18
  transactionId: string
18
19
  documents: DocumentSet<TDocument>
19
20
  previous: DocumentSet<TDocument>
@@ -55,7 +56,7 @@ export function applyDocumentActions<
55
56
  >(
56
57
  instance: SanityInstance,
57
58
  options: ApplyDocumentActionsOptions,
58
- ): Promise<ActionsResult<SanityDocument<TDocumentType, `${TProjectId}.${TDataset}`>>>
59
+ ): Promise<ActionsResult<ResolveDocument<TDocumentType, `${TProjectId}.${TDataset}`>>>
59
60
  /** @beta */
60
61
  export function applyDocumentActions(
61
62
  instance: SanityInstance,
@@ -74,12 +75,7 @@ const boundApplyDocumentActions = bindActionByResource(documentStore, _applyDocu
74
75
  /** @internal */
75
76
  async function _applyDocumentActions(
76
77
  {state}: StoreContext<DocumentStoreState>,
77
- {
78
- actions,
79
- resource,
80
- transactionId = crypto.randomUUID(),
81
- disableBatching,
82
- }: ApplyDocumentActionsOptions,
78
+ {actions, resource, transactionId = randomUuid(), disableBatching}: ApplyDocumentActionsOptions,
83
79
  ): Promise<ActionsResult> {
84
80
  const {events} = state.get()
85
81
 
@@ -44,6 +44,7 @@ import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest'
44
44
  import {getClientState} from '../client/clientStore'
45
45
  import {createSanityInstance, type SanityInstance} from '../store/createSanityInstance'
46
46
  import {type StateSource} from '../store/createStateSourceAction'
47
+ import {randomUuid} from '../utils/ids'
47
48
  import {editDocument, publishDocument} from './actions'
48
49
  import {applyDocumentActions} from './applyDocumentActions'
49
50
  import {getDocumentState, getDocumentSyncStatus} from './documentStore'
@@ -273,7 +274,7 @@ function createMockClient(server: MockContentLake): SanityClient {
273
274
 
274
275
  const action = vi.fn(async (input: HttpAction | HttpAction[], options?: BaseActionOptions) => {
275
276
  const actions = Array.isArray(input) ? input : [input]
276
- return server.submitActions(actions, options?.transactionId ?? crypto.randomUUID())
277
+ return server.submitActions(actions, options?.transactionId ?? randomUuid())
277
278
  })
278
279
 
279
280
  const request = vi.fn(async () => {
@@ -36,6 +36,7 @@ import {getClientState} from '../client/clientStore'
36
36
  import {createDocumentHandle} from '../config/handles'
37
37
  import {createSanityInstance, type SanityInstance} from '../store/createSanityInstance'
38
38
  import {type StateSource} from '../store/createStateSourceAction'
39
+ import {randomUuid} from '../utils/ids'
39
40
  import {
40
41
  createDocument,
41
42
  deleteDocument,
@@ -369,7 +370,7 @@ it('labels an echo that arrives after the last subscriber left as its own', asyn
369
370
  // and out of band, which is the ordering the real API produces
370
371
  const clientActionMockImplementation = vi.mocked(client.action).getMockImplementation()!
371
372
  vi.mocked(client.action).mockImplementation(
372
- async (_input, {transactionId = crypto.randomUUID()} = {}) => ({transactionId}),
373
+ async (_input, {transactionId = randomUuid()} = {}) => ({transactionId}),
373
374
  )
374
375
 
375
376
  const patchEvents: DocumentRemotePatchesEvent[] = []
@@ -671,7 +672,7 @@ it('fetches documents if there are no active subscriptions for the actions appli
671
672
  })
672
673
 
673
674
  it('batches edit transaction into one outgoing transaction', async () => {
674
- const doc = createDocumentHandle({documentId: crypto.randomUUID(), documentType: 'article'})
675
+ const doc = createDocumentHandle({documentId: randomUuid(), documentType: 'article'})
675
676
 
676
677
  const unsubscribe = getDocumentState(instance, doc).subscribe()
677
678
 
@@ -747,7 +748,7 @@ it('submits liveEdit document edits through observable.mutate', async () => {
747
748
  })
748
749
 
749
750
  it('provides the consistency status via `getDocumentSyncStatus`', async () => {
750
- const doc = createDocumentHandle({documentId: crypto.randomUUID(), documentType: 'article'})
751
+ const doc = createDocumentHandle({documentId: randomUuid(), documentType: 'article'})
751
752
 
752
753
  const syncStatus = getDocumentSyncStatus(instance, doc)
753
754
  expect(syncStatus.getCurrent()).toBeUndefined()
@@ -802,7 +803,7 @@ it('reverts failed outgoing transaction locally', async () => {
802
803
  })
803
804
  })
804
805
 
805
- const documentId = DocumentId(crypto.randomUUID())
806
+ const documentId = DocumentId(randomUuid())
806
807
  const doc = createDocumentHandle({documentId, documentType: 'article'})
807
808
 
808
809
  const {getCurrent, subscribe} = getDocumentState(instance, doc)
@@ -870,7 +871,7 @@ it('removes a queued transaction if it fails to apply', async () => {
870
871
  })
871
872
  })
872
873
 
873
- const doc = createDocumentHandle({documentId: crypto.randomUUID(), documentType: 'article'})
874
+ const doc = createDocumentHandle({documentId: randomUuid(), documentType: 'article'})
874
875
  const state = getDocumentState(instance, doc)
875
876
  const unsubscribe = state.subscribe()
876
877
 
@@ -975,8 +976,8 @@ it('fetches dataset ACL and updates grants in the document store state', async (
975
976
  ]
976
977
  vi.mocked(client.request).mockResolvedValue(datasetAcl)
977
978
 
978
- const book = createDocumentHandle({documentId: crypto.randomUUID(), documentType: 'book'})
979
- const author = createDocumentHandle({documentId: crypto.randomUUID(), documentType: 'author'})
979
+ const book = createDocumentHandle({documentId: randomUuid(), documentType: 'book'})
980
+ const author = createDocumentHandle({documentId: randomUuid(), documentType: 'author'})
980
981
 
981
982
  expect(await resolvePermissions(instance, {actions: [createDocument(book)]})).toEqual({
982
983
  allowed: true,
@@ -988,12 +989,12 @@ it('fetches dataset ACL and updates grants in the document store state', async (
988
989
  })
989
990
 
990
991
  it('does not send credentials with the dataset ACL request', async () => {
991
- const doc = createDocumentHandle({documentId: crypto.randomUUID(), documentType: 'article'})
992
+ const doc = createDocumentHandle({documentId: randomUuid(), documentType: 'article'})
992
993
  await resolvePermissions(instance, {actions: [createDocument(doc)]})
993
994
 
994
995
  const aclCall = vi
995
996
  .mocked(client.request)
996
- .mock.calls.find(([options]) => options.uri?.endsWith('/acl'))
997
+ .mock.calls.find(([options]) => options.url?.endsWith('/acl'))
997
998
  expect(aclCall).toBeDefined()
998
999
  expect(aclCall![0]).not.toHaveProperty('withCredentials')
999
1000
  })
@@ -1011,7 +1012,7 @@ it('retries transient dataset ACL fetch failures instead of failing fatally', as
1011
1012
  }),
1012
1013
  )
1013
1014
 
1014
- const doc = createDocumentHandle({documentId: crypto.randomUUID(), documentType: 'article'})
1015
+ const doc = createDocumentHandle({documentId: randomUuid(), documentType: 'article'})
1015
1016
  const result = await resolvePermissions(instance, {actions: [createDocument(doc)]})
1016
1017
 
1017
1018
  expect(result).toEqual({allowed: true})
@@ -1037,7 +1038,7 @@ it('does not retry dataset ACL fetch failures caused by 4xx client errors', asyn
1037
1038
  }),
1038
1039
  )
1039
1040
 
1040
- const doc = createDocumentHandle({documentId: crypto.randomUUID(), documentType: 'article'})
1041
+ const doc = createDocumentHandle({documentId: randomUuid(), documentType: 'article'})
1041
1042
  const documentState = getDocumentState(instance, doc)
1042
1043
  const unsubscribe = documentState.subscribe()
1043
1044
 
@@ -1070,7 +1071,7 @@ it('retries dataset ACL fetch failures caused by 429 rate-limit errors', async (
1070
1071
  }),
1071
1072
  )
1072
1073
 
1073
- const doc = createDocumentHandle({documentId: crypto.randomUUID(), documentType: 'article'})
1074
+ const doc = createDocumentHandle({documentId: randomUuid(), documentType: 'article'})
1074
1075
  const result = await resolvePermissions(instance, {actions: [createDocument(doc)]})
1075
1076
 
1076
1077
  expect(result).toEqual({allowed: true})
@@ -1087,7 +1088,7 @@ it('fetches ACL for MediaLibraryResource', async () => {
1087
1088
  const datasetAcl = [{filter: 'true', permissions: ['read', 'update', 'create', 'history']}]
1088
1089
  vi.mocked(client.request).mockResolvedValue(datasetAcl)
1089
1090
 
1090
- const doc = createDocumentHandle({documentId: crypto.randomUUID(), documentType: 'article'})
1091
+ const doc = createDocumentHandle({documentId: randomUuid(), documentType: 'article'})
1091
1092
  const mediaLibraryResource = {mediaLibraryId: 'test-media-library'}
1092
1093
 
1093
1094
  const result = await resolvePermissions(mediaLibraryInstance, {
@@ -1105,7 +1106,7 @@ it('fetches ACL for CanvasResource', async () => {
1105
1106
  const datasetAcl = [{filter: 'true', permissions: ['read', 'update', 'create', 'history']}]
1106
1107
  vi.mocked(client.request).mockResolvedValue(datasetAcl)
1107
1108
 
1108
- const doc = createDocumentHandle({documentId: crypto.randomUUID(), documentType: 'article'})
1109
+ const doc = createDocumentHandle({documentId: randomUuid(), documentType: 'article'})
1109
1110
  const canvasResource = {canvasId: 'test-canvas'}
1110
1111
 
1111
1112
  const result = await resolvePermissions(canvasInstance, {
@@ -1118,7 +1119,7 @@ it('fetches ACL for CanvasResource', async () => {
1118
1119
  })
1119
1120
 
1120
1121
  it('returns a promise that resolves when a document has been loaded in the store (useful for suspense)', async () => {
1121
- const documentId = DocumentId(crypto.randomUUID())
1122
+ const documentId = DocumentId(randomUuid())
1122
1123
  const doc = createDocumentHandle({documentId, documentType: 'article'})
1123
1124
 
1124
1125
  expect(await resolveDocument(instance, doc)).toBe(null)
@@ -1143,7 +1144,7 @@ it('emits an event for each action after an outgoing transaction has been accept
1143
1144
  const handler = vi.fn()
1144
1145
  const unsubscribe = subscribeDocumentEvents(instance, {resource, eventHandler: handler})
1145
1146
 
1146
- const documentId = DocumentId(crypto.randomUUID())
1147
+ const documentId = DocumentId(randomUuid())
1147
1148
  const doc = createDocumentHandle({documentId, documentType: 'article'})
1148
1149
  expect(handler).toHaveBeenCalledTimes(0)
1149
1150
 
@@ -1574,7 +1575,7 @@ beforeEach(() => {
1574
1575
  mutations: Mutation[],
1575
1576
  options: BaseMutationOptions = {},
1576
1577
  ): Promise<MultipleMutationResult> => {
1577
- const transactionId = options.transactionId ?? crypto.randomUUID()
1578
+ const transactionId = options.transactionId ?? randomUuid()
1578
1579
  const timestamp = new Date().toISOString()
1579
1580
  const prior = {...documents}
1580
1581
  let next = {...documents}
@@ -1647,7 +1648,7 @@ beforeEach(() => {
1647
1648
  const action = vi.fn(
1648
1649
  async (
1649
1650
  input: HttpAction | HttpAction[],
1650
- {transactionId = crypto.randomUUID(), dryRun}: BaseActionOptions,
1651
+ {transactionId = randomUuid(), dryRun}: BaseActionOptions,
1651
1652
  ): Promise<SingleActionResult | MultipleActionResult> => {
1652
1653
  const actions = Array.isArray(input) ? input : [input]
1653
1654
  let next: DocumentSet = {...documents}
@@ -1,7 +1,6 @@
1
1
  import {type Action, ClientError, CorsOriginError, type Mutation} from '@sanity/client'
2
2
  import {DocumentId, getDraftId, getPublishedId, getVersionId} from '@sanity/id-utils'
3
3
  import {jsonMatch} from '@sanity/json-match'
4
- import {type SanityDocument} from 'groq'
5
4
  import {type ExprNode} from 'groq-js'
6
5
  import {
7
6
  catchError,
@@ -46,6 +45,7 @@ import {
46
45
  import {type SanityInstance} from '../store/createSanityInstance'
47
46
  import {createStateSourceAction, type StateSource} from '../store/createStateSourceAction'
48
47
  import {defineStore, type StoreContext} from '../store/defineStore'
48
+ import {type ResolveDocument} from '../typegen/resolve'
49
49
  import {type DocumentAction} from './actions'
50
50
  import {
51
51
  ACL_RETRY_BASE_DELAY,
@@ -98,7 +98,7 @@ export interface DocumentStoreState {
98
98
  identity?: string
99
99
  error?: unknown
100
100
  sharedListener: SharedListener
101
- fetchDocument: (documentId: string) => Observable<SanityDocument | null>
101
+ fetchDocument: (documentId: string) => Observable<ResolveDocument | null>
102
102
  events: Subject<DocumentEvent>
103
103
  }
104
104
 
@@ -108,12 +108,12 @@ export interface DocumentState {
108
108
  * the "remote" local copy that matches the server. represents the last known
109
109
  * server state. this gets updated every time we confirm remote patches
110
110
  */
111
- remote?: SanityDocument | null
111
+ remote?: ResolveDocument | null
112
112
  /**
113
113
  * the current ephemeral working copy that includes local optimistic changes
114
114
  * that have not yet been confirmed by the server
115
115
  */
116
- local?: SanityDocument | null
116
+ local?: ResolveDocument | null
117
117
  /**
118
118
  * the revision that our remote document is at
119
119
  */
@@ -194,7 +194,7 @@ export function getDocumentState<
194
194
  >(
195
195
  instance: SanityInstance,
196
196
  options: DocumentOptions<undefined, TDocumentType, TDataset, TProjectId>,
197
- ): StateSource<SanityDocument<TDocumentType, `${TProjectId}.${TDataset}`> | undefined | null>
197
+ ): StateSource<ResolveDocument<TDocumentType, `${TProjectId}.${TDataset}`> | undefined | null>
198
198
 
199
199
  /** @beta */
200
200
  export function getDocumentState<
@@ -206,7 +206,7 @@ export function getDocumentState<
206
206
  instance: SanityInstance,
207
207
  options: DocumentOptions<TPath, TDocumentType, TDataset, TProjectId>,
208
208
  ): StateSource<
209
- JsonMatch<SanityDocument<TDocumentType, `${TProjectId}.${TDataset}`>, TPath> | undefined
209
+ JsonMatch<ResolveDocument<TDocumentType, `${TProjectId}.${TDataset}`>, TPath> | undefined
210
210
  >
211
211
 
212
212
  /** @beta */
@@ -229,12 +229,12 @@ const _getDocumentState = bindActionByResource(
229
229
  const {documentId: docId, path, liveEdit, perspective} = options
230
230
  const documentId = DocumentId(docId)
231
231
  if (error) throw error
232
- let document: SanityDocument | null | undefined
232
+ let document: ResolveDocument | null | undefined
233
233
 
234
234
  if (liveEdit) {
235
235
  document = documentStates[documentId]?.local
236
236
  } else {
237
- let version: SanityDocument | null | undefined
237
+ let version: ResolveDocument | null | undefined
238
238
  if (isReleasePerspective(perspective)) {
239
239
  const versionId = getVersionId(documentId, perspective.releaseName)
240
240
  version = documentStates[versionId]?.local
@@ -267,16 +267,16 @@ export function resolveDocument<
267
267
  >(
268
268
  instance: SanityInstance,
269
269
  docHandle: DocumentHandle<TDocumentType, TDataset, TProjectId>,
270
- ): Promise<SanityDocument<TDocumentType, `${TProjectId}.${TDataset}`> | null>
270
+ ): Promise<ResolveDocument<TDocumentType, `${TProjectId}.${TDataset}`> | null>
271
271
  /** @beta */
272
- export function resolveDocument<TData extends SanityDocument>(
272
+ export function resolveDocument<TData extends ResolveDocument>(
273
273
  instance: SanityInstance,
274
274
  docHandle: DocumentHandle<string, string, string>,
275
275
  ): Promise<TData | null>
276
276
  /** @beta */
277
277
  export function resolveDocument(
278
278
  ...args: Parameters<typeof _resolveDocument>
279
- ): Promise<SanityDocument | null> {
279
+ ): Promise<ResolveDocument | null> {
280
280
  return _resolveDocument(...args)
281
281
  }
282
282
  const _resolveDocument = bindActionByResource(
@@ -287,7 +287,7 @@ const _resolveDocument = bindActionByResource(
287
287
  ...docHandle,
288
288
  path: undefined,
289
289
  }).observable.pipe(filter((i) => i !== undefined)),
290
- ) as Promise<SanityDocument | null>
290
+ ) as Promise<ResolveDocument | null>
291
291
  },
292
292
  )
293
293
 
@@ -550,13 +550,13 @@ const subscribeToClientAndFetchDatasetAcl = ({
550
550
  }: StoreContext<DocumentStoreState, BoundResourceKey>) => {
551
551
  const clientOptions: ClientOptions = {apiVersion: API_VERSION, resource}
552
552
 
553
- let uri: string
553
+ let url: string
554
554
  if (resource && isDatasetResource(resource)) {
555
- uri = `/projects/${resource.projectId}/datasets/${resource.dataset}/acl`
555
+ url = `/projects/${resource.projectId}/datasets/${resource.dataset}/acl`
556
556
  } else if (resource && isMediaLibraryResource(resource)) {
557
- uri = `/media-libraries/${resource.mediaLibraryId}/acl`
557
+ url = `/media-libraries/${resource.mediaLibraryId}/acl`
558
558
  } else if (resource && isCanvasResource(resource)) {
559
- uri = `/canvases/${resource.canvasId}/acl`
559
+ url = `/canvases/${resource.canvasId}/acl`
560
560
  } else {
561
561
  throw new Error(`Received invalid resource: ${JSON.stringify(resource)}`)
562
562
  }
@@ -566,7 +566,7 @@ const subscribeToClientAndFetchDatasetAcl = ({
566
566
  switchMap((client) =>
567
567
  client.observable
568
568
  .request<DatasetAcl>({
569
- uri,
569
+ url,
570
570
  tag: 'acl.get',
571
571
  })
572
572
  .pipe(
@@ -5,6 +5,7 @@ import {createSelector} from 'reselect'
5
5
 
6
6
  import {isReleasePerspective} from '../releases/utils/isReleasePerspective'
7
7
  import {type SelectorContext} from '../store/createStateSourceAction'
8
+ import {randomId} from '../utils/ids'
8
9
  import {MultiKeyWeakMap} from '../utils/MultiKeyWeakMap'
9
10
  import {type DocumentAction} from './actions'
10
11
  import {ActionError, PermissionActionError, processActions} from './processActions/processActions'
@@ -190,7 +191,7 @@ const _calculatePermissions = createSelector(
190
191
  try {
191
192
  processActions({
192
193
  actions,
193
- transactionId: crypto.randomUUID(),
194
+ transactionId: randomId(16),
194
195
  working: documents,
195
196
  base: documents,
196
197
  timestamp,
@@ -2,6 +2,7 @@ import {type CreateMutation, type Reference, type SanityDocument} from '@sanity/
2
2
  import {parse} from 'groq-js'
3
3
  import {describe, expect, it} from 'vitest'
4
4
 
5
+ import {randomUuid} from '../utils/ids'
5
6
  import {type Action, type DocumentAction} from './actions'
6
7
  import {ActionError, processActions} from './processActions/processActions'
7
8
  import {type DocumentSet, processMutations} from './processMutations'
@@ -973,7 +974,7 @@ describe('processActions', () => {
973
974
  })
974
975
 
975
976
  it('should strengthen `_strengthenOnPublish` references', () => {
976
- const _ref = crypto.randomUUID()
977
+ const _ref = randomUuid()
977
978
  const referenceToStrength: Reference = {
978
979
  _ref,
979
980
  _type: 'reference',