@sanity/client 7.26.2 → 8.0.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 (130) hide show
  1. package/README.md +5 -377
  2. package/dist/browserUpload-CQgx9YYo.js +86 -0
  3. package/dist/browserUpload-CQgx9YYo.js.map +1 -0
  4. package/dist/browserUpload-icWlVP15.js +86 -0
  5. package/dist/browserUpload-icWlVP15.js.map +1 -0
  6. package/dist/config-a8VajuEY.js +126 -0
  7. package/dist/config-a8VajuEY.js.map +1 -0
  8. package/dist/csm.d.ts +122 -335
  9. package/dist/csm.js +79 -91
  10. package/dist/csm.js.map +1 -1
  11. package/dist/dist-C9ExSk2R.js +73 -0
  12. package/dist/dist-C9ExSk2R.js.map +1 -0
  13. package/dist/dist-Z8cIRxoB.js +69 -0
  14. package/dist/dist-Z8cIRxoB.js.map +1 -0
  15. package/dist/index.d.ts +107 -6723
  16. package/dist/index.js +2822 -2847
  17. package/dist/index.js.map +1 -1
  18. package/dist/{index.d.cts → index.node.d.ts} +4291 -5328
  19. package/dist/index.node.js +3131 -0
  20. package/dist/index.node.js.map +1 -0
  21. package/dist/isRecord-Kfmt-nk-.js +7 -0
  22. package/dist/isRecord-Kfmt-nk-.js.map +1 -0
  23. package/dist/media-library.d.ts +9 -100
  24. package/dist/media-library.js +36 -14
  25. package/dist/media-library.js.map +1 -1
  26. package/dist/request-CJxcN16k.js +372 -0
  27. package/dist/request-CJxcN16k.js.map +1 -0
  28. package/dist/request-k7VS_NnC.js +354 -0
  29. package/dist/request-k7VS_NnC.js.map +1 -0
  30. package/dist/resolveEditInfo-sq7yF78q.js +325 -0
  31. package/dist/resolveEditInfo-sq7yF78q.js.map +1 -0
  32. package/dist/rolldown-runtime-vyAXikos.js +9 -0
  33. package/dist/stega.d.ts +78 -7137
  34. package/dist/stega.js +27 -20
  35. package/dist/stega.js.map +1 -1
  36. package/dist/stegaClean-C18wLWau.js +21 -0
  37. package/dist/stegaClean-C18wLWau.js.map +1 -0
  38. package/dist/stegaEncodeSourceMap-B2fGArSf.js +163 -0
  39. package/dist/stegaEncodeSourceMap-B2fGArSf.js.map +1 -0
  40. package/dist/stegaEncodeSourceMap-DkoIlutY.js +367 -0
  41. package/dist/stegaEncodeSourceMap-DkoIlutY.js.map +1 -0
  42. package/dist/{index.browser.d.cts → types-CUxZSgB2.d.ts} +3956 -5256
  43. package/dist/types-CfGzbXrl.d.ts +108 -0
  44. package/package.json +64 -141
  45. package/src/SanityClient.ts +82 -117
  46. package/src/agent/actions/AgentActionsClient.ts +12 -12
  47. package/src/agent/actions/commonTypes.ts +2 -1
  48. package/src/agent/actions/generate.ts +26 -6
  49. package/src/agent/actions/patch.ts +27 -7
  50. package/src/agent/actions/prompt.ts +19 -5
  51. package/src/agent/actions/transform.ts +23 -5
  52. package/src/agent/actions/translate.ts +23 -5
  53. package/src/assets/AssetsClient.ts +71 -23
  54. package/src/csm/applySourceDocuments.ts +3 -1
  55. package/src/csm/createEditUrl.ts +1 -1
  56. package/src/csm/draftUtils.ts +0 -1
  57. package/src/csm/index.ts +2 -2
  58. package/src/csm/studioPath.ts +2 -1
  59. package/src/csm/types.ts +8 -2
  60. package/src/data/dataMethods.ts +801 -349
  61. package/src/data/eventsource.ts +13 -10
  62. package/src/data/listen.ts +18 -23
  63. package/src/data/live.ts +44 -27
  64. package/src/data/resolveEventSourceFetch.ts +96 -0
  65. package/src/datasets/DatasetsClient.ts +58 -41
  66. package/src/defineCreateClient.ts +35 -23
  67. package/src/http/browserMiddleware.ts +7 -1
  68. package/src/http/browserUpload.ts +162 -0
  69. package/src/http/errors.ts +60 -6
  70. package/src/http/nodeMiddleware.ts +94 -30
  71. package/src/http/request.ts +310 -87
  72. package/src/http/requestOptions.ts +131 -15
  73. package/src/{index.browser.ts → index.node.ts} +9 -4
  74. package/src/index.ts +4 -3
  75. package/src/mediaLibrary/MediaLibraryVideoClient.ts +26 -11
  76. package/src/projects/ProjectsClient.ts +18 -16
  77. package/src/releases/ReleasesClient.ts +38 -33
  78. package/src/stega/index.ts +1 -2
  79. package/src/stega/stegaEncodeSourceMap.ts +5 -0
  80. package/src/stega/types.ts +2 -2
  81. package/src/types.ts +148 -105
  82. package/src/users/UsersClient.ts +10 -14
  83. package/src/util/shareReplayLatest.ts +1 -1
  84. package/src/warnings.ts +6 -1
  85. package/dist/_chunks-cjs/config.cjs +0 -206
  86. package/dist/_chunks-cjs/config.cjs.map +0 -1
  87. package/dist/_chunks-cjs/isRecord.cjs +0 -6
  88. package/dist/_chunks-cjs/isRecord.cjs.map +0 -1
  89. package/dist/_chunks-cjs/resolveEditInfo.cjs +0 -334
  90. package/dist/_chunks-cjs/resolveEditInfo.cjs.map +0 -1
  91. package/dist/_chunks-cjs/stegaClean.cjs +0 -46
  92. package/dist/_chunks-cjs/stegaClean.cjs.map +0 -1
  93. package/dist/_chunks-cjs/stegaEncodeSourceMap.cjs +0 -388
  94. package/dist/_chunks-cjs/stegaEncodeSourceMap.cjs.map +0 -1
  95. package/dist/_chunks-es/config.js +0 -207
  96. package/dist/_chunks-es/config.js.map +0 -1
  97. package/dist/_chunks-es/isRecord.js +0 -7
  98. package/dist/_chunks-es/isRecord.js.map +0 -1
  99. package/dist/_chunks-es/resolveEditInfo.js +0 -335
  100. package/dist/_chunks-es/resolveEditInfo.js.map +0 -1
  101. package/dist/_chunks-es/stegaClean.js +0 -47
  102. package/dist/_chunks-es/stegaClean.js.map +0 -1
  103. package/dist/_chunks-es/stegaEncodeSourceMap.js +0 -389
  104. package/dist/_chunks-es/stegaEncodeSourceMap.js.map +0 -1
  105. package/dist/csm.cjs +0 -99
  106. package/dist/csm.cjs.map +0 -1
  107. package/dist/csm.d.cts +0 -391
  108. package/dist/index.browser.cjs +0 -3106
  109. package/dist/index.browser.cjs.map +0 -1
  110. package/dist/index.browser.d.ts +0 -6802
  111. package/dist/index.browser.js +0 -3082
  112. package/dist/index.browser.js.map +0 -1
  113. package/dist/index.cjs +0 -2946
  114. package/dist/index.cjs.map +0 -1
  115. package/dist/media-library.cjs +0 -20
  116. package/dist/media-library.cjs.map +0 -1
  117. package/dist/media-library.d.cts +0 -117
  118. package/dist/stega.browser.cjs +0 -29
  119. package/dist/stega.browser.cjs.map +0 -1
  120. package/dist/stega.browser.d.cts +0 -7200
  121. package/dist/stega.browser.d.ts +0 -7200
  122. package/dist/stega.browser.js +0 -24
  123. package/dist/stega.browser.js.map +0 -1
  124. package/dist/stega.cjs +0 -29
  125. package/dist/stega.cjs.map +0 -1
  126. package/dist/stega.d.cts +0 -7200
  127. package/src/data/eventsourcePolyfill.ts +0 -7
  128. package/umd/.gitkeep +0 -1
  129. package/umd/sanityClient.js +0 -6543
  130. package/umd/sanityClient.min.js +0 -6
@@ -1,11 +1,11 @@
1
1
  import {getDraftId, getVersionFromId, getVersionId, isDraftId} from '@sanity/client/csm'
2
- import {from, type MonoTypeOperatorFunction, Observable, of} from 'rxjs'
3
- import {combineLatestWith, concatMap, filter, map, reduce} from 'rxjs/operators'
2
+ import {type MonoTypeOperatorFunction, Observable} from 'rxjs'
3
+ import {filter, map} from 'rxjs/operators'
4
4
 
5
5
  import {validateApiPerspective} from '../config'
6
- import {requestOptions} from '../http/requestOptions'
6
+ import {type FetchRequest, requestOptions} from '../http/requestOptions'
7
7
  import type {ObservableSanityClient, SanityClient} from '../SanityClient'
8
- import {stegaClean} from '../stega/stegaClean'
8
+ import {stegaClean, type StegaCleaned} from '../stega/stegaClean'
9
9
  import type {
10
10
  Action,
11
11
  AllDocumentIdsMutationOptions,
@@ -19,7 +19,6 @@ import type {
19
19
  FirstDocumentIdMutationOptions,
20
20
  FirstDocumentMutationOptions,
21
21
  HttpRequest,
22
- HttpRequestEvent,
23
22
  IdentifiedSanityDocumentStub,
24
23
  InitializedClientConfig,
25
24
  InitializedStegaConfig,
@@ -31,11 +30,11 @@ import type {
31
30
  RawQueryResponse,
32
31
  ReplaceVersionAction,
33
32
  RequestObservableOptions,
34
- RequestOptions,
35
33
  SanityDocument,
36
34
  SingleActionResult,
37
35
  SingleMutationResult,
38
36
  UnpublishVersionAction,
37
+ UploadEvent,
39
38
  } from '../types'
40
39
  import {getSelection} from '../util/getSelection'
41
40
  import * as validate from '../validators'
@@ -43,6 +42,7 @@ import * as validators from '../validators'
43
42
  import {
44
43
  printCdnPreviewDraftsWarning,
45
44
  printCreateVersionWithBaseIdWarning,
45
+ printDeprecatedUriOptionWarning,
46
46
  printPreviewDraftsDeprecationWarning,
47
47
  } from '../warnings'
48
48
  import {encodeQueryString} from './encodeQueryString'
@@ -67,9 +67,6 @@ const getMutationQuery = (options: BaseMutationOptions = {}) => {
67
67
  }
68
68
  }
69
69
 
70
- const isResponse = (event: Any) => event.type === 'response'
71
- const getBody = (event: Any) => event.body
72
-
73
70
  const indexBy = (docs: Any[], attr: Any) =>
74
71
  docs.reduce((indexed, doc) => {
75
72
  indexed[attr(doc)] = doc
@@ -78,19 +75,28 @@ const indexBy = (docs: Any[], attr: Any) =>
78
75
 
79
76
  const getQuerySizeLimit = 11264
80
77
 
81
- /** @internal */
82
- export function _fetch<R, Q>(
83
- client: Client,
84
- httpRequest: HttpRequest,
78
+ /**
79
+ * Resolve the effective stega config, cleaned params, response mapper and
80
+ * transport request options for a `fetch()` call. Shared by the observable and
81
+ * promise paths.
82
+ *
83
+ * @internal
84
+ */
85
+ function _fetchRequest<Q>(
85
86
  _stega: InitializedStegaConfig,
86
- query: string,
87
- _params: Q = {} as Q,
88
- options: QueryOptions = {},
89
- ): Observable<RawQueryResponse<R> | R> {
87
+ _params: Q,
88
+ options: QueryOptions,
89
+ ): {
90
+ stega: InitializedStegaConfig
91
+ // Stega-enabled requests send the cleaned (brand-stripped) params.
92
+ params: Q | StegaCleaned<Q>
93
+ mapResponse: (res: Any) => Any
94
+ reqOpts: Any
95
+ } {
90
96
  const stega =
91
97
  'stega' in options
92
98
  ? {
93
- ...(_stega || {}),
99
+ ..._stega,
94
100
  ...(typeof options.stega === 'boolean' ? {enabled: options.stega} : options.stega || {}),
95
101
  }
96
102
  : _stega
@@ -114,55 +120,128 @@ export function _fetch<R, Q>(
114
120
  ? {...opts, fetch: {cache, next}}
115
121
  : opts
116
122
 
117
- const $request = _dataRequest(client, httpRequest, 'query', {query, params}, reqOpts)
118
- return stega.enabled
119
- ? $request.pipe(
120
- combineLatestWith(
121
- from(
122
- import('../stega/stegaEncodeSourceMap').then(
123
- ({stegaEncodeSourceMap}) => stegaEncodeSourceMap,
124
- ),
125
- ),
126
- ),
127
- map(
128
- ([res, stegaEncodeSourceMap]: [
129
- Any,
130
- (typeof import('../stega/stegaEncodeSourceMap'))['stegaEncodeSourceMap'],
131
- ]) => {
132
- const result = stegaEncodeSourceMap(res.result, res.resultSourceMap, stega)
133
- return mapResponse({...res, result})
134
- },
135
- ),
136
- )
137
- : $request.pipe(map(mapResponse))
123
+ return {stega, params, mapResponse, reqOpts}
138
124
  }
139
125
 
140
126
  /** @internal */
141
- export function _getDocument<R extends Record<string, Any>>(
127
+ export function _fetchObservable<R, Q>(
128
+ client: Client,
129
+ httpRequest: HttpRequest,
130
+ _stega: InitializedStegaConfig,
131
+ query: string,
132
+ _params: Q = {} as Q,
133
+ options: QueryOptions = {},
134
+ ): Observable<RawQueryResponse<R> | R> {
135
+ return _observe(options.signal, (signal) =>
136
+ _fetch<R, Q>(client, httpRequest, _stega, query, _params, {...options, signal}),
137
+ )
138
+ }
139
+
140
+ /**
141
+ * Promise-based sibling of {@link _fetchObservable}.
142
+ *
143
+ * @internal
144
+ */
145
+ export function _fetch<R, Q>(
146
+ client: Client,
147
+ httpRequest: HttpRequest,
148
+ _stega: InitializedStegaConfig,
149
+ query: string,
150
+ _params: Q = {} as Q,
151
+ options: QueryOptions = {},
152
+ ): Promise<RawQueryResponse<R> | R> {
153
+ const {stega, params, mapResponse, reqOpts} = _fetchRequest(_stega, _params, options)
154
+
155
+ // Kick off the request synchronously (not in an `async` body) so option
156
+ // validation - e.g. an invalid request tag or a missing dataset - throws
157
+ // synchronously at call time, matching the rest of the promise surface.
158
+ const request = _dataRequest(client, httpRequest, 'query', {query, params}, reqOpts)
159
+ if (!stega.enabled) {
160
+ return request.then(mapResponse)
161
+ }
162
+ return Promise.all([request, import('../stega/stegaEncodeSourceMap')]).then(
163
+ ([res, {stegaEncodeSourceMap}]) => {
164
+ const result = stegaEncodeSourceMap(res.result, res.resultSourceMap, stega)
165
+ return mapResponse({...res, result})
166
+ },
167
+ )
168
+ }
169
+
170
+ /** @internal */
171
+ export function _getDocumentObservable<R extends Record<string, Any>>(
142
172
  client: Client,
143
173
  httpRequest: HttpRequest,
144
174
  id: string,
145
175
  opts: {signal?: AbortSignal; tag?: string; releaseId?: string; includeAllVersions: true},
146
176
  ): Observable<SanityDocument<R>[]>
147
177
  /** @internal */
148
- export function _getDocument<R extends Record<string, Any>>(
178
+ export function _getDocumentObservable<R extends Record<string, Any>>(
149
179
  client: Client,
150
180
  httpRequest: HttpRequest,
151
181
  id: string,
152
182
  opts?: {signal?: AbortSignal; tag?: string; releaseId?: string; includeAllVersions?: false},
153
183
  ): Observable<SanityDocument<R> | undefined>
154
184
  /** @internal */
155
- export function _getDocument<R extends Record<string, Any>>(
185
+ export function _getDocumentObservable<R extends Record<string, Any>>(
156
186
  client: Client,
157
187
  httpRequest: HttpRequest,
158
188
  id: string,
159
189
  opts: {signal?: AbortSignal; tag?: string; releaseId?: string; includeAllVersions?: boolean} = {},
160
190
  ): Observable<SanityDocument<R> | undefined | SanityDocument<R>[]> {
161
- const getDocId = () => {
162
- if (!opts.releaseId) {
163
- return id
164
- }
191
+ return _observe(opts.signal, (signal) =>
192
+ _request<{documents?: SanityDocument<R>[]}>(
193
+ client,
194
+ httpRequest,
195
+ _getDocumentOptions(client, id, {...opts, signal}),
196
+ ).then((body) => _mapGetDocument<R>(body, opts.includeAllVersions)),
197
+ )
198
+ }
199
+
200
+ /**
201
+ * Promise-based sibling of {@link _getDocumentObservable}.
202
+ *
203
+ * @internal
204
+ */
205
+ export function _getDocument<R extends Record<string, Any>>(
206
+ client: Client,
207
+ httpRequest: HttpRequest,
208
+ id: string,
209
+ opts: {signal?: AbortSignal; tag?: string; releaseId?: string; includeAllVersions: true},
210
+ ): Promise<SanityDocument<R>[]>
211
+ /** @internal */
212
+ export function _getDocument<R extends Record<string, Any>>(
213
+ client: Client,
214
+ httpRequest: HttpRequest,
215
+ id: string,
216
+ opts?: {signal?: AbortSignal; tag?: string; releaseId?: string; includeAllVersions?: false},
217
+ ): Promise<SanityDocument<R> | undefined>
218
+ /** @internal */
219
+ export function _getDocument<R extends Record<string, Any>>(
220
+ client: Client,
221
+ httpRequest: HttpRequest,
222
+ id: string,
223
+ opts: {signal?: AbortSignal; tag?: string; releaseId?: string; includeAllVersions?: boolean} = {},
224
+ ): Promise<SanityDocument<R> | undefined | SanityDocument<R>[]> {
225
+ const options = _getDocumentOptions(client, id, opts)
226
+ return _request<{documents?: SanityDocument<R>[]}>(client, httpRequest, options).then((body) =>
227
+ _mapGetDocument<R>(body, opts.includeAllVersions),
228
+ )
229
+ }
165
230
 
231
+ /**
232
+ * Resolve the document id (honoring `releaseId`) and build the transport
233
+ * request options for {@link _getDocumentObservable}. Shared by the observable and
234
+ * promise paths.
235
+ *
236
+ * @internal
237
+ */
238
+ function _getDocumentOptions(
239
+ client: Client,
240
+ id: string,
241
+ opts: {signal?: AbortSignal; tag?: string; releaseId?: string; includeAllVersions?: boolean},
242
+ ): Any {
243
+ let docId = id
244
+ if (opts.releaseId) {
166
245
  const versionId = getVersionFromId(id)
167
246
  if (!versionId) {
168
247
  if (isDraftId(id)) {
@@ -170,23 +249,16 @@ export function _getDocument<R extends Record<string, Any>>(
170
249
  `The document ID (\`${id}\`) is a draft, but \`options.releaseId\` is set as \`${opts.releaseId}\``,
171
250
  )
172
251
  }
173
-
174
- return getVersionId(id, opts.releaseId)
175
- }
176
-
177
- if (versionId !== opts.releaseId) {
252
+ docId = getVersionId(id, opts.releaseId)
253
+ } else if (versionId !== opts.releaseId) {
178
254
  throw new Error(
179
255
  `The document ID (\`${id}\`) is already a version of \`${versionId}\` release, but this does not match the provided \`options.releaseId\` (\`${opts.releaseId}\`)`,
180
256
  )
181
257
  }
182
-
183
- return id
184
258
  }
185
- const docId = getDocId()
186
259
 
187
- const options = {
188
- uri: _getDataUrl(client, 'doc', docId),
189
- json: true,
260
+ return {
261
+ url: _getDataUrl(client, 'doc', docId),
190
262
  tag: opts.tag,
191
263
  signal: opts.signal,
192
264
  query:
@@ -194,111 +266,563 @@ export function _getDocument<R extends Record<string, Any>>(
194
266
  ? {includeAllVersions: opts.includeAllVersions}
195
267
  : undefined,
196
268
  }
197
- return _requestObservable<SanityDocument<R> | undefined | SanityDocument<R>[]>(
198
- client,
199
- httpRequest,
200
- options,
201
- ).pipe(
202
- filter(isResponse),
203
- map((event) => {
204
- const documents = event.body.documents
205
- if (!documents) {
206
- return opts.includeAllVersions ? [] : undefined
207
- }
208
- return opts.includeAllVersions ? documents : documents[0]
209
- }),
210
- )
269
+ }
270
+
271
+ function _mapGetDocument<R extends Record<string, Any>>(
272
+ body: {documents?: SanityDocument<R>[]},
273
+ includeAllVersions?: boolean,
274
+ ): SanityDocument<R> | undefined | SanityDocument<R>[] {
275
+ const documents = body.documents
276
+ if (!documents) {
277
+ return includeAllVersions ? [] : undefined
278
+ }
279
+ return includeAllVersions ? documents : documents[0]
211
280
  }
212
281
 
213
282
  /** @internal */
214
- export function _getDocuments<R extends Record<string, Any>>(
283
+ export function _getDocumentsObservable<R extends Record<string, Any>>(
215
284
  client: Client,
216
285
  httpRequest: HttpRequest,
217
286
  ids: string[],
218
287
  opts: {signal?: AbortSignal; tag?: string} = {},
219
288
  ): Observable<(SanityDocument<R> | null)[]> {
220
- const options = {
221
- uri: _getDataUrl(client, 'doc', ids.join(',')),
222
- json: true,
289
+ return _observe(opts.signal, (signal) =>
290
+ _getDocuments<R>(client, httpRequest, ids, {...opts, signal}),
291
+ )
292
+ }
293
+
294
+ /**
295
+ * Promise-based sibling of {@link _getDocumentsObservable}.
296
+ *
297
+ * @internal
298
+ */
299
+ export function _getDocuments<R extends Record<string, Any>>(
300
+ client: Client,
301
+ httpRequest: HttpRequest,
302
+ ids: string[],
303
+ opts: {signal?: AbortSignal; tag?: string} = {},
304
+ ): Promise<(SanityDocument<R> | null)[]> {
305
+ const options = _getDocumentsOptions(client, ids, opts)
306
+ return _request<{documents?: SanityDocument<R>[]}>(client, httpRequest, options).then((body) =>
307
+ _mapGetDocuments<R>(body, ids),
308
+ )
309
+ }
310
+
311
+ function _getDocumentsOptions(
312
+ client: Client,
313
+ ids: string[],
314
+ opts: {signal?: AbortSignal; tag?: string},
315
+ ): Any {
316
+ return {
317
+ url: _getDataUrl(client, 'doc', ids.join(',')),
223
318
  tag: opts.tag,
224
319
  signal: opts.signal,
225
320
  }
226
- return _requestObservable<(SanityDocument<R> | null)[]>(client, httpRequest, options).pipe(
227
- filter(isResponse),
228
- map((event: Any) => {
229
- const indexed = indexBy(event.body.documents || [], (doc: Any) => doc._id)
230
- return ids.map((id) => indexed[id] || null)
231
- }),
232
- )
321
+ }
322
+
323
+ function _mapGetDocuments<R extends Record<string, Any>>(
324
+ body: {documents?: SanityDocument<R>[]},
325
+ ids: string[],
326
+ ): (SanityDocument<R> | null)[] {
327
+ const indexed = indexBy(body.documents || [], (doc: Any) => doc._id)
328
+ return ids.map((id) => indexed[id] || null)
233
329
  }
234
330
 
235
331
  const DOCUMENTS_EXISTS_BATCH_SIZE = 100
236
332
 
237
333
  /** @internal */
238
- export function _documentsExists(
334
+ export function _documentsExistsObservable(
239
335
  client: Client,
240
336
  httpRequest: HttpRequest,
241
337
  ids: string[],
242
338
  opts: {signal?: AbortSignal; tag?: string} = {},
243
339
  ): Observable<Set<string>> {
340
+ return _observe(opts.signal, (signal) =>
341
+ _documentsExists(client, httpRequest, ids, {...opts, signal}),
342
+ )
343
+ }
344
+
345
+ /**
346
+ * Promise-based sibling of {@link _documentsExistsObservable}. Checks document
347
+ * existence in batches, resolving to the set of IDs that exist.
348
+ *
349
+ * @internal
350
+ */
351
+ export async function _documentsExists(
352
+ client: Client,
353
+ httpRequest: HttpRequest,
354
+ ids: string[],
355
+ opts: {signal?: AbortSignal; tag?: string} = {},
356
+ ): Promise<Set<string>> {
357
+ const existing = new Set<string>()
244
358
  if (ids.length === 0) {
245
- return of(new Set<string>())
359
+ return existing
246
360
  }
247
361
 
248
- const batches: string[][] = []
249
362
  for (let i = 0; i < ids.length; i += DOCUMENTS_EXISTS_BATCH_SIZE) {
250
- batches.push(ids.slice(i, i + DOCUMENTS_EXISTS_BATCH_SIZE))
363
+ const batchIds = ids.slice(i, i + DOCUMENTS_EXISTS_BATCH_SIZE)
364
+ const body = await _request<{omitted?: {id: string; reason: string}[]}>(client, httpRequest, {
365
+ url: _getDataUrl(client, 'doc', batchIds.map(encodeURIComponent).join(',')),
366
+ tag: opts.tag,
367
+ signal: opts.signal,
368
+ query: {excludeContent: true},
369
+ })
370
+
371
+ const missing = new Set<string>()
372
+ for (const omitted of body.omitted || []) {
373
+ if (omitted.reason !== 'existence') continue
374
+ missing.add(omitted.id)
375
+ }
376
+ for (const id of batchIds) {
377
+ if (!missing.has(id)) existing.add(id)
378
+ }
379
+ }
380
+
381
+ return existing
382
+ }
383
+
384
+ /** @internal */
385
+ export function _getReleaseDocumentsObservable<R extends Record<string, Any>>(
386
+ client: ObservableSanityClient | SanityClient,
387
+ httpRequest: HttpRequest,
388
+ releaseId: string,
389
+ opts: BaseMutationOptions = {},
390
+ ): Observable<RawQueryResponse<SanityDocument<R>[]>> {
391
+ return _observe(opts.signal, (signal) =>
392
+ _getReleaseDocuments<R>(client, httpRequest, releaseId, {...opts, signal}),
393
+ )
394
+ }
395
+
396
+ /**
397
+ * Promise-based sibling of {@link _getReleaseDocumentsObservable}.
398
+ *
399
+ * @internal
400
+ */
401
+ export function _getReleaseDocuments<R extends Record<string, Any>>(
402
+ client: ObservableSanityClient | SanityClient,
403
+ httpRequest: HttpRequest,
404
+ releaseId: string,
405
+ opts: BaseMutationOptions = {},
406
+ ): Promise<RawQueryResponse<SanityDocument<R>[]>> {
407
+ return _dataRequest(
408
+ client,
409
+ httpRequest,
410
+ 'query',
411
+ {
412
+ query: '*[sanity::partOfRelease($releaseId)]',
413
+ params: {
414
+ releaseId,
415
+ },
416
+ },
417
+ opts,
418
+ )
419
+ }
420
+
421
+ /** @internal */
422
+ export function _createIfNotExistsObservable<R extends Record<string, Any>>(
423
+ client: Client,
424
+ httpRequest: HttpRequest,
425
+ doc: IdentifiedSanityDocumentStub<R>,
426
+ options?:
427
+ | AllDocumentIdsMutationOptions
428
+ | AllDocumentsMutationOptions
429
+ | BaseMutationOptions
430
+ | FirstDocumentIdMutationOptions
431
+ | FirstDocumentMutationOptions,
432
+ ): Observable<
433
+ SanityDocument<R> | SanityDocument<R>[] | SingleMutationResult | MultipleMutationResult
434
+ > {
435
+ return _observe(options?.signal, (signal) =>
436
+ _createIfNotExists<R>(client, httpRequest, doc, {...options, signal}),
437
+ )
438
+ }
439
+
440
+ /** @internal */
441
+ export function _createOrReplaceObservable<R extends Record<string, Any>>(
442
+ client: Client,
443
+ httpRequest: HttpRequest,
444
+ doc: IdentifiedSanityDocumentStub<R>,
445
+ options?:
446
+ | AllDocumentIdsMutationOptions
447
+ | AllDocumentsMutationOptions
448
+ | BaseMutationOptions
449
+ | FirstDocumentIdMutationOptions
450
+ | FirstDocumentMutationOptions,
451
+ ): Observable<
452
+ SanityDocument<R> | SanityDocument<R>[] | SingleMutationResult | MultipleMutationResult
453
+ > {
454
+ return _observe(options?.signal, (signal) =>
455
+ _createOrReplace<R>(client, httpRequest, doc, {...options, signal}),
456
+ )
457
+ }
458
+
459
+ /** @internal */
460
+ export function _createVersionObservable<R extends Record<string, Any>>(
461
+ client: ObservableSanityClient | SanityClient,
462
+ httpRequest: HttpRequest,
463
+ doc: IdentifiedSanityDocumentStub<R>,
464
+ publishedId: string,
465
+ options?: BaseActionOptions,
466
+ ): Observable<SingleActionResult> {
467
+ return _observe(options?.signal, (signal) =>
468
+ _createVersion<R>(client, httpRequest, doc, publishedId, {...options, signal}),
469
+ )
470
+ }
471
+
472
+ /** @internal */
473
+ export function _createVersionFromBaseObservable(
474
+ client: ObservableSanityClient | SanityClient,
475
+ httpRequest: HttpRequest,
476
+ publishedId?: string,
477
+ baseId?: string,
478
+ releaseId?: string,
479
+ ifBaseRevisionId?: string,
480
+ options?: BaseActionOptions,
481
+ ): Observable<SingleActionResult> {
482
+ return _observe(options?.signal, (signal) =>
483
+ _createVersionFromBase(client, httpRequest, publishedId, baseId, releaseId, ifBaseRevisionId, {
484
+ ...options,
485
+ signal,
486
+ }),
487
+ )
488
+ }
489
+
490
+ /** @internal */
491
+ export function _deleteObservable<R extends Record<string, Any>>(
492
+ client: Client,
493
+ httpRequest: HttpRequest,
494
+ selection: string | MutationSelection,
495
+ options?:
496
+ | AllDocumentIdsMutationOptions
497
+ | AllDocumentsMutationOptions
498
+ | BaseMutationOptions
499
+ | FirstDocumentIdMutationOptions
500
+ | FirstDocumentMutationOptions,
501
+ ): Observable<
502
+ SanityDocument<R> | SanityDocument<R>[] | SingleMutationResult | MultipleMutationResult
503
+ > {
504
+ return _observe(options?.signal, (signal) =>
505
+ _delete<R>(client, httpRequest, selection, {...options, signal}),
506
+ )
507
+ }
508
+
509
+ /** @internal */
510
+ export function _discardVersionObservable(
511
+ client: ObservableSanityClient | SanityClient,
512
+ httpRequest: HttpRequest,
513
+ versionId: string,
514
+ purge: boolean = false,
515
+ options?: BaseActionOptions,
516
+ ): Observable<SingleActionResult> {
517
+ return _observe(options?.signal, (signal) =>
518
+ _discardVersion(client, httpRequest, versionId, purge, {...options, signal}),
519
+ )
520
+ }
521
+
522
+ /** @internal */
523
+ export function _replaceVersionObservable<R extends Record<string, Any>>(
524
+ client: ObservableSanityClient | SanityClient,
525
+ httpRequest: HttpRequest,
526
+ doc: IdentifiedSanityDocumentStub<R>,
527
+ options?: BaseActionOptions,
528
+ ): Observable<SingleActionResult> {
529
+ return _observe(options?.signal, (signal) =>
530
+ _replaceVersion<R>(client, httpRequest, doc, {...options, signal}),
531
+ )
532
+ }
533
+
534
+ /** @internal */
535
+ export function _unpublishVersionObservable(
536
+ client: ObservableSanityClient | SanityClient,
537
+ httpRequest: HttpRequest,
538
+ versionId: string,
539
+ publishedId: string,
540
+ options?: BaseActionOptions,
541
+ ): Observable<SingleActionResult> {
542
+ return _observe(options?.signal, (signal) =>
543
+ _unpublishVersion(client, httpRequest, versionId, publishedId, {
544
+ ...options,
545
+ signal,
546
+ }),
547
+ )
548
+ }
549
+
550
+ /** @internal */
551
+ export function _mutateObservable<R extends Record<string, Any>>(
552
+ client: Client,
553
+ httpRequest: HttpRequest,
554
+ mutations: Mutation<R>[] | Patch | ObservablePatch | Transaction | ObservableTransaction,
555
+ options?:
556
+ | AllDocumentIdsMutationOptions
557
+ | AllDocumentsMutationOptions
558
+ | BaseMutationOptions
559
+ | FirstDocumentIdMutationOptions
560
+ | FirstDocumentMutationOptions,
561
+ ): Observable<
562
+ SanityDocument<R> | SanityDocument<R>[] | SingleMutationResult | MultipleMutationResult
563
+ > {
564
+ return _observe(options?.signal, (signal) =>
565
+ _mutate<R>(client, httpRequest, mutations, {...options, signal}),
566
+ )
567
+ }
568
+
569
+ /**
570
+ * @internal
571
+ */
572
+ export function _actionObservable(
573
+ client: Client,
574
+ httpRequest: HttpRequest,
575
+ actions: Action | Action[],
576
+ options?: BaseActionOptions,
577
+ ): Observable<SingleActionResult | MultipleActionResult> {
578
+ return _observe(options?.signal, (signal) =>
579
+ _action(client, httpRequest, actions, {...options, signal}),
580
+ )
581
+ }
582
+
583
+ /**
584
+ * Build the transport request options for a data endpoint (`query` / `mutate`
585
+ * / `actions`). Shared by the observable and promise request paths.
586
+ *
587
+ * @internal
588
+ */
589
+ function _dataRequestOptions(
590
+ client: Client,
591
+ endpoint: string,
592
+ body: Any,
593
+ options: Any = {},
594
+ ): {reqOptions: Any; isMutation: boolean; returnFirst: boolean} {
595
+ const isMutation = endpoint === 'mutate'
596
+ const isAction = endpoint === 'actions'
597
+ const isQuery = endpoint === 'query'
598
+
599
+ // Check if the query string is within a configured threshold,
600
+ // in which case we can use GET. Otherwise, use POST.
601
+ const strQuery = isMutation || isAction ? '' : encodeQueryString(body)
602
+ const useGet = !isMutation && !isAction && strQuery.length < getQuerySizeLimit
603
+ const stringQuery = useGet ? strQuery : ''
604
+ const returnFirst = options.returnFirst
605
+ const {timeout, token, tag, headers, returnQuery, lastLiveEventId, cacheMode} = options
606
+
607
+ const url = _getDataUrl(client, endpoint, stringQuery)
608
+
609
+ const reqOptions = {
610
+ method: useGet ? 'GET' : 'POST',
611
+ url,
612
+ body: useGet ? undefined : body,
613
+ query: isMutation && getMutationQuery(options),
614
+ timeout,
615
+ headers,
616
+ token,
617
+ tag,
618
+ returnQuery,
619
+ perspective: options.perspective,
620
+ variant: options.variant,
621
+ resultSourceMap: options.resultSourceMap,
622
+ lastLiveEventId: Array.isArray(lastLiveEventId) ? lastLiveEventId[0] : lastLiveEventId,
623
+ cacheMode: cacheMode,
624
+ canUseCdn: isQuery,
625
+ signal: options.signal,
626
+ fetch: options.fetch,
627
+ useAbortSignal: options.useAbortSignal,
628
+ useCdn: options.useCdn,
629
+ }
630
+
631
+ return {reqOptions, isMutation, returnFirst}
632
+ }
633
+
634
+ /**
635
+ * Shape the raw response body of a data request. For mutations this reduces
636
+ * the API response to documents or ids; everything else passes through. Pure
637
+ * function shared by the observable and promise request paths.
638
+ *
639
+ * @internal
640
+ */
641
+ function _mapDataResponse(
642
+ res: Any,
643
+ isMutation: boolean,
644
+ returnFirst: boolean,
645
+ returnDocuments: boolean,
646
+ ): Any {
647
+ if (!isMutation) {
648
+ return res
649
+ }
650
+
651
+ // Should we return documents?
652
+ const results = res.results || []
653
+ if (returnDocuments) {
654
+ return returnFirst ? results[0] && results[0].document : results.map((mut: Any) => mut.document)
655
+ }
656
+
657
+ // Return a reduced subset
658
+ const key = returnFirst ? 'documentId' : 'documentIds'
659
+ const ids = returnFirst ? results[0] && results[0].id : results.map((mut: Any) => mut.id)
660
+ return {
661
+ transactionId: res.transactionId,
662
+ results: results,
663
+ [key]: ids,
664
+ }
665
+ }
666
+
667
+ /**
668
+ * @internal
669
+ */
670
+ export function _dataRequestObservable(
671
+ client: Client,
672
+ httpRequest: HttpRequest,
673
+ endpoint: string,
674
+ body: Any,
675
+ options: Any = {},
676
+ ): Observable<Any> {
677
+ return _observe(options.signal, (signal) =>
678
+ _dataRequest(client, httpRequest, endpoint, body, {...options, signal}),
679
+ )
680
+ }
681
+
682
+ /**
683
+ * Promise-based sibling of {@link _dataRequestObservable}.
684
+ *
685
+ * @internal
686
+ */
687
+ export function _dataRequest(
688
+ client: Client,
689
+ httpRequest: HttpRequest,
690
+ endpoint: string,
691
+ body: Any,
692
+ options: Any = {},
693
+ ): Promise<Any> {
694
+ const {reqOptions, isMutation, returnFirst} = _dataRequestOptions(client, endpoint, body, options)
695
+ return _request(client, httpRequest, reqOptions).then((res: Any) =>
696
+ _mapDataResponse(res, isMutation, returnFirst, options.returnDocuments),
697
+ )
698
+ }
699
+
700
+ /**
701
+ * @internal
702
+ */
703
+ export function _createObservable<R extends Record<string, Any>>(
704
+ client: Client,
705
+ httpRequest: HttpRequest,
706
+ doc: Any,
707
+ op: Any,
708
+ options: Any = {},
709
+ ): Observable<
710
+ SanityDocument<R> | SanityDocument<R>[] | SingleMutationResult | MultipleMutationResult
711
+ > {
712
+ return _observe(options.signal, (signal) =>
713
+ _create<R>(client, httpRequest, doc, op, {...options, signal}),
714
+ )
715
+ }
716
+
717
+ /**
718
+ * Promise-based sibling of {@link _createObservable}.
719
+ *
720
+ * @internal
721
+ */
722
+ export function _create<R extends Record<string, Any>>(
723
+ client: Client,
724
+ httpRequest: HttpRequest,
725
+ doc: Any,
726
+ op: Any,
727
+ options: Any = {},
728
+ ): Promise<
729
+ SanityDocument<R> | SanityDocument<R>[] | SingleMutationResult | MultipleMutationResult
730
+ > {
731
+ const mutation = {[op]: doc}
732
+ const opts = Object.assign({returnFirst: true, returnDocuments: true}, options)
733
+ return _dataRequest(client, httpRequest, 'mutate', {mutations: [mutation]}, opts)
734
+ }
735
+
736
+ /**
737
+ * Promise-based sibling of {@link _actionObservable}.
738
+ *
739
+ * @internal
740
+ */
741
+ export function _action(
742
+ client: Client,
743
+ httpRequest: HttpRequest,
744
+ actions: Action | Action[],
745
+ options?: BaseActionOptions,
746
+ ): Promise<SingleActionResult | MultipleActionResult> {
747
+ const acts = Array.isArray(actions) ? actions : [actions]
748
+ const transactionId = (options && options.transactionId) || undefined
749
+ const skipCrossDatasetReferenceValidation =
750
+ (options && options.skipCrossDatasetReferenceValidation) || undefined
751
+ const dryRun = (options && options.dryRun) || undefined
752
+
753
+ return _dataRequest(
754
+ client,
755
+ httpRequest,
756
+ 'actions',
757
+ {actions: acts, transactionId, skipCrossDatasetReferenceValidation, dryRun},
758
+ options,
759
+ )
760
+ }
761
+
762
+ /**
763
+ * Promise-based sibling of {@link _mutateObservable}.
764
+ *
765
+ * @internal
766
+ */
767
+ export function _mutate<R extends Record<string, Any>>(
768
+ client: Client,
769
+ httpRequest: HttpRequest,
770
+ mutations: Mutation<R>[] | Patch | ObservablePatch | Transaction | ObservableTransaction,
771
+ options?:
772
+ | AllDocumentIdsMutationOptions
773
+ | AllDocumentsMutationOptions
774
+ | BaseMutationOptions
775
+ | FirstDocumentIdMutationOptions
776
+ | FirstDocumentMutationOptions,
777
+ ): Promise<
778
+ SanityDocument<R> | SanityDocument<R>[] | SingleMutationResult | MultipleMutationResult
779
+ > {
780
+ let mut: Mutation | Mutation[]
781
+ if (mutations instanceof Patch || mutations instanceof ObservablePatch) {
782
+ mut = {patch: mutations.serialize()}
783
+ } else if (mutations instanceof Transaction || mutations instanceof ObservableTransaction) {
784
+ mut = mutations.serialize()
785
+ } else {
786
+ mut = mutations
251
787
  }
252
788
 
253
- const fetchBatch = (batchIds: string[]) =>
254
- _requestObservable<Any>(client, httpRequest, {
255
- uri: _getDataUrl(client, 'doc', batchIds.map(encodeURIComponent).join(',')),
256
- tag: opts.tag,
257
- signal: opts.signal,
258
- query: {excludeContent: true},
259
- }).pipe(
260
- filter(isResponse),
261
- map((event: Any) => {
262
- const missing = new Set<string>()
263
- for (const omitted of event.body.omitted || []) {
264
- if (omitted.reason !== 'existence') continue
265
- missing.add(omitted.id)
266
- }
267
- return new Set(batchIds.filter((id) => !missing.has(id)))
268
- }),
269
- )
270
-
271
- return from(batches).pipe(
272
- concatMap(fetchBatch),
273
- reduce((acc, set) => {
274
- for (const id of set) acc.add(id)
275
- return acc
276
- }, new Set<string>()),
277
- )
789
+ const muts = Array.isArray(mut) ? mut : [mut]
790
+ const transactionId = (options && options.transactionId) || undefined
791
+ return _dataRequest(client, httpRequest, 'mutate', {mutations: muts, transactionId}, options)
278
792
  }
279
793
 
280
- /** @internal */
281
- export function _getReleaseDocuments<R extends Record<string, Any>>(
282
- client: ObservableSanityClient | SanityClient,
794
+ /**
795
+ * Promise-based sibling of {@link _deleteObservable}.
796
+ *
797
+ * @internal
798
+ */
799
+ export function _delete<R extends Record<string, Any>>(
800
+ client: Client,
283
801
  httpRequest: HttpRequest,
284
- releaseId: string,
285
- opts: BaseMutationOptions = {},
286
- ): Observable<RawQueryResponse<SanityDocument<R>[]>> {
802
+ selection: string | MutationSelection,
803
+ options?:
804
+ | AllDocumentIdsMutationOptions
805
+ | AllDocumentsMutationOptions
806
+ | BaseMutationOptions
807
+ | FirstDocumentIdMutationOptions
808
+ | FirstDocumentMutationOptions,
809
+ ): Promise<
810
+ SanityDocument<R> | SanityDocument<R>[] | SingleMutationResult | MultipleMutationResult
811
+ > {
287
812
  return _dataRequest(
288
813
  client,
289
814
  httpRequest,
290
- 'query',
291
- {
292
- query: '*[sanity::partOfRelease($releaseId)]',
293
- params: {
294
- releaseId,
295
- },
296
- },
297
- opts,
815
+ 'mutate',
816
+ {mutations: [{delete: getSelection(selection)}]},
817
+ options,
298
818
  )
299
819
  }
300
820
 
301
- /** @internal */
821
+ /**
822
+ * Promise-based sibling of {@link _createIfNotExistsObservable}.
823
+ *
824
+ * @internal
825
+ */
302
826
  export function _createIfNotExists<R extends Record<string, Any>>(
303
827
  client: Client,
304
828
  httpRequest: HttpRequest,
@@ -309,14 +833,18 @@ export function _createIfNotExists<R extends Record<string, Any>>(
309
833
  | BaseMutationOptions
310
834
  | FirstDocumentIdMutationOptions
311
835
  | FirstDocumentMutationOptions,
312
- ): Observable<
836
+ ): Promise<
313
837
  SanityDocument<R> | SanityDocument<R>[] | SingleMutationResult | MultipleMutationResult
314
838
  > {
315
839
  validators.requireDocumentId('createIfNotExists', doc)
316
840
  return _create<R>(client, httpRequest, doc, 'createIfNotExists', options)
317
841
  }
318
842
 
319
- /** @internal */
843
+ /**
844
+ * Promise-based sibling of {@link _createOrReplaceObservable}.
845
+ *
846
+ * @internal
847
+ */
320
848
  export function _createOrReplace<R extends Record<string, Any>>(
321
849
  client: Client,
322
850
  httpRequest: HttpRequest,
@@ -327,21 +855,25 @@ export function _createOrReplace<R extends Record<string, Any>>(
327
855
  | BaseMutationOptions
328
856
  | FirstDocumentIdMutationOptions
329
857
  | FirstDocumentMutationOptions,
330
- ): Observable<
858
+ ): Promise<
331
859
  SanityDocument<R> | SanityDocument<R>[] | SingleMutationResult | MultipleMutationResult
332
860
  > {
333
861
  validators.requireDocumentId('createOrReplace', doc)
334
862
  return _create<R>(client, httpRequest, doc, 'createOrReplace', options)
335
863
  }
336
864
 
337
- /** @internal */
865
+ /**
866
+ * Promise-based sibling of {@link _createVersionObservable}.
867
+ *
868
+ * @internal
869
+ */
338
870
  export function _createVersion<R extends Record<string, Any>>(
339
871
  client: ObservableSanityClient | SanityClient,
340
872
  httpRequest: HttpRequest,
341
873
  doc: IdentifiedSanityDocumentStub<R>,
342
874
  publishedId: string,
343
875
  options?: BaseActionOptions,
344
- ): Observable<SingleActionResult> {
876
+ ): Promise<SingleActionResult> {
345
877
  validators.requireDocumentId('createVersion', doc)
346
878
  validators.requireDocumentType('createVersion', doc)
347
879
  printCreateVersionWithBaseIdWarning()
@@ -355,7 +887,11 @@ export function _createVersion<R extends Record<string, Any>>(
355
887
  return _action(client, httpRequest, createVersionAction, options)
356
888
  }
357
889
 
358
- /** @internal */
890
+ /**
891
+ * Promise-based sibling of {@link _createVersionFromBaseObservable}.
892
+ *
893
+ * @internal
894
+ */
359
895
  export function _createVersionFromBase(
360
896
  client: ObservableSanityClient | SanityClient,
361
897
  httpRequest: HttpRequest,
@@ -364,7 +900,7 @@ export function _createVersionFromBase(
364
900
  releaseId?: string,
365
901
  ifBaseRevisionId?: string,
366
902
  options?: BaseActionOptions,
367
- ): Observable<SingleActionResult> {
903
+ ): Promise<SingleActionResult> {
368
904
  if (!baseId) {
369
905
  throw new Error('`createVersion()` requires `baseId` when no `document` is provided')
370
906
  }
@@ -387,37 +923,18 @@ export function _createVersionFromBase(
387
923
  return _action(client, httpRequest, createVersionAction, options)
388
924
  }
389
925
 
390
- /** @internal */
391
- export function _delete<R extends Record<string, Any>>(
392
- client: Client,
393
- httpRequest: HttpRequest,
394
- selection: string | MutationSelection,
395
- options?:
396
- | AllDocumentIdsMutationOptions
397
- | AllDocumentsMutationOptions
398
- | BaseMutationOptions
399
- | FirstDocumentIdMutationOptions
400
- | FirstDocumentMutationOptions,
401
- ): Observable<
402
- SanityDocument<R> | SanityDocument<R>[] | SingleMutationResult | MultipleMutationResult
403
- > {
404
- return _dataRequest(
405
- client,
406
- httpRequest,
407
- 'mutate',
408
- {mutations: [{delete: getSelection(selection)}]},
409
- options,
410
- )
411
- }
412
-
413
- /** @internal */
926
+ /**
927
+ * Promise-based sibling of {@link _discardVersionObservable}.
928
+ *
929
+ * @internal
930
+ */
414
931
  export function _discardVersion(
415
932
  client: ObservableSanityClient | SanityClient,
416
933
  httpRequest: HttpRequest,
417
934
  versionId: string,
418
935
  purge: boolean = false,
419
936
  options?: BaseActionOptions,
420
- ): Observable<SingleActionResult> {
937
+ ): Promise<SingleActionResult> {
421
938
  const discardVersionAction: DiscardVersionAction = {
422
939
  actionType: 'sanity.action.document.version.discard',
423
940
  versionId,
@@ -427,13 +944,17 @@ export function _discardVersion(
427
944
  return _action(client, httpRequest, discardVersionAction, options)
428
945
  }
429
946
 
430
- /** @internal */
947
+ /**
948
+ * Promise-based sibling of {@link _replaceVersionObservable}.
949
+ *
950
+ * @internal
951
+ */
431
952
  export function _replaceVersion<R extends Record<string, Any>>(
432
953
  client: ObservableSanityClient | SanityClient,
433
954
  httpRequest: HttpRequest,
434
955
  doc: IdentifiedSanityDocumentStub<R>,
435
956
  options?: BaseActionOptions,
436
- ): Observable<SingleActionResult> {
957
+ ): Promise<SingleActionResult> {
437
958
  validators.requireDocumentId('replaceVersion', doc)
438
959
  validators.requireDocumentType('replaceVersion', doc)
439
960
 
@@ -445,14 +966,18 @@ export function _replaceVersion<R extends Record<string, Any>>(
445
966
  return _action(client, httpRequest, replaceVersionAction, options)
446
967
  }
447
968
 
448
- /** @internal */
969
+ /**
970
+ * Promise-based sibling of {@link _unpublishVersionObservable}.
971
+ *
972
+ * @internal
973
+ */
449
974
  export function _unpublishVersion(
450
975
  client: ObservableSanityClient | SanityClient,
451
976
  httpRequest: HttpRequest,
452
977
  versionId: string,
453
978
  publishedId: string,
454
979
  options?: BaseActionOptions,
455
- ): Observable<SingleActionResult> {
980
+ ): Promise<SingleActionResult> {
456
981
  const unpublishVersionAction: UnpublishVersionAction = {
457
982
  actionType: 'sanity.action.document.version.unpublish',
458
983
  versionId,
@@ -462,150 +987,6 @@ export function _unpublishVersion(
462
987
  return _action(client, httpRequest, unpublishVersionAction, options)
463
988
  }
464
989
 
465
- /** @internal */
466
- export function _mutate<R extends Record<string, Any>>(
467
- client: Client,
468
- httpRequest: HttpRequest,
469
- mutations: Mutation<R>[] | Patch | ObservablePatch | Transaction | ObservableTransaction,
470
- options?:
471
- | AllDocumentIdsMutationOptions
472
- | AllDocumentsMutationOptions
473
- | BaseMutationOptions
474
- | FirstDocumentIdMutationOptions
475
- | FirstDocumentMutationOptions,
476
- ): Observable<
477
- SanityDocument<R> | SanityDocument<R>[] | SingleMutationResult | MultipleMutationResult
478
- > {
479
- let mut: Mutation | Mutation[]
480
- if (mutations instanceof Patch || mutations instanceof ObservablePatch) {
481
- mut = {patch: mutations.serialize()}
482
- } else if (mutations instanceof Transaction || mutations instanceof ObservableTransaction) {
483
- mut = mutations.serialize()
484
- } else {
485
- mut = mutations
486
- }
487
-
488
- const muts = Array.isArray(mut) ? mut : [mut]
489
- const transactionId = (options && options.transactionId) || undefined
490
- return _dataRequest(client, httpRequest, 'mutate', {mutations: muts, transactionId}, options)
491
- }
492
-
493
- /**
494
- * @internal
495
- */
496
- export function _action(
497
- client: Client,
498
- httpRequest: HttpRequest,
499
- actions: Action | Action[],
500
- options?: BaseActionOptions,
501
- ): Observable<SingleActionResult | MultipleActionResult> {
502
- const acts = Array.isArray(actions) ? actions : [actions]
503
- const transactionId = (options && options.transactionId) || undefined
504
- const skipCrossDatasetReferenceValidation =
505
- (options && options.skipCrossDatasetReferenceValidation) || undefined
506
- const dryRun = (options && options.dryRun) || undefined
507
-
508
- return _dataRequest(
509
- client,
510
- httpRequest,
511
- 'actions',
512
- {actions: acts, transactionId, skipCrossDatasetReferenceValidation, dryRun},
513
- options,
514
- )
515
- }
516
-
517
- /**
518
- * @internal
519
- */
520
- export function _dataRequest(
521
- client: Client,
522
- httpRequest: HttpRequest,
523
- endpoint: string,
524
- body: Any,
525
- options: Any = {},
526
- ): Any {
527
- const isMutation = endpoint === 'mutate'
528
- const isAction = endpoint === 'actions'
529
- const isQuery = endpoint === 'query'
530
-
531
- // Check if the query string is within a configured threshold,
532
- // in which case we can use GET. Otherwise, use POST.
533
- const strQuery = isMutation || isAction ? '' : encodeQueryString(body)
534
- const useGet = !isMutation && !isAction && strQuery.length < getQuerySizeLimit
535
- const stringQuery = useGet ? strQuery : ''
536
- const returnFirst = options.returnFirst
537
- const {timeout, token, tag, headers, returnQuery, lastLiveEventId, cacheMode} = options
538
-
539
- const uri = _getDataUrl(client, endpoint, stringQuery)
540
-
541
- const reqOptions = {
542
- method: useGet ? 'GET' : 'POST',
543
- uri: uri,
544
- json: true,
545
- body: useGet ? undefined : body,
546
- query: isMutation && getMutationQuery(options),
547
- timeout,
548
- headers,
549
- token,
550
- tag,
551
- returnQuery,
552
- perspective: options.perspective,
553
- variant: options.variant,
554
- resultSourceMap: options.resultSourceMap,
555
- lastLiveEventId: Array.isArray(lastLiveEventId) ? lastLiveEventId[0] : lastLiveEventId,
556
- cacheMode: cacheMode,
557
- canUseCdn: isQuery,
558
- signal: options.signal,
559
- fetch: options.fetch,
560
- useAbortSignal: options.useAbortSignal,
561
- useCdn: options.useCdn,
562
- }
563
-
564
- return _requestObservable(client, httpRequest, reqOptions).pipe(
565
- filter(isResponse),
566
- map(getBody),
567
- map((res) => {
568
- if (!isMutation) {
569
- return res
570
- }
571
-
572
- // Should we return documents?
573
- const results = res.results || []
574
- if (options.returnDocuments) {
575
- return returnFirst
576
- ? results[0] && results[0].document
577
- : results.map((mut: Any) => mut.document)
578
- }
579
-
580
- // Return a reduced subset
581
- const key = returnFirst ? 'documentId' : 'documentIds'
582
- const ids = returnFirst ? results[0] && results[0].id : results.map((mut: Any) => mut.id)
583
- return {
584
- transactionId: res.transactionId,
585
- results: results,
586
- [key]: ids,
587
- }
588
- }),
589
- )
590
- }
591
-
592
- /**
593
- * @internal
594
- */
595
- export function _create<R extends Record<string, Any>>(
596
- client: Client,
597
- httpRequest: HttpRequest,
598
- doc: Any,
599
- op: Any,
600
- options: Any = {},
601
- ): Observable<
602
- SanityDocument<R> | SanityDocument<R>[] | SingleMutationResult | MultipleMutationResult
603
- > {
604
- const mutation = {[op]: doc}
605
- const opts = Object.assign({returnFirst: true, returnDocuments: true}, options)
606
- return _dataRequest(client, httpRequest, 'mutate', {mutations: [mutation]}, opts)
607
- }
608
-
609
990
  const hasDataConfig = (client: Client) => {
610
991
  const config = client.config()
611
992
  return (
@@ -638,19 +1019,23 @@ const isData = (client: Client, uri: string) =>
638
1019
  isHistory(client, uri)
639
1020
 
640
1021
  /**
1022
+ * Build the final request options (URL, headers, query params, etc.) used by
1023
+ * both the regular request pipeline and the asset upload path.
1024
+ *
641
1025
  * @internal
642
1026
  */
643
- export function _requestObservable<R>(
644
- client: Client,
645
- httpRequest: HttpRequest,
646
- options: RequestObservableOptions,
647
- ): Observable<HttpRequestEvent<R>> {
648
- // Capture the call-site error here, where the caller's code is still
649
- // on the synchronous call stack, and pass it through to get-it so errors
650
- // include the originating call site rather than just internal plumbing.
651
- const callSiteStack = new Error()
1027
+ export function _prepareRequest(client: Client, options: RequestObservableOptions): FetchRequest {
1028
+ if (options.uri !== undefined) {
1029
+ printDeprecatedUriOptionWarning()
1030
+ }
652
1031
 
653
- const uri = options.url || (options.uri as string)
1032
+ // `uri` takes precedence over `url` to match the v8 behaviour this alias
1033
+ // restores. The types make passing both an error, so this only matters to
1034
+ // untyped callers.
1035
+ const uri = options.uri || options.url
1036
+ if (typeof uri !== 'string') {
1037
+ throw new TypeError('Request options must include a `url`')
1038
+ }
654
1039
  const config = client.config()
655
1040
 
656
1041
  // If the `canUseCdn`-option is not set we detect it automatically based on the method + URL.
@@ -731,31 +1116,114 @@ export function _requestObservable<R>(
731
1116
  }
732
1117
  }
733
1118
 
734
- const reqOptions = requestOptions(
1119
+ return requestOptions(
735
1120
  config,
736
1121
  Object.assign({}, options, {
737
1122
  url: _getUrl(client, uri, useCdn),
738
- callSiteStack,
739
1123
  }),
740
- ) as RequestOptions
741
-
742
- const request = new Observable<HttpRequestEvent<R>>((subscriber) =>
743
- httpRequest(reqOptions, config.requester!).subscribe(subscriber),
744
1124
  )
1125
+ }
745
1126
 
746
- return options.signal ? request.pipe(_withAbortSignal(options.signal)) : request
1127
+ /**
1128
+ * Wrap a promise-returning request in a cold, single-value Observable.
1129
+ *
1130
+ * Each subscription invokes `run` with a fresh `AbortSignal` that is aborted
1131
+ * when the subscriber unsubscribes — so unsubscribing cancels the in-flight
1132
+ * `fetch`. A caller-supplied `userSignal` is chained in too, so the request
1133
+ * still aborts when the caller's own signal fires. The single resolved value
1134
+ * (or rejection) is forwarded to the subscriber.
1135
+ *
1136
+ * This is the bridge that lets the observable client surface reuse the exact
1137
+ * same promise implementations as the promise client surface, with no
1138
+ * duplicated request logic.
1139
+ *
1140
+ * @internal
1141
+ */
1142
+ export function _observe<R>(
1143
+ userSignal: AbortSignal | undefined,
1144
+ run: (signal: AbortSignal) => Promise<R>,
1145
+ ): Observable<R> {
1146
+ return new Observable<R>((subscriber) => {
1147
+ const controller = new AbortController()
1148
+ // `AbortSignal.any` rather than an `addEventListener` on the caller's
1149
+ // signal: that signal can be long-lived and reused across many requests,
1150
+ // and a listener per subscription would accumulate there (`{once: true}`
1151
+ // only cleans up if the signal actually fires).
1152
+ const signal = userSignal ? AbortSignal.any([userSignal, controller.signal]) : controller.signal
1153
+ run(signal).then(
1154
+ (value) => {
1155
+ subscriber.next(value)
1156
+ subscriber.complete()
1157
+ },
1158
+ (err) => subscriber.error(err),
1159
+ )
1160
+ return () => controller.abort()
1161
+ })
1162
+ }
1163
+
1164
+ /**
1165
+ * Promise-based sibling of {@link _requestObservable}. Resolves directly to
1166
+ * the parsed response body without ever constructing an Observable. The abort
1167
+ * signal is honored by the underlying `fetch` (it is threaded onto the request
1168
+ * options), so no RxJS-level teardown is required.
1169
+ *
1170
+ * @internal
1171
+ */
1172
+ export function _request<R>(client: Client, httpRequest: HttpRequest, options: Any): Promise<R> {
1173
+ const reqOptions = _prepareRequest(client, options)
1174
+ return httpRequest(reqOptions).then((body) => body as R)
747
1175
  }
748
1176
 
749
1177
  /**
1178
+ * Execute an HTTP request through the regular pipeline and resolve to the
1179
+ * parsed response body. Observable wrapper over {@link _request}.
1180
+ *
750
1181
  * @internal
751
1182
  */
752
- export function _request<R>(client: Client, httpRequest: HttpRequest, options: Any): Observable<R> {
753
- const observable = _requestObservable<R>(client, httpRequest, options).pipe(
754
- filter((event: Any) => event.type === 'response'),
755
- map((event: Any) => event.body),
1183
+ export function _requestObservable<R>(
1184
+ client: Client,
1185
+ httpRequest: HttpRequest,
1186
+ options: Any,
1187
+ ): Observable<R> {
1188
+ return _observe(options.signal, (signal) =>
1189
+ _request<R>(client, httpRequest, {...options, signal}),
756
1190
  )
1191
+ }
757
1192
 
758
- return observable
1193
+ /**
1194
+ * Execute an asset upload through the underlying requester directly,
1195
+ * exposing the full upload event stream (progress + response).
1196
+ *
1197
+ * Bypasses the body-only `HttpRequest` boundary so progress events from the
1198
+ * transport layer can surface to consumers, reading the resolved requester
1199
+ * straight off the initialized config.
1200
+ *
1201
+ * @internal
1202
+ */
1203
+ export function _uploadObservable<T>(
1204
+ client: Client,
1205
+ options: RequestObservableOptions,
1206
+ ): Observable<UploadEvent<T>> {
1207
+ const reqOptions = _prepareRequest(client, options)
1208
+ const requester = client.config().requester
1209
+ const request = new Observable<Any>((subscriber) =>
1210
+ requester(reqOptions).subscribe(subscriber),
1211
+ ).pipe(
1212
+ filter((event: Any) => event?.type === 'progress' || event?.type === 'response'),
1213
+ map((event: Any): UploadEvent<T> =>
1214
+ event.type === 'progress'
1215
+ ? {
1216
+ type: 'progress',
1217
+ stage: event.stage,
1218
+ percent: event.percent,
1219
+ total: event.total,
1220
+ loaded: event.loaded,
1221
+ lengthComputable: event.lengthComputable,
1222
+ }
1223
+ : {type: 'response', body: event.body as T},
1224
+ ),
1225
+ )
1226
+ return options.signal ? request.pipe(_withAbortSignal(options.signal)) : request
759
1227
  }
760
1228
 
761
1229
  /**
@@ -806,30 +1274,14 @@ function _withAbortSignal<T>(signal: AbortSignal): MonoTypeOperatorFunction<T> {
806
1274
  })
807
1275
  }
808
1276
  }
809
- // DOMException is supported on most modern browsers and Node.js 18+.
810
- // @see https://developer.mozilla.org/en-US/docs/Web/API/DOMException#browser_compatibility
811
- const isDomExceptionSupported = Boolean(globalThis.DOMException)
812
-
813
1277
  /**
1278
+ * `DOMException` is globally available in every supported runtime
1279
+ * (Node 22.12+ and all modern browsers), so we can construct one directly.
1280
+ *
814
1281
  * @internal
815
- * @param signal - The abort signal to use.
816
- * Original source copied from https://github.com/sindresorhus/ky/blob/740732c78aad97e9aec199e9871bdbf0ae29b805/source/errors/DOMException.ts
817
- * TODO: When targeting Node.js 18, use `signal.throwIfAborted()` (https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/throwIfAborted)
818
1282
  */
819
- function _createAbortError(signal?: AbortSignal) {
820
- /*
821
- NOTE: Use DomException with AbortError name as specified in MDN docs (https://developer.mozilla.org/en-US/docs/Web/API/AbortController/abort)
822
- > When abort() is called, the fetch() promise rejects with an Error of type DOMException, with name AbortError.
823
- */
824
- if (isDomExceptionSupported) {
825
- return new DOMException(signal?.reason ?? 'The operation was aborted.', 'AbortError')
826
- }
827
-
828
- // DOMException not supported. Fall back to use of error and override name.
829
- const error = new Error(signal?.reason ?? 'The operation was aborted.')
830
- error.name = 'AbortError'
831
-
832
- return error
1283
+ function _createAbortError(signal?: AbortSignal): DOMException {
1284
+ return new DOMException(signal?.reason ?? 'The operation was aborted.', 'AbortError')
833
1285
  }
834
1286
 
835
1287
  const resourceDataBase = (config: InitializedClientConfig): string => {