@sanity/client 7.1.0-views.0 → 7.1.0-views.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/README.md +668 -40
  2. package/dist/_chunks-cjs/config.cjs +14 -0
  3. package/dist/_chunks-cjs/config.cjs.map +1 -1
  4. package/dist/_chunks-cjs/dataMethods.cjs +197 -32
  5. package/dist/_chunks-cjs/dataMethods.cjs.map +1 -1
  6. package/dist/_chunks-cjs/isRecord.cjs +6 -0
  7. package/dist/_chunks-cjs/isRecord.cjs.map +1 -0
  8. package/dist/_chunks-cjs/resolveEditInfo.cjs +3 -5
  9. package/dist/_chunks-cjs/resolveEditInfo.cjs.map +1 -1
  10. package/dist/_chunks-cjs/stegaClean.cjs +4 -0
  11. package/dist/_chunks-cjs/stegaClean.cjs.map +1 -1
  12. package/dist/_chunks-cjs/stegaEncodeSourceMap.cjs +2 -5
  13. package/dist/_chunks-cjs/stegaEncodeSourceMap.cjs.map +1 -1
  14. package/dist/_chunks-es/config.js +15 -1
  15. package/dist/_chunks-es/config.js.map +1 -1
  16. package/dist/_chunks-es/dataMethods.js +200 -33
  17. package/dist/_chunks-es/dataMethods.js.map +1 -1
  18. package/dist/_chunks-es/isRecord.js +7 -0
  19. package/dist/_chunks-es/isRecord.js.map +1 -0
  20. package/dist/_chunks-es/resolveEditInfo.js +1 -3
  21. package/dist/_chunks-es/resolveEditInfo.js.map +1 -1
  22. package/dist/_chunks-es/stegaClean.js +4 -0
  23. package/dist/_chunks-es/stegaClean.js.map +1 -1
  24. package/dist/_chunks-es/stegaEncodeSourceMap.js +1 -4
  25. package/dist/_chunks-es/stegaEncodeSourceMap.js.map +1 -1
  26. package/dist/index.browser.cjs +1019 -59
  27. package/dist/index.browser.cjs.map +1 -1
  28. package/dist/index.browser.d.cts +1948 -149
  29. package/dist/index.browser.d.ts +1948 -149
  30. package/dist/index.browser.js +1021 -60
  31. package/dist/index.browser.js.map +1 -1
  32. package/dist/index.cjs +825 -29
  33. package/dist/index.cjs.map +1 -1
  34. package/dist/index.d.cts +1948 -149
  35. package/dist/index.d.ts +1948 -149
  36. package/dist/index.js +826 -31
  37. package/dist/index.js.map +1 -1
  38. package/dist/stega.browser.d.cts +1948 -149
  39. package/dist/stega.browser.d.ts +1948 -149
  40. package/dist/stega.d.cts +1948 -149
  41. package/dist/stega.d.ts +1948 -149
  42. package/dist/views.cjs +13 -5
  43. package/dist/views.cjs.map +1 -1
  44. package/dist/views.d.cts +51 -36
  45. package/dist/views.d.ts +51 -36
  46. package/dist/views.js +14 -5
  47. package/dist/views.js.map +1 -1
  48. package/package.json +2 -1
  49. package/src/SanityClient.ts +652 -12
  50. package/src/agent/actions/AgentActionsClient.ts +29 -2
  51. package/src/agent/actions/commonTypes.ts +57 -17
  52. package/src/agent/actions/generate.ts +36 -2
  53. package/src/agent/actions/patch.ts +136 -0
  54. package/src/agent/actions/prompt.ts +145 -0
  55. package/src/agent/actions/transform.ts +105 -7
  56. package/src/agent/actions/translate.ts +5 -2
  57. package/src/config.ts +3 -1
  58. package/src/csm/walkMap.ts +1 -1
  59. package/src/data/dataMethods.ts +170 -12
  60. package/src/data/encodeQueryString.ts +1 -1
  61. package/src/data/eventsource.ts +16 -7
  62. package/src/data/listen.ts +10 -4
  63. package/src/data/live.ts +13 -5
  64. package/src/datasets/DatasetsClient.ts +4 -1
  65. package/src/defineCreateClient.ts +7 -1
  66. package/src/http/errors.ts +92 -27
  67. package/src/http/request.ts +3 -3
  68. package/src/http/requestOptions.ts +4 -0
  69. package/src/projects/ProjectsClient.ts +6 -2
  70. package/src/releases/ReleasesClient.ts +693 -0
  71. package/src/releases/createRelease.ts +53 -0
  72. package/src/types.ts +291 -10
  73. package/src/users/UsersClient.ts +7 -3
  74. package/src/util/codeFrame.ts +174 -0
  75. package/src/util/createVersionId.ts +79 -0
  76. package/src/{csm → util}/isRecord.ts +1 -1
  77. package/src/validators.ts +23 -1
  78. package/src/views/index.ts +51 -15
  79. package/umd/sanityClient.js +1067 -61
  80. package/umd/sanityClient.min.js +2 -2
@@ -2,7 +2,13 @@ import {type Observable} from 'rxjs'
2
2
 
3
3
  import {_request} from '../../data/dataMethods'
4
4
  import type {ObservableSanityClient, SanityClient} from '../../SanityClient'
5
- import type {AgentActionParams, Any, HttpRequest, IdentifiedSanityDocumentStub} from '../../types'
5
+ import type {
6
+ AgentActionParams,
7
+ AgentActionPath,
8
+ Any,
9
+ HttpRequest,
10
+ IdentifiedSanityDocumentStub,
11
+ } from '../../types'
6
12
  import {hasDataset} from '../../validators'
7
13
  import type {
8
14
  AgentActionAsync,
@@ -20,6 +26,8 @@ export interface TransformRequestBase extends AgentActionRequestBase {
20
26
 
21
27
  /**
22
28
  * The source document the transformation will use as input.
29
+ *
30
+ * @see #AgentActionSchema.forcePublishedWrite
23
31
  */
24
32
  documentId: string
25
33
 
@@ -28,13 +36,15 @@ export interface TransformRequestBase extends AgentActionRequestBase {
28
36
  * then it is transformed according to the instruction.
29
37
  *
30
38
  * When omitted, the source document (documentId) is also the target document.
39
+ *
40
+ * @see #AgentActionSchema.forcePublishedWrite
31
41
  */
32
42
  targetDocument?: TransformTargetDocument
33
43
 
34
44
  /**
35
45
  * Instruct the LLM how to transform the input to th output.
36
46
  *
37
- * String template using $variable from instructionParams.
47
+ * String template with support for $variable from `instructionParams`.
38
48
  *
39
49
  * Capped to 2000 characters, after variables has been injected.
40
50
  * */
@@ -134,24 +144,97 @@ export interface TransformRequestBase extends AgentActionRequestBase {
134
144
  * It is therefor an error to provide conflicting include/exclude across targets (ie, include title in one, and exclude it in another)
135
145
  *
136
146
  * Default max depth for transform: 12
147
+ *
148
+ * ## Transforming images
149
+ *
150
+ * To transform an existing image, directly target an image asset path.
151
+ *
152
+ * For example, all the following will transform the image into the provided asset:
153
+ * * `target: {path: ['image', 'asset'] }`
154
+ * * `target: {path: 'image', include: ['asset'] }`
155
+ *
156
+ * Image transform can be combined with regular content targets:
157
+ * * `target: [{path: ['image', 'asset'] }, {include: ['title', 'description']}]`
158
+ *
159
+ * Image transform can have per-path instructions, just like any other target paths:
160
+ * * `target: [{path: ['image', 'asset'], instruction: 'Make the sky blue' }`
161
+ *
137
162
  * @see AgentActionRequestBase#conditionalPaths
138
163
  */
139
164
  target?: TransformTarget | TransformTarget[]
140
165
  }
141
166
 
142
- /** @beta */
167
+ /**
168
+ * @see #AgentActionSchema.forcePublishedWrite
169
+ *
170
+ * @beta
171
+ */
143
172
  export type TransformTargetDocument =
144
173
  | {operation: 'edit'; _id: string}
145
174
  | {operation: 'create'; _id?: string}
146
175
  | {operation: 'createIfNotExists'; _id: string}
147
176
  | {operation: 'createOrReplace'; _id: string}
148
177
 
149
- /** @beta */
178
+ /**
179
+ *
180
+ * @see #TransformOperation
181
+ * @beta
182
+ */
183
+ export interface ImageDescriptionOperation {
184
+ type: 'image-description'
185
+ /**
186
+ * When omitted, parent image value will be inferred from the arget path.
187
+ *
188
+ * When specified, the `sourcePath` should be a path to an image (or image asset) field:
189
+ * - `['image']`
190
+ * - `['wrapper', 'mainImage']`
191
+ * - `['heroImage', 'asset'] // the asset segment is optional, but supported`
192
+ */
193
+ sourcePath?: AgentActionPath
194
+ }
195
+
196
+ /**
197
+ *
198
+ * ## `set` by default
199
+ * By default, Transform will change the value of every target field in place using a set operation.
200
+ *
201
+ * ## Image description
202
+ *
203
+ * ### Targeting image fields
204
+ * Images can be transformed to a textual description by targeting a `string`, `text` or Portable Text field (`array` with `block`)
205
+ * with `operation: {type: 'image-description'}`.
206
+ *
207
+ * Custom instructions for image description targets will be used to generate the description.
208
+ *
209
+ * Such targets must be a descendant field of an image object.
210
+ *
211
+ * For example:
212
+ * - `target: {path: ['image', 'description'], operation: {type: 'image-description'} }`
213
+ * - `target: {path: ['array', {_key: 'abc'}, 'alt'], operation: {type: 'image-description'} } //assuming the item in the array on the key-ed path is an image`
214
+ * - `target: {path: ['image'], include: ['portableTextField'], operation: {type: 'image-description'}, instruction: 'Use formatting and headings to describe the image in great detail' }`
215
+ *
216
+ * ### Targeting non-image fields
217
+ * If the target image description lives outside an image object, use the `sourcePath` option to specify the path to the image field.
218
+ * `sourcePath` must be an image or image asset field.
219
+ *
220
+ * For example:
221
+ * - `target: {path: ['description'], operation: operation: {type: 'image-description', sourcePath: ['image', 'asset'] }`
222
+ * - `target: {path: ['wrapper', 'title'], operation: {type: 'image-description', sourcePath: ['array', {_key: 'abc'}, 'image'] }`
223
+ * - `target: {path: ['wrapper'], include: ['portableTextField'], operation: {type: 'image-description', sourcePath: ['image', 'asset'] }, instruction: 'Use formatting and headings to describe the image in great detail' }`
224
+ *
225
+ * @beta
226
+ */
227
+ export type TransformOperation = 'set' | ImageDescriptionOperation
228
+
229
+ /**
230
+ * @see #TransformOperation
231
+ * @beta
232
+ * */
150
233
  export interface TransformTargetInclude extends AgentActionTargetInclude {
151
234
  /**
152
235
  * Specifies a tailored instruction of this target.
153
236
  *
154
- * string template using $variable from instructionParams */
237
+ * String template with support for $variable from `instructionParams`. */
155
238
  instruction?: string
156
239
 
157
240
  /**
@@ -162,14 +245,23 @@ export interface TransformTargetInclude extends AgentActionTargetInclude {
162
245
  * Fields or array items not on the include list, are implicitly excluded.
163
246
  */
164
247
  include?: (AgentActionPathSegment | TransformTargetInclude)[]
248
+
249
+ /**
250
+ * Default: `set`
251
+ * @see #TransformOperation
252
+ */
253
+ operation?: TransformOperation
165
254
  }
166
255
 
167
- /** @beta */
256
+ /**
257
+ * @see #TransformOperation
258
+ * @beta
259
+ * */
168
260
  export interface TransformTarget extends AgentActionTarget {
169
261
  /**
170
262
  * Specifies a tailored instruction of this target.
171
263
  *
172
- * string template using $variable from instructionParams.
264
+ * String template with support for $variable from `instructionParams`.
173
265
  * */
174
266
  instruction?: string
175
267
 
@@ -181,6 +273,12 @@ export interface TransformTarget extends AgentActionTarget {
181
273
  * Fields or array items not on the include list, are implicitly excluded.
182
274
  */
183
275
  include?: (AgentActionPathSegment | TransformTargetInclude)[]
276
+
277
+ /**
278
+ * Default: `set`
279
+ * @see #TransformOperation
280
+ */
281
+ operation?: TransformOperation
184
282
  }
185
283
 
186
284
  /** @beta */
@@ -27,6 +27,7 @@ export interface TranslateRequestBase extends AgentActionRequestBase {
27
27
 
28
28
  /**
29
29
  * The source document the transformation will use as input.
30
+ * @see #AgentActionSchema.forcePublishedWrite
30
31
  */
31
32
  documentId: string
32
33
 
@@ -35,6 +36,8 @@ export interface TranslateRequestBase extends AgentActionRequestBase {
35
36
  * then it is translated according to the instruction.
36
37
  *
37
38
  * When omitted, the source document (documentId) is also the target document.
39
+ *
40
+ * @see #AgentActionSchema.forcePublishedWrite
38
41
  */
39
42
  targetDocument?: TransformTargetDocument
40
43
 
@@ -105,7 +108,7 @@ export interface TranslateLanguage {
105
108
 
106
109
  /** @beta */
107
110
  export interface TranslateTargetInclude extends AgentActionTargetInclude {
108
- /** string template using $variable from instructionParams */
111
+ /** String template using $variable from styleGuideParams. */
109
112
  styleGuide?: string
110
113
 
111
114
  /**
@@ -120,7 +123,7 @@ export interface TranslateTargetInclude extends AgentActionTargetInclude {
120
123
 
121
124
  /** @beta */
122
125
  export interface TranslateTarget extends AgentActionTarget {
123
- /** string template using $variable from instructionParams */
126
+ /** String template using $variable from styleGuideParams. */
124
127
  styleGuide?: string
125
128
 
126
129
  /**
package/src/config.ts CHANGED
@@ -154,7 +154,9 @@ export const initConfig = (
154
154
  const protocol = hostParts[0]
155
155
  const host = hostParts[1]
156
156
 
157
- const cdnURL = newConfig.isDefaultApi ? `https://${defaultCdnHost}` : newConfig.apiCdnHost || newConfig.apiHost
157
+ const cdnURL = newConfig.isDefaultApi
158
+ ? `https://${defaultCdnHost}`
159
+ : newConfig.apiCdnHost || newConfig.apiHost
158
160
  const cdnURLParts = cdnURL.split('://', 2)
159
161
  const cdnProtocol = cdnURLParts[0]
160
162
  const cdnHost = cdnURLParts[1]
@@ -1,5 +1,5 @@
1
+ import {isRecord} from '../util/isRecord'
1
2
  import {isArray} from './isArray'
2
- import {isRecord} from './isRecord'
3
3
  import type {ContentSourceMapParsedPath, WalkMapFn} from './types'
4
4
 
5
5
  /**
@@ -1,3 +1,4 @@
1
+ import {getVersionFromId, getVersionId, isDraftId} from '@sanity/client/csm'
1
2
  import {from, type MonoTypeOperatorFunction, Observable} from 'rxjs'
2
3
  import {combineLatestWith, filter, map} from 'rxjs/operators'
3
4
 
@@ -11,6 +12,8 @@ import type {
11
12
  Any,
12
13
  BaseActionOptions,
13
14
  BaseMutationOptions,
15
+ CreateVersionAction,
16
+ DiscardVersionAction,
14
17
  FirstDocumentIdMutationOptions,
15
18
  FirstDocumentMutationOptions,
16
19
  HttpRequest,
@@ -24,11 +27,13 @@ import type {
24
27
  MutationSelection,
25
28
  QueryOptions,
26
29
  RawQueryResponse,
30
+ ReplaceVersionAction,
27
31
  RequestObservableOptions,
28
32
  RequestOptions,
29
33
  SanityDocument,
30
34
  SingleActionResult,
31
35
  SingleMutationResult,
36
+ UnpublishVersionAction,
32
37
  } from '../types'
33
38
  import {getSelection} from '../util/getSelection'
34
39
  import * as validate from '../validators'
@@ -85,12 +90,19 @@ export function _fetch<R, Q>(
85
90
  const mapResponse =
86
91
  options.filterResponse === false ? (res: Any) => res : (res: Any) => res.result
87
92
 
88
- const {cache, next, ...opts} = {
93
+ const {cache, next, useEmulate, connections, ...opts} = {
89
94
  // Opt out of setting a `signal` on an internal `fetch` if one isn't provided.
90
95
  // This is necessary in React Server Components to avoid opting out of Request Memoization.
91
96
  useAbortSignal: typeof options.signal !== 'undefined',
92
97
  // Set `resultSourceMap' when stega is enabled, as it's required for encoding.
93
98
  resultSourceMap: stega.enabled ? 'withKeyArraySelector' : options.resultSourceMap,
99
+
100
+ // Only use emulate if explicitly asked for
101
+ useEmulate: false,
102
+
103
+ // Having connections is a special case for views
104
+ connections: undefined,
105
+
94
106
  ...options,
95
107
  // Default to not returning the query, unless `filterResponse` is `false`,
96
108
  // or `returnQuery` is explicitly set. `true` is the default in Content Lake, so skip if truthy
@@ -101,7 +113,17 @@ export function _fetch<R, Q>(
101
113
  ? {...opts, fetch: {cache, next}}
102
114
  : opts
103
115
 
104
- const $request = _dataRequest(config, httpRequest, 'query', {query, params}, reqOpts)
116
+ // Use 'emulate' endpoint for view emulation, otherwise use 'query'
117
+ const endpoint = useEmulate ? 'emulate' : 'query'
118
+ const requestBody = useEmulate
119
+ ? {
120
+ query,
121
+ params,
122
+ connections: connections,
123
+ }
124
+ : {query, params}
125
+
126
+ const $request = _dataRequest(config, httpRequest, endpoint, requestBody, reqOpts)
105
127
  return stega.enabled
106
128
  ? $request.pipe(
107
129
  combineLatestWith(
@@ -129,10 +151,36 @@ export function _getDocument<R extends Record<string, Any>>(
129
151
  config: InitializedClientConfig,
130
152
  httpRequest: HttpRequest,
131
153
  id: string,
132
- opts: {signal?: AbortSignal; tag?: string} = {},
154
+ opts: {signal?: AbortSignal; tag?: string; releaseId?: string} = {},
133
155
  ): Observable<SanityDocument<R> | undefined> {
156
+ const getDocId = () => {
157
+ if (!opts.releaseId) {
158
+ return id
159
+ }
160
+
161
+ const versionId = getVersionFromId(id)
162
+ if (!versionId) {
163
+ if (isDraftId(id)) {
164
+ throw new Error(
165
+ `The document ID (\`${id}\`) is a draft, but \`options.releaseId\` is set as \`${opts.releaseId}\``,
166
+ )
167
+ }
168
+
169
+ return getVersionId(id, opts.releaseId)
170
+ }
171
+
172
+ if (versionId !== opts.releaseId) {
173
+ throw new Error(
174
+ `The document ID (\`${id}\`) is already a version of \`${versionId}\` release, but this does not match the provided \`options.releaseId\` (\`${opts.releaseId}\`)`,
175
+ )
176
+ }
177
+
178
+ return id
179
+ }
180
+ const docId = getDocId()
181
+
134
182
  const options = {
135
- uri: _getDataUrl(config, 'doc', id),
183
+ uri: _getDataUrl(config, 'doc', docId),
136
184
  json: true,
137
185
  tag: opts.tag,
138
186
  signal: opts.signal,
@@ -165,6 +213,27 @@ export function _getDocuments<R extends Record<string, Any>>(
165
213
  )
166
214
  }
167
215
 
216
+ /** @internal */
217
+ export function _getReleaseDocuments<R extends Record<string, Any>>(
218
+ config: InitializedClientConfig,
219
+ httpRequest: HttpRequest,
220
+ releaseId: string,
221
+ opts: BaseMutationOptions = {},
222
+ ): Observable<RawQueryResponse<SanityDocument<R>[]>> {
223
+ return _dataRequest(
224
+ config,
225
+ httpRequest,
226
+ 'query',
227
+ {
228
+ query: '*[sanity::partOfRelease($releaseId)]',
229
+ params: {
230
+ releaseId,
231
+ },
232
+ },
233
+ opts,
234
+ )
235
+ }
236
+
168
237
  /** @internal */
169
238
  export function _createIfNotExists<R extends Record<string, Any>>(
170
239
  config: InitializedClientConfig,
@@ -201,6 +270,26 @@ export function _createOrReplace<R extends Record<string, Any>>(
201
270
  return _create<R>(config, httpRequest, doc, 'createOrReplace', options)
202
271
  }
203
272
 
273
+ /** @internal */
274
+ export function _createVersion<R extends Record<string, Any>>(
275
+ config: InitializedClientConfig,
276
+ httpRequest: HttpRequest,
277
+ doc: IdentifiedSanityDocumentStub<R>,
278
+ publishedId: string,
279
+ options?: BaseActionOptions,
280
+ ): Observable<SingleActionResult> {
281
+ validators.requireDocumentId('createVersion', doc)
282
+ validators.requireDocumentType('createVersion', doc)
283
+
284
+ const createVersionAction: CreateVersionAction = {
285
+ actionType: 'sanity.action.document.version.create',
286
+ publishedId,
287
+ document: doc,
288
+ }
289
+
290
+ return _action(config, httpRequest, createVersionAction, options)
291
+ }
292
+
204
293
  /** @internal */
205
294
  export function _delete<R extends Record<string, Any>>(
206
295
  config: InitializedClientConfig,
@@ -224,6 +313,58 @@ export function _delete<R extends Record<string, Any>>(
224
313
  )
225
314
  }
226
315
 
316
+ /** @internal */
317
+ export function _discardVersion(
318
+ config: InitializedClientConfig,
319
+ httpRequest: HttpRequest,
320
+ versionId: string,
321
+ purge: boolean = false,
322
+ options?: BaseActionOptions,
323
+ ): Observable<SingleActionResult> {
324
+ const discardVersionAction: DiscardVersionAction = {
325
+ actionType: 'sanity.action.document.version.discard',
326
+ versionId,
327
+ purge,
328
+ }
329
+
330
+ return _action(config, httpRequest, discardVersionAction, options)
331
+ }
332
+
333
+ /** @internal */
334
+ export function _replaceVersion<R extends Record<string, Any>>(
335
+ config: InitializedClientConfig,
336
+ httpRequest: HttpRequest,
337
+ doc: IdentifiedSanityDocumentStub<R>,
338
+ options?: BaseActionOptions,
339
+ ): Observable<SingleActionResult> {
340
+ validators.requireDocumentId('replaceVersion', doc)
341
+ validators.requireDocumentType('replaceVersion', doc)
342
+
343
+ const replaceVersionAction: ReplaceVersionAction = {
344
+ actionType: 'sanity.action.document.version.replace',
345
+ document: doc,
346
+ }
347
+
348
+ return _action(config, httpRequest, replaceVersionAction, options)
349
+ }
350
+
351
+ /** @internal */
352
+ export function _unpublishVersion(
353
+ config: InitializedClientConfig,
354
+ httpRequest: HttpRequest,
355
+ versionId: string,
356
+ publishedId: string,
357
+ options?: BaseActionOptions,
358
+ ): Observable<SingleActionResult> {
359
+ const unpublishVersionAction: UnpublishVersionAction = {
360
+ actionType: 'sanity.action.document.version.unpublish',
361
+ versionId,
362
+ publishedId,
363
+ }
364
+
365
+ return _action(config, httpRequest, unpublishVersionAction, options)
366
+ }
367
+
227
368
  /** @internal */
228
369
  export function _mutate<R extends Record<string, Any>>(
229
370
  config: InitializedClientConfig,
@@ -289,11 +430,13 @@ export function _dataRequest(
289
430
  const isMutation = endpoint === 'mutate'
290
431
  const isAction = endpoint === 'actions'
291
432
  const isQuery = endpoint === 'query'
433
+ const isEmulate = endpoint === 'emulate'
292
434
 
293
435
  // Check if the query string is within a configured threshold,
294
436
  // in which case we can use GET. Otherwise, use POST.
295
- const strQuery = isMutation || isAction ? '' : encodeQueryString(body)
296
- const useGet = !isMutation && !isAction && strQuery.length < getQuerySizeLimit
437
+ // Emulate endpoint always uses POST
438
+ const strQuery = isMutation || isAction || isEmulate ? '' : encodeQueryString(body)
439
+ const useGet = !isMutation && !isAction && !isEmulate && strQuery.length < getQuerySizeLimit
297
440
  const stringQuery = useGet ? strQuery : ''
298
441
  const returnFirst = options.returnFirst
299
442
  const {timeout, token, tag, headers, returnQuery, lastLiveEventId, cacheMode} = options
@@ -377,6 +520,9 @@ const isQuery = (config: InitializedClientConfig, uri: string) =>
377
520
  const isViewQuery = (config: InitializedClientConfig, uri: string) =>
378
521
  hasDataConfig(config) && uri.startsWith(_getDataUrl(config, 'views'))
379
522
 
523
+ const isEmulate = (config: InitializedClientConfig, uri: string) =>
524
+ hasDataConfig(config) && uri.startsWith(_getDataUrl(config, 'emulate'))
525
+
380
526
  const isMutate = (config: InitializedClientConfig, uri: string) =>
381
527
  hasDataConfig(config) && uri.startsWith(_getDataUrl(config, 'mutate'))
382
528
 
@@ -396,7 +542,8 @@ const isData = (config: InitializedClientConfig, uri: string) =>
396
542
  isDoc(config, uri) ||
397
543
  isListener(config, uri) ||
398
544
  isHistory(config, uri) ||
399
- isViewQuery(config, uri)
545
+ isViewQuery(config, uri) ||
546
+ isEmulate(config, uri)
400
547
 
401
548
  /**
402
549
  * @internal
@@ -425,8 +572,11 @@ export function _requestObservable<R>(
425
572
  options.query = {tag: validate.requestTag(tag), ...options.query}
426
573
  }
427
574
 
428
- // GROQ query-only parameters
429
- if (['GET', 'HEAD', 'POST'].indexOf(options.method || 'GET') >= 0 && isQuery(config, uri)) {
575
+ // GROQ query-only parameters (applies to both query and emulate endpoints)
576
+ if (
577
+ ['GET', 'HEAD', 'POST'].indexOf(options.method || 'GET') >= 0 &&
578
+ (isQuery(config, uri) || isEmulate(config, uri))
579
+ ) {
430
580
  const resultSourceMap = options.resultSourceMap ?? config.resultSourceMap
431
581
  if (resultSourceMap !== undefined && resultSourceMap !== false) {
432
582
  options.query = {resultSourceMap, ...options.query}
@@ -486,7 +636,11 @@ export function _requestObservable<R>(
486
636
  /**
487
637
  * @internal
488
638
  */
489
- export function _request<R>(config: InitializedClientConfig, httpRequest: HttpRequest, options: Any): Observable<R> {
639
+ export function _request<R>(
640
+ config: InitializedClientConfig,
641
+ httpRequest: HttpRequest,
642
+ options: Any,
643
+ ): Observable<R> {
490
644
  const observable = _requestObservable<R>(config, httpRequest, options).pipe(
491
645
  filter((event: Any) => event.type === 'response'),
492
646
  map((event: Any) => event.body),
@@ -498,7 +652,11 @@ export function _request<R>(config: InitializedClientConfig, httpRequest: HttpRe
498
652
  /**
499
653
  * @internal
500
654
  */
501
- export function _getDataUrl(config: InitializedClientConfig, operation: string, path?: string): string {
655
+ export function _getDataUrl(
656
+ config: InitializedClientConfig,
657
+ operation: string,
658
+ path?: string,
659
+ ): string {
502
660
  if (config['~experimental_resource']) {
503
661
  validators.resourceConfig(config)
504
662
  const resourceBase = resourceDataBase(config)
@@ -514,7 +672,7 @@ export function _getDataUrl(config: InitializedClientConfig, operation: string,
514
672
  /**
515
673
  * @internal
516
674
  */
517
- export function _getUrl(config: InitializedClientConfig, uri: string, canUseCdn = false, options: {forceApiUrl?: boolean} = {}): string {
675
+ export function _getUrl(config: InitializedClientConfig, uri: string, canUseCdn = false): string {
518
676
  const {url, cdnUrl} = config
519
677
  const base = canUseCdn ? cdnUrl : url
520
678
  return `${base}/${uri.replace(/^\//, '')}`
@@ -18,7 +18,7 @@ export const encodeQueryString = ({
18
18
 
19
19
  // Iterate params, the keys are prefixed with `$` and their values JSON stringified
20
20
  for (const [key, value] of Object.entries(params)) {
21
- searchParams.append(`$${key}`, JSON.stringify(value))
21
+ if (value !== undefined) searchParams.append(`$${key}`, JSON.stringify(value))
22
22
  }
23
23
  // Options are passed as-is
24
24
  for (const [key, value] of Object.entries(opts)) {
@@ -1,5 +1,6 @@
1
1
  import {defer, isObservable, mergeMap, Observable, of} from 'rxjs'
2
2
 
3
+ import {formatQueryParseError, isQueryParseError} from '../http/errors'
3
4
  import {type Any} from '../types'
4
5
 
5
6
  /**
@@ -169,8 +170,10 @@ function connectWithESInstance<EventTypeName extends string>(
169
170
  }
170
171
  if (message.type === 'channelError') {
171
172
  // An error occurred. This is different from a network-level error (which will be emitted as 'error').
172
- // Possible causes are things such as malformed filters, non-existant datasets or similar.
173
- observer.error(new ChannelError(extractErrorMessage(event?.data), event.data))
173
+ // Possible causes are things such as malformed filters, non-existant datasets
174
+ // or similar.
175
+ const tag = new URL(es.url).searchParams.get('tag')
176
+ observer.error(new ChannelError(extractErrorMessage(event?.data, tag), event.data))
174
177
  return
175
178
  }
176
179
  if (message.type === 'disconnect') {
@@ -235,16 +238,22 @@ function parseEvent(
235
238
  }
236
239
  }
237
240
 
238
- function extractErrorMessage(err: Any) {
239
- if (!err.error) {
241
+ function extractErrorMessage(err: Any, tag?: string | null) {
242
+ const error = err.error
243
+
244
+ if (!error) {
240
245
  return err.message || 'Unknown listener error'
241
246
  }
242
247
 
243
- if (err.error.description) {
244
- return err.error.description
248
+ if (isQueryParseError(error)) {
249
+ return formatQueryParseError(error, tag)
250
+ }
251
+
252
+ if (error.description) {
253
+ return error.description
245
254
  }
246
255
 
247
- return typeof err.error === 'string' ? err.error : JSON.stringify(err.error, null, 2)
256
+ return typeof error === 'string' ? error : JSON.stringify(error, null, 2)
248
257
  }
249
258
 
250
259
  function isEmptyObject(data: object) {
@@ -70,7 +70,7 @@ export function _listen<R extends Record<string, Any> = Record<string, Any>>(
70
70
  params?: ListenParams,
71
71
  opts: ListenOptions = {},
72
72
  ): Observable<MutationEvent<R> | ListenEvent<R>> {
73
- const {url, token, withCredentials, requestTagPrefix} = this.config()
73
+ const {url, token, withCredentials, requestTagPrefix, headers: configHeaders} = this.config()
74
74
  const tag = opts.tag && requestTagPrefix ? [requestTagPrefix, opts.tag].join('.') : opts.tag
75
75
  const options = {...defaults(opts, defaultOptions), tag}
76
76
  const listenOpts = pick(options, possibleOptions)
@@ -88,9 +88,15 @@ export function _listen<R extends Record<string, Any> = Record<string, Any>>(
88
88
  esOptions.withCredentials = true
89
89
  }
90
90
 
91
- if (token) {
92
- esOptions.headers = {
93
- Authorization: `Bearer ${token}`,
91
+ if (token || configHeaders) {
92
+ esOptions.headers = {}
93
+
94
+ if (token) {
95
+ esOptions.headers.Authorization = `Bearer ${token}`
96
+ }
97
+
98
+ if (configHeaders) {
99
+ Object.assign(esOptions.headers, configHeaders)
94
100
  }
95
101
  }
96
102
 
package/src/data/live.ts CHANGED
@@ -52,6 +52,7 @@ export class LiveClient {
52
52
  token,
53
53
  withCredentials,
54
54
  requestTagPrefix,
55
+ headers: configHeaders,
55
56
  } = this.#client.config()
56
57
  const apiVersion = _apiVersion.replace(/^v/, '')
57
58
  if (apiVersion !== 'X' && apiVersion < requiredApiVersion) {
@@ -76,15 +77,22 @@ export class LiveClient {
76
77
  url.searchParams.set('includeDrafts', 'true')
77
78
  }
78
79
  const esOptions: EventSourceInit & {headers?: Record<string, string>} = {}
79
- if (includeDrafts && token) {
80
- esOptions.headers = {
81
- Authorization: `Bearer ${token}`,
82
- }
83
- }
84
80
  if (includeDrafts && withCredentials) {
85
81
  esOptions.withCredentials = true
86
82
  }
87
83
 
84
+ if ((includeDrafts && token) || configHeaders) {
85
+ esOptions.headers = {}
86
+
87
+ if (includeDrafts && token) {
88
+ esOptions.headers.Authorization = `Bearer ${token}`
89
+ }
90
+
91
+ if (configHeaders) {
92
+ Object.assign(esOptions.headers, configHeaders)
93
+ }
94
+ }
95
+
88
96
  const key = `${url.href}::${JSON.stringify(esOptions)}`
89
97
  const existing = eventsCache.get(key)
90
98
 
@@ -105,7 +105,10 @@ export class DatasetsClient {
105
105
  list(): Promise<DatasetsResponse> {
106
106
  validate.resourceGuard('dataset', this.#client.config())
107
107
  return lastValueFrom(
108
- _request<DatasetsResponse>(this.#client.config(), this.#httpRequest, {uri: '/datasets', tag: null}),
108
+ _request<DatasetsResponse>(this.#client.config(), this.#httpRequest, {
109
+ uri: '/datasets',
110
+ tag: null,
111
+ }),
109
112
  )
110
113
  }
111
114
  }
@@ -17,7 +17,13 @@ export {
17
17
  } from './data/eventsource'
18
18
  export * from './data/patch'
19
19
  export * from './data/transaction'
20
- export {ClientError, CorsOriginError, ServerError} from './http/errors'
20
+ export {
21
+ ClientError,
22
+ CorsOriginError,
23
+ formatQueryParseError,
24
+ isQueryParseError,
25
+ ServerError,
26
+ } from './http/errors'
21
27
  export * from './SanityClient'
22
28
  export * from './types'
23
29