@sanity/client 0.0.0-dev.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 (42) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1225 -0
  3. package/dist/index.browser.cjs +1760 -0
  4. package/dist/index.browser.cjs.map +1 -0
  5. package/dist/index.browser.js +1737 -0
  6. package/dist/index.browser.js.map +1 -0
  7. package/dist/index.cjs +1769 -0
  8. package/dist/index.cjs.js +16 -0
  9. package/dist/index.cjs.map +1 -0
  10. package/dist/index.d.ts +2234 -0
  11. package/dist/index.js +1746 -0
  12. package/dist/index.js.map +1 -0
  13. package/package.json +127 -0
  14. package/src/SanityClient.ts +1253 -0
  15. package/src/assets/AssetsClient.ts +191 -0
  16. package/src/config.ts +96 -0
  17. package/src/data/dataMethods.ts +416 -0
  18. package/src/data/encodeQueryString.ts +29 -0
  19. package/src/data/listen.ts +196 -0
  20. package/src/data/patch.ts +345 -0
  21. package/src/data/transaction.ts +358 -0
  22. package/src/datasets/DatasetsClient.ts +115 -0
  23. package/src/generateHelpUrl.ts +5 -0
  24. package/src/http/browserMiddleware.ts +1 -0
  25. package/src/http/errors.ts +68 -0
  26. package/src/http/nodeMiddleware.ts +11 -0
  27. package/src/http/request.ts +48 -0
  28. package/src/http/requestOptions.ts +33 -0
  29. package/src/index.browser.ts +28 -0
  30. package/src/index.ts +28 -0
  31. package/src/projects/ProjectsClient.ts +61 -0
  32. package/src/types.ts +575 -0
  33. package/src/users/UsersClient.ts +55 -0
  34. package/src/util/defaults.ts +10 -0
  35. package/src/util/getSelection.ts +21 -0
  36. package/src/util/once.ts +14 -0
  37. package/src/util/pick.ts +11 -0
  38. package/src/validators.ts +78 -0
  39. package/src/warnings.ts +30 -0
  40. package/umd/.gitkeep +1 -0
  41. package/umd/sanityClient.js +4840 -0
  42. package/umd/sanityClient.min.js +14 -0
@@ -0,0 +1,29 @@
1
+ import type {Any, QueryParams} from '../types'
2
+
3
+ export const encodeQueryString = ({
4
+ query,
5
+ params = {},
6
+ options = {},
7
+ }: {
8
+ query: string
9
+ params?: QueryParams
10
+ options?: Any
11
+ }) => {
12
+ const searchParams = new URLSearchParams()
13
+ // We generally want tag at the start of the query string
14
+ const {tag, ...opts} = options
15
+ if (tag) searchParams.set('tag', tag)
16
+ searchParams.set('query', query)
17
+
18
+ // Iterate params, the keys are prefixed with `$` and their values JSON stringified
19
+ for (const [key, value] of Object.entries(params)) {
20
+ searchParams.set(`$${key}`, JSON.stringify(value))
21
+ }
22
+ // Options are passed as-is
23
+ for (const [key, value] of Object.entries(opts)) {
24
+ // Skip falsy values
25
+ if (value) searchParams.set(key, `${value}`)
26
+ }
27
+
28
+ return `?${searchParams}`
29
+ }
@@ -0,0 +1,196 @@
1
+ import polyfilledEventSource from '@sanity/eventsource'
2
+ import {Observable} from 'rxjs'
3
+
4
+ import type {ObservableSanityClient, SanityClient} from '../SanityClient'
5
+ import type {Any, ListenEvent, ListenOptions, MutationEvent, QueryParams} from '../types'
6
+ import defaults from '../util/defaults'
7
+ import {pick} from '../util/pick'
8
+ import {_getDataUrl} from './dataMethods'
9
+ import {encodeQueryString} from './encodeQueryString'
10
+
11
+ // Limit is 16K for a _request_, eg including headers. Have to account for an
12
+ // unknown range of headers, but an average EventSource request from Chrome seems
13
+ // to have around 700 bytes of cruft, so let us account for 1.2K to be "safe"
14
+ const MAX_URL_LENGTH = 16000 - 1200
15
+ const EventSource = polyfilledEventSource
16
+
17
+ const possibleOptions = [
18
+ 'includePreviousRevision',
19
+ 'includeResult',
20
+ 'visibility',
21
+ 'effectFormat',
22
+ 'tag',
23
+ ]
24
+
25
+ const defaultOptions = {
26
+ includeResult: true,
27
+ }
28
+
29
+ /**
30
+ * Set up a listener that will be notified when mutations occur on documents matching the provided query/filter.
31
+ *
32
+ * @param query - GROQ-filter to listen to changes for
33
+ * @param params - Optional query parameters
34
+ * @param options - Listener options
35
+ * @internal
36
+ */
37
+ export function _listen<R extends Record<string, Any> = Record<string, Any>>(
38
+ this: SanityClient | ObservableSanityClient,
39
+ query: string,
40
+ params?: QueryParams
41
+ ): Observable<MutationEvent<R>>
42
+ /**
43
+ * Set up a listener that will be notified when mutations occur on documents matching the provided query/filter.
44
+ *
45
+ * @param query - GROQ-filter to listen to changes for
46
+ * @param params - Optional query parameters
47
+ * @param options - Listener options
48
+ * @internal
49
+ */
50
+ export function _listen<R extends Record<string, Any> = Record<string, Any>>(
51
+ this: SanityClient | ObservableSanityClient,
52
+ query: string,
53
+ params?: QueryParams,
54
+ options?: ListenOptions
55
+ ): Observable<ListenEvent<R>>
56
+ /** @internal */
57
+ export function _listen<R extends Record<string, Any> = Record<string, Any>>(
58
+ this: SanityClient | ObservableSanityClient,
59
+ query: string,
60
+ params?: QueryParams,
61
+ opts: ListenOptions = {}
62
+ ): Observable<MutationEvent<R> | ListenEvent<R>> {
63
+ const {url, token, withCredentials, requestTagPrefix} = this.config()
64
+ const tag = opts.tag && requestTagPrefix ? [requestTagPrefix, opts.tag].join('.') : opts.tag
65
+ const options = {...defaults(opts, defaultOptions), tag}
66
+ const listenOpts = pick(options, possibleOptions)
67
+ const qs = encodeQueryString({query, params, options: {tag, ...listenOpts}})
68
+
69
+ const uri = `${url}${_getDataUrl(this, 'listen', qs)}`
70
+ if (uri.length > MAX_URL_LENGTH) {
71
+ return new Observable((observer) => observer.error(new Error('Query too large for listener')))
72
+ }
73
+
74
+ const listenFor = options.events ? options.events : ['mutation']
75
+ const shouldEmitReconnect = listenFor.indexOf('reconnect') !== -1
76
+
77
+ const esOptions: EventSourceInit & {headers?: Record<string, string>} = {}
78
+ if (token || withCredentials) {
79
+ esOptions.withCredentials = true
80
+ }
81
+
82
+ if (token) {
83
+ esOptions.headers = {
84
+ Authorization: `Bearer ${token}`,
85
+ }
86
+ }
87
+
88
+ return new Observable((observer) => {
89
+ let es = getEventSource()
90
+ let reconnectTimer: NodeJS.Timeout
91
+ let stopped = false
92
+
93
+ function onError() {
94
+ if (stopped) {
95
+ return
96
+ }
97
+
98
+ emitReconnect()
99
+
100
+ // Allow event handlers of `emitReconnect` to cancel/close the reconnect attempt
101
+ if (stopped) {
102
+ return
103
+ }
104
+
105
+ // Unless we've explicitly stopped the ES (in which case `stopped` should be true),
106
+ // we should never be in a disconnected state. By default, EventSource will reconnect
107
+ // automatically, in which case it sets readyState to `CONNECTING`, but in some cases
108
+ // (like when a laptop lid is closed), it closes the connection. In these cases we need
109
+ // to explicitly reconnect.
110
+ if (es.readyState === EventSource.CLOSED) {
111
+ unsubscribe()
112
+ clearTimeout(reconnectTimer)
113
+ reconnectTimer = setTimeout(open, 100)
114
+ }
115
+ }
116
+
117
+ function onChannelError(err: Any) {
118
+ observer.error(cooerceError(err))
119
+ }
120
+
121
+ function onMessage(evt: Any) {
122
+ const event = parseEvent(evt)
123
+ return event instanceof Error ? observer.error(event) : observer.next(event)
124
+ }
125
+
126
+ function onDisconnect() {
127
+ stopped = true
128
+ unsubscribe()
129
+ observer.complete()
130
+ }
131
+
132
+ function unsubscribe() {
133
+ es.removeEventListener('error', onError, false)
134
+ es.removeEventListener('channelError', onChannelError, false)
135
+ es.removeEventListener('disconnect', onDisconnect, false)
136
+ listenFor.forEach((type: string) => es.removeEventListener(type, onMessage, false))
137
+ es.close()
138
+ }
139
+
140
+ function emitReconnect() {
141
+ if (shouldEmitReconnect) {
142
+ observer.next({type: 'reconnect'})
143
+ }
144
+ }
145
+
146
+ function getEventSource() {
147
+ const evs = new EventSource(uri, esOptions)
148
+ evs.addEventListener('error', onError, false)
149
+ evs.addEventListener('channelError', onChannelError, false)
150
+ evs.addEventListener('disconnect', onDisconnect, false)
151
+ listenFor.forEach((type: string) => evs.addEventListener(type, onMessage, false))
152
+ return evs
153
+ }
154
+
155
+ function open() {
156
+ es = getEventSource()
157
+ }
158
+
159
+ function stop() {
160
+ stopped = true
161
+ unsubscribe()
162
+ }
163
+
164
+ return stop
165
+ })
166
+ }
167
+
168
+ function parseEvent(event: Any) {
169
+ try {
170
+ const data = (event.data && JSON.parse(event.data)) || {}
171
+ return Object.assign({type: event.type}, data)
172
+ } catch (err) {
173
+ return err
174
+ }
175
+ }
176
+
177
+ function cooerceError(err: Any) {
178
+ if (err instanceof Error) {
179
+ return err
180
+ }
181
+
182
+ const evt = parseEvent(err)
183
+ return evt instanceof Error ? evt : new Error(extractErrorMessage(evt))
184
+ }
185
+
186
+ function extractErrorMessage(err: Any) {
187
+ if (!err.error) {
188
+ return err.message || 'Unknown listener error'
189
+ }
190
+
191
+ if (err.error.description) {
192
+ return err.error.description
193
+ }
194
+
195
+ return typeof err.error === 'string' ? err.error : JSON.stringify(err.error, null, 2)
196
+ }
@@ -0,0 +1,345 @@
1
+ import {type Observable} from 'rxjs'
2
+
3
+ import type {ObservableSanityClient, SanityClient} from '../SanityClient'
4
+ import type {
5
+ AllDocumentIdsMutationOptions,
6
+ AllDocumentsMutationOptions,
7
+ Any,
8
+ AttributeSet,
9
+ BaseMutationOptions,
10
+ FirstDocumentIdMutationOptions,
11
+ FirstDocumentMutationOptions,
12
+ MultipleMutationResult,
13
+ PatchMutationOperation,
14
+ PatchOperations,
15
+ PatchSelection,
16
+ SanityDocument,
17
+ SingleMutationResult,
18
+ } from '../types'
19
+ import {getSelection} from '../util/getSelection'
20
+ import {validateInsert, validateObject} from '../validators'
21
+
22
+ /** @internal */
23
+ export class BasePatch {
24
+ protected selection: PatchSelection
25
+ protected operations: PatchOperations
26
+ constructor(selection: PatchSelection, operations: PatchOperations = {}) {
27
+ this.selection = selection
28
+ this.operations = operations
29
+ }
30
+
31
+ /**
32
+ * Sets the given attributes to the document. Does NOT merge objects.
33
+ * The operation is added to the current patch, ready to be commited by `commit()`
34
+ *
35
+ * @param attrs - Attributes to set. To set a deep attribute, use JSONMatch, eg: \{"nested.prop": "value"\}
36
+ */
37
+ set(attrs: AttributeSet): this {
38
+ return this._assign('set', attrs)
39
+ }
40
+
41
+ /**
42
+ * Sets the given attributes to the document if they are not currently set. Does NOT merge objects.
43
+ * The operation is added to the current patch, ready to be commited by `commit()`
44
+ *
45
+ * @param attrs - Attributes to set. To set a deep attribute, use JSONMatch, eg: \{"nested.prop": "value"\}
46
+ */
47
+ setIfMissing(attrs: AttributeSet): this {
48
+ return this._assign('setIfMissing', attrs)
49
+ }
50
+
51
+ /**
52
+ * Performs a "diff-match-patch" operation on the string attributes provided.
53
+ * The operation is added to the current patch, ready to be commited by `commit()`
54
+ *
55
+ * @param attrs - Attributes to perform operation on. To set a deep attribute, use JSONMatch, eg: \{"nested.prop": "dmp"\}
56
+ */
57
+ diffMatchPatch(attrs: AttributeSet): this {
58
+ validateObject('diffMatchPatch', attrs)
59
+ return this._assign('diffMatchPatch', attrs)
60
+ }
61
+
62
+ /**
63
+ * Unsets the attribute paths provided.
64
+ * The operation is added to the current patch, ready to be commited by `commit()`
65
+ *
66
+ * @param attrs - Attribute paths to unset.
67
+ */
68
+ unset(attrs: string[]): this {
69
+ if (!Array.isArray(attrs)) {
70
+ throw new Error('unset(attrs) takes an array of attributes to unset, non-array given')
71
+ }
72
+
73
+ this.operations = Object.assign({}, this.operations, {unset: attrs})
74
+ return this
75
+ }
76
+
77
+ /**
78
+ * Increment a numeric value. Each entry in the argument is either an attribute or a JSON path. The value may be a positive or negative integer or floating-point value. The operation will fail if target value is not a numeric value, or doesn't exist.
79
+ *
80
+ * @param attrs - Object of attribute paths to increment, values representing the number to increment by.
81
+ */
82
+ inc(attrs: {[key: string]: number}): this {
83
+ return this._assign('inc', attrs)
84
+ }
85
+
86
+ /**
87
+ * Decrement a numeric value. Each entry in the argument is either an attribute or a JSON path. The value may be a positive or negative integer or floating-point value. The operation will fail if target value is not a numeric value, or doesn't exist.
88
+ *
89
+ * @param attrs - Object of attribute paths to decrement, values representing the number to decrement by.
90
+ */
91
+ dec(attrs: {[key: string]: number}): this {
92
+ return this._assign('dec', attrs)
93
+ }
94
+
95
+ /**
96
+ * Provides methods for modifying arrays, by inserting, appending and replacing elements via a JSONPath expression.
97
+ *
98
+ * @param at - Location to insert at, relative to the given selector, or 'replace' the matched path
99
+ * @param selector - JSONPath expression, eg `comments[-1]` or `blocks[_key=="abc123"]`
100
+ * @param items - Array of items to insert/replace
101
+ */
102
+ insert(at: 'before' | 'after' | 'replace', selector: string, items: Any[]): this {
103
+ validateInsert(at, selector, items)
104
+ return this._assign('insert', {[at]: selector, items})
105
+ }
106
+
107
+ /**
108
+ * Append the given items to the array at the given JSONPath
109
+ *
110
+ * @param selector - Attribute/path to append to, eg `comments` or `person.hobbies`
111
+ * @param items - Array of items to append to the array
112
+ */
113
+ append(selector: string, items: Any[]): this {
114
+ return this.insert('after', `${selector}[-1]`, items)
115
+ }
116
+
117
+ /**
118
+ * Prepend the given items to the array at the given JSONPath
119
+ *
120
+ * @param selector - Attribute/path to prepend to, eg `comments` or `person.hobbies`
121
+ * @param items - Array of items to prepend to the array
122
+ */
123
+ prepend(selector: string, items: Any[]): this {
124
+ return this.insert('before', `${selector}[0]`, items)
125
+ }
126
+
127
+ /**
128
+ * Change the contents of an array by removing existing elements and/or adding new elements.
129
+ *
130
+ * @param selector - Attribute or JSONPath expression for array
131
+ * @param start - Index at which to start changing the array (with origin 0). If greater than the length of the array, actual starting index will be set to the length of the array. If negative, will begin that many elements from the end of the array (with origin -1) and will be set to 0 if absolute value is greater than the length of the array.x
132
+ * @param deleteCount - An integer indicating the number of old array elements to remove.
133
+ * @param items - The elements to add to the array, beginning at the start index. If you don't specify any elements, splice() will only remove elements from the array.
134
+ */
135
+ splice(selector: string, start: number, deleteCount?: number, items?: Any[]): this {
136
+ // Negative indexes doesn't mean the same in Sanity as they do in JS;
137
+ // -1 means "actually at the end of the array", which allows inserting
138
+ // at the end of the array without knowing its length. We therefore have
139
+ // to substract negative indexes by one to match JS. If you want Sanity-
140
+ // behaviour, just use `insert('replace', selector, items)` directly
141
+ const delAll = typeof deleteCount === 'undefined' || deleteCount === -1
142
+ const startIndex = start < 0 ? start - 1 : start
143
+ const delCount = delAll ? -1 : Math.max(0, start + deleteCount)
144
+ const delRange = startIndex < 0 && delCount >= 0 ? '' : delCount
145
+ const rangeSelector = `${selector}[${startIndex}:${delRange}]`
146
+ return this.insert('replace', rangeSelector, items || [])
147
+ }
148
+
149
+ /**
150
+ * Adds a revision clause, preventing the document from being patched if the `_rev` property does not match the given value
151
+ *
152
+ * @param rev - Revision to lock the patch to
153
+ */
154
+ ifRevisionId(rev: string): this {
155
+ this.operations.ifRevisionID = rev
156
+ return this
157
+ }
158
+
159
+ /**
160
+ * Return a plain JSON representation of the patch
161
+ */
162
+ serialize(): PatchMutationOperation {
163
+ return {...getSelection(this.selection), ...this.operations}
164
+ }
165
+
166
+ /**
167
+ * Return a plain JSON representation of the patch
168
+ */
169
+ toJSON(): PatchMutationOperation {
170
+ return this.serialize()
171
+ }
172
+
173
+ /**
174
+ * Clears the patch of all operations
175
+ */
176
+ reset(): this {
177
+ this.operations = {}
178
+ return this
179
+ }
180
+
181
+ protected _assign(op: keyof PatchOperations, props: Any, merge = true): this {
182
+ validateObject(op, props)
183
+ this.operations = Object.assign({}, this.operations, {
184
+ [op]: Object.assign({}, (merge && this.operations[op]) || {}, props),
185
+ })
186
+ return this
187
+ }
188
+
189
+ protected _set(op: keyof PatchOperations, props: Any): this {
190
+ return this._assign(op, props, false)
191
+ }
192
+ }
193
+
194
+ /** @public */
195
+ export class ObservablePatch extends BasePatch {
196
+ #client?: ObservableSanityClient
197
+
198
+ constructor(
199
+ selection: PatchSelection,
200
+ operations?: PatchOperations,
201
+ client?: ObservableSanityClient
202
+ ) {
203
+ super(selection, operations)
204
+ this.#client = client
205
+ }
206
+
207
+ /**
208
+ * Clones the patch
209
+ */
210
+ clone(): ObservablePatch {
211
+ return new ObservablePatch(this.selection, {...this.operations}, this.#client)
212
+ }
213
+
214
+ /**
215
+ * Commit the patch, returning an observable that produces the first patched document
216
+ *
217
+ * @param options - Options for the mutation operation
218
+ */
219
+ commit<R extends Record<string, Any> = Record<string, Any>>(
220
+ options: FirstDocumentMutationOptions
221
+ ): Observable<SanityDocument<R>>
222
+ /**
223
+ * Commit the patch, returning an observable that produces an array of the mutated documents
224
+ *
225
+ * @param options - Options for the mutation operation
226
+ */
227
+ commit<R extends Record<string, Any> = Record<string, Any>>(
228
+ options: AllDocumentsMutationOptions
229
+ ): Observable<SanityDocument<R>[]>
230
+ /**
231
+ * Commit the patch, returning an observable that produces a mutation result object
232
+ *
233
+ * @param options - Options for the mutation operation
234
+ */
235
+ commit(options: FirstDocumentIdMutationOptions): Observable<SingleMutationResult>
236
+ /**
237
+ * Commit the patch, returning an observable that produces a mutation result object
238
+ *
239
+ * @param options - Options for the mutation operation
240
+ */
241
+ commit(options: AllDocumentIdsMutationOptions): Observable<MultipleMutationResult>
242
+ /**
243
+ * Commit the patch, returning an observable that produces the first patched document
244
+ *
245
+ * @param options - Options for the mutation operation
246
+ */
247
+ commit<R extends Record<string, Any> = Record<string, Any>>(
248
+ options?: BaseMutationOptions
249
+ ): Observable<SanityDocument<R>>
250
+ commit<R extends Record<string, Any> = Record<string, Any>>(
251
+ options?:
252
+ | FirstDocumentMutationOptions
253
+ | AllDocumentsMutationOptions
254
+ | FirstDocumentIdMutationOptions
255
+ | AllDocumentIdsMutationOptions
256
+ | BaseMutationOptions
257
+ ): Observable<
258
+ SanityDocument<R> | SanityDocument<R>[] | SingleMutationResult | MultipleMutationResult
259
+ > {
260
+ if (!this.#client) {
261
+ throw new Error(
262
+ 'No `client` passed to patch, either provide one or pass the ' +
263
+ 'patch to a clients `mutate()` method'
264
+ )
265
+ }
266
+
267
+ const returnFirst = typeof this.selection === 'string'
268
+ const opts = Object.assign({returnFirst, returnDocuments: true}, options)
269
+ return this.#client.mutate<R>({patch: this.serialize()} as Any, opts)
270
+ }
271
+ }
272
+
273
+ /** @public */
274
+ export class Patch extends BasePatch {
275
+ #client?: SanityClient
276
+ constructor(selection: PatchSelection, operations?: PatchOperations, client?: SanityClient) {
277
+ super(selection, operations)
278
+ this.#client = client
279
+ }
280
+
281
+ /**
282
+ * Clones the patch
283
+ */
284
+ clone(): Patch {
285
+ return new Patch(this.selection, {...this.operations}, this.#client)
286
+ }
287
+
288
+ /**
289
+ * Commit the patch, returning a promise that resolves to the first patched document
290
+ *
291
+ * @param options - Options for the mutation operation
292
+ */
293
+ commit<R extends Record<string, Any> = Record<string, Any>>(
294
+ options: FirstDocumentMutationOptions
295
+ ): Promise<SanityDocument<R>>
296
+ /**
297
+ * Commit the patch, returning a promise that resolves to an array of the mutated documents
298
+ *
299
+ * @param options - Options for the mutation operation
300
+ */
301
+ commit<R extends Record<string, Any> = Record<string, Any>>(
302
+ options: AllDocumentsMutationOptions
303
+ ): Promise<SanityDocument<R>[]>
304
+ /**
305
+ * Commit the patch, returning a promise that resolves to a mutation result object
306
+ *
307
+ * @param options - Options for the mutation operation
308
+ */
309
+ commit(options: FirstDocumentIdMutationOptions): Promise<SingleMutationResult>
310
+ /**
311
+ * Commit the patch, returning a promise that resolves to a mutation result object
312
+ *
313
+ * @param options - Options for the mutation operation
314
+ */
315
+ commit(options: AllDocumentIdsMutationOptions): Promise<MultipleMutationResult>
316
+ /**
317
+ * Commit the patch, returning a promise that resolves to the first patched document
318
+ *
319
+ * @param options - Options for the mutation operation
320
+ */
321
+ commit<R extends Record<string, Any> = Record<string, Any>>(
322
+ options?: BaseMutationOptions
323
+ ): Promise<SanityDocument<R>>
324
+ commit<R extends Record<string, Any> = Record<string, Any>>(
325
+ options?:
326
+ | FirstDocumentMutationOptions
327
+ | AllDocumentsMutationOptions
328
+ | FirstDocumentIdMutationOptions
329
+ | AllDocumentIdsMutationOptions
330
+ | BaseMutationOptions
331
+ ): Promise<
332
+ SanityDocument<R> | SanityDocument<R>[] | SingleMutationResult | MultipleMutationResult
333
+ > {
334
+ if (!this.#client) {
335
+ throw new Error(
336
+ 'No `client` passed to patch, either provide one or pass the ' +
337
+ 'patch to a clients `mutate()` method'
338
+ )
339
+ }
340
+
341
+ const returnFirst = typeof this.selection === 'string'
342
+ const opts = Object.assign({returnFirst, returnDocuments: true}, options)
343
+ return this.#client.mutate<R>({patch: this.serialize()} as Any, opts)
344
+ }
345
+ }