@sanity/client 3.3.0 → 3.4.0-beta.esm.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 (39) hide show
  1. package/README.md +3 -5
  2. package/dist/sanityClient.browser.mjs +4317 -0
  3. package/dist/sanityClient.browser.mjs.map +7 -0
  4. package/dist/sanityClient.node.js +1129 -0
  5. package/dist/sanityClient.node.js.map +7 -0
  6. package/package.json +26 -6
  7. package/sanityClient.d.ts +3 -2
  8. package/src/assets/assetsClient.js +135 -0
  9. package/src/auth/authClient.js +17 -0
  10. package/src/config.js +96 -0
  11. package/src/data/dataMethods.js +183 -0
  12. package/src/data/encodeQueryString.js +18 -0
  13. package/src/data/listen.js +160 -0
  14. package/src/data/patch.js +124 -0
  15. package/src/data/transaction.js +106 -0
  16. package/src/datasets/datasetsClient.js +31 -0
  17. package/src/http/browserMiddleware.js +1 -0
  18. package/src/http/errors.js +57 -0
  19. package/src/http/nodeMiddleware.js +13 -0
  20. package/src/http/queryString.js +10 -0
  21. package/src/http/request.js +54 -0
  22. package/src/http/requestOptions.js +31 -0
  23. package/src/projects/projectsClient.js +17 -0
  24. package/src/sanityClient.js +119 -0
  25. package/src/users/usersClient.js +13 -0
  26. package/src/util/defaults.js +8 -0
  27. package/src/util/getSelection.js +17 -0
  28. package/src/util/observable.js +11 -0
  29. package/src/util/once.js +12 -0
  30. package/src/util/pick.js +9 -0
  31. package/src/validators.js +76 -0
  32. package/src/warnings.js +25 -0
  33. package/test/client.test.js +0 -2561
  34. package/test/encodeQueryString.test.js +0 -36
  35. package/test/fixtures/horsehead-nebula.jpg +0 -0
  36. package/test/fixtures/pdf-sample.pdf +0 -0
  37. package/test/helpers/sseServer.js +0 -26
  38. package/test/listen.test.js +0 -205
  39. package/test/warnings.test.disabled.js +0 -80
@@ -0,0 +1,183 @@
1
+ const assign = require('object-assign')
2
+ const {map, filter} = require('../util/observable')
3
+ const validators = require('../validators')
4
+ const getSelection = require('../util/getSelection')
5
+ const encodeQueryString = require('./encodeQueryString')
6
+ const Transaction = require('./transaction')
7
+ const Patch = require('./patch')
8
+ const listen = require('./listen')
9
+
10
+ const excludeFalsey = (param, defValue) => {
11
+ const value = typeof param === 'undefined' ? defValue : param
12
+ return param === false ? undefined : value
13
+ }
14
+
15
+ const getMutationQuery = (options = {}) => {
16
+ return {
17
+ dryRun: options.dryRun,
18
+ returnIds: true,
19
+ returnDocuments: excludeFalsey(options.returnDocuments, true),
20
+ visibility: options.visibility || 'sync',
21
+ autoGenerateArrayKeys: options.autoGenerateArrayKeys,
22
+ skipCrossDatasetReferenceValidation: options.skipCrossDatasetReferenceValidation,
23
+ }
24
+ }
25
+
26
+ const isResponse = (event) => event.type === 'response'
27
+ const getBody = (event) => event.body
28
+
29
+ const indexBy = (docs, attr) =>
30
+ docs.reduce((indexed, doc) => {
31
+ indexed[attr(doc)] = doc
32
+ return indexed
33
+ }, Object.create(null))
34
+
35
+ const toPromise = (observable) => observable.toPromise()
36
+
37
+ const getQuerySizeLimit = 11264
38
+
39
+ module.exports = {
40
+ listen: listen,
41
+
42
+ getDataUrl(operation, path) {
43
+ const config = this.clientConfig
44
+ const catalog = validators.hasDataset(config)
45
+ const baseUri = `/${operation}/${catalog}`
46
+ const uri = path ? `${baseUri}/${path}` : baseUri
47
+ return `/data${uri}`.replace(/\/($|\?)/, '$1')
48
+ },
49
+
50
+ fetch(query, params, options = {}) {
51
+ const mapResponse = options.filterResponse === false ? (res) => res : (res) => res.result
52
+
53
+ const observable = this._dataRequest('query', {query, params}, options).pipe(map(mapResponse))
54
+ return this.isPromiseAPI() ? toPromise(observable) : observable
55
+ },
56
+
57
+ getDocument(id, opts = {}) {
58
+ const options = {uri: this.getDataUrl('doc', id), json: true, tag: opts.tag}
59
+ const observable = this._requestObservable(options).pipe(
60
+ filter(isResponse),
61
+ map((event) => event.body.documents && event.body.documents[0])
62
+ )
63
+
64
+ return this.isPromiseAPI() ? toPromise(observable) : observable
65
+ },
66
+
67
+ getDocuments(ids, opts = {}) {
68
+ const options = {uri: this.getDataUrl('doc', ids.join(',')), json: true, tag: opts.tag}
69
+ const observable = this._requestObservable(options).pipe(
70
+ filter(isResponse),
71
+ map((event) => {
72
+ const indexed = indexBy(event.body.documents || [], (doc) => doc._id)
73
+ return ids.map((id) => indexed[id] || null)
74
+ })
75
+ )
76
+
77
+ return this.isPromiseAPI() ? toPromise(observable) : observable
78
+ },
79
+
80
+ create(doc, options) {
81
+ return this._create(doc, 'create', options)
82
+ },
83
+
84
+ createIfNotExists(doc, options) {
85
+ validators.requireDocumentId('createIfNotExists', doc)
86
+ return this._create(doc, 'createIfNotExists', options)
87
+ },
88
+
89
+ createOrReplace(doc, options) {
90
+ validators.requireDocumentId('createOrReplace', doc)
91
+ return this._create(doc, 'createOrReplace', options)
92
+ },
93
+
94
+ patch(selector, operations) {
95
+ return new Patch(selector, operations, this)
96
+ },
97
+
98
+ delete(selection, options) {
99
+ return this.dataRequest('mutate', {mutations: [{delete: getSelection(selection)}]}, options)
100
+ },
101
+
102
+ mutate(mutations, options) {
103
+ const mut =
104
+ mutations instanceof Patch || mutations instanceof Transaction
105
+ ? mutations.serialize()
106
+ : mutations
107
+
108
+ const muts = Array.isArray(mut) ? mut : [mut]
109
+ const transactionId = options && options.transactionId
110
+ return this.dataRequest('mutate', {mutations: muts, transactionId}, options)
111
+ },
112
+
113
+ transaction(operations) {
114
+ return new Transaction(operations, this)
115
+ },
116
+
117
+ dataRequest(endpoint, body, options = {}) {
118
+ const request = this._dataRequest(endpoint, body, options)
119
+
120
+ return this.isPromiseAPI() ? toPromise(request) : request
121
+ },
122
+
123
+ _dataRequest(endpoint, body, options = {}) {
124
+ const isMutation = endpoint === 'mutate'
125
+ const isQuery = endpoint === 'query'
126
+
127
+ // Check if the query string is within a configured threshold,
128
+ // in which case we can use GET. Otherwise, use POST.
129
+ const strQuery = !isMutation && encodeQueryString(body)
130
+ const useGet = !isMutation && strQuery.length < getQuerySizeLimit
131
+ const stringQuery = useGet ? strQuery : ''
132
+ const returnFirst = options.returnFirst
133
+ const {timeout, token, tag, headers} = options
134
+
135
+ const uri = this.getDataUrl(endpoint, stringQuery)
136
+
137
+ const reqOptions = {
138
+ method: useGet ? 'GET' : 'POST',
139
+ uri: uri,
140
+ json: true,
141
+ body: useGet ? undefined : body,
142
+ query: isMutation && getMutationQuery(options),
143
+ timeout,
144
+ headers,
145
+ token,
146
+ tag,
147
+ canUseCdn: isQuery,
148
+ }
149
+
150
+ return this._requestObservable(reqOptions).pipe(
151
+ filter(isResponse),
152
+ map(getBody),
153
+ map((res) => {
154
+ if (!isMutation) {
155
+ return res
156
+ }
157
+
158
+ // Should we return documents?
159
+ const results = res.results || []
160
+ if (options.returnDocuments) {
161
+ return returnFirst
162
+ ? results[0] && results[0].document
163
+ : results.map((mut) => mut.document)
164
+ }
165
+
166
+ // Return a reduced subset
167
+ const key = returnFirst ? 'documentId' : 'documentIds'
168
+ const ids = returnFirst ? results[0] && results[0].id : results.map((mut) => mut.id)
169
+ return {
170
+ transactionId: res.transactionId,
171
+ results: results,
172
+ [key]: ids,
173
+ }
174
+ })
175
+ )
176
+ },
177
+
178
+ _create(doc, op, options = {}) {
179
+ const mutation = {[op]: doc}
180
+ const opts = assign({returnFirst: true, returnDocuments: true}, options)
181
+ return this.dataRequest('mutate', {mutations: [mutation]}, opts)
182
+ },
183
+ }
@@ -0,0 +1,18 @@
1
+ const enc = encodeURIComponent
2
+
3
+ module.exports = ({query, params = {}, options = {}}) => {
4
+ // We generally want tag at the start of the query string
5
+ const {tag, ...opts} = options
6
+ const q = `query=${enc(query)}`
7
+ const base = tag ? `?tag=${enc(tag)}&${q}` : `?${q}`
8
+
9
+ const qString = Object.keys(params).reduce(
10
+ (qs, param) => `${qs}&${enc(`$${param}`)}=${enc(JSON.stringify(params[param]))}`,
11
+ base
12
+ )
13
+
14
+ return Object.keys(opts).reduce((qs, option) => {
15
+ // Only include the option if it is truthy
16
+ return options[option] ? `${qs}&${enc(option)}=${enc(options[option])}` : qs
17
+ }, qString)
18
+ }
@@ -0,0 +1,160 @@
1
+ const assign = require('object-assign')
2
+ const {Observable} = require('../util/observable')
3
+ const polyfilledEventSource = require('@sanity/eventsource')
4
+ const pick = require('../util/pick')
5
+ const defaults = require('../util/defaults')
6
+ const encodeQueryString = require('./encodeQueryString')
7
+
8
+ // Limit is 16K for a _request_, eg including headers. Have to account for an
9
+ // unknown range of headers, but an average EventSource request from Chrome seems
10
+ // to have around 700 bytes of cruft, so let us account for 1.2K to be "safe"
11
+ const MAX_URL_LENGTH = 16000 - 1200
12
+ const EventSource = polyfilledEventSource
13
+
14
+ const possibleOptions = [
15
+ 'includePreviousRevision',
16
+ 'includeResult',
17
+ 'visibility',
18
+ 'effectFormat',
19
+ 'tag',
20
+ ]
21
+
22
+ const defaultOptions = {
23
+ includeResult: true,
24
+ }
25
+
26
+ module.exports = function listen(query, params, opts = {}) {
27
+ const {url, token, withCredentials, requestTagPrefix} = this.clientConfig
28
+ const tag = opts.tag && requestTagPrefix ? [requestTagPrefix, opts.tag].join('.') : opts.tag
29
+ const options = {...defaults(opts, defaultOptions), tag}
30
+ const listenOpts = pick(options, possibleOptions)
31
+ const qs = encodeQueryString({query, params, options: listenOpts, tag})
32
+
33
+ const uri = `${url}${this.getDataUrl('listen', qs)}`
34
+ if (uri.length > MAX_URL_LENGTH) {
35
+ return new Observable((observer) => observer.error(new Error('Query too large for listener')))
36
+ }
37
+
38
+ const listenFor = options.events ? options.events : ['mutation']
39
+ const shouldEmitReconnect = listenFor.indexOf('reconnect') !== -1
40
+
41
+ const esOptions = {}
42
+ if (token || withCredentials) {
43
+ esOptions.withCredentials = true
44
+ }
45
+
46
+ if (token) {
47
+ esOptions.headers = {
48
+ Authorization: `Bearer ${token}`,
49
+ }
50
+ }
51
+
52
+ return new Observable((observer) => {
53
+ let es = getEventSource()
54
+ let reconnectTimer
55
+ let stopped = false
56
+
57
+ function onError() {
58
+ if (stopped) {
59
+ return
60
+ }
61
+
62
+ emitReconnect()
63
+
64
+ // Allow event handlers of `emitReconnect` to cancel/close the reconnect attempt
65
+ if (stopped) {
66
+ return
67
+ }
68
+
69
+ // Unless we've explicitly stopped the ES (in which case `stopped` should be true),
70
+ // we should never be in a disconnected state. By default, EventSource will reconnect
71
+ // automatically, in which case it sets readyState to `CONNECTING`, but in some cases
72
+ // (like when a laptop lid is closed), it closes the connection. In these cases we need
73
+ // to explicitly reconnect.
74
+ if (es.readyState === EventSource.CLOSED) {
75
+ unsubscribe()
76
+ clearTimeout(reconnectTimer)
77
+ reconnectTimer = setTimeout(open, 100)
78
+ }
79
+ }
80
+
81
+ function onChannelError(err) {
82
+ observer.error(cooerceError(err))
83
+ }
84
+
85
+ function onMessage(evt) {
86
+ const event = parseEvent(evt)
87
+ return event instanceof Error ? observer.error(event) : observer.next(event)
88
+ }
89
+
90
+ function onDisconnect(evt) {
91
+ stopped = true
92
+ unsubscribe()
93
+ observer.complete()
94
+ }
95
+
96
+ function unsubscribe() {
97
+ es.removeEventListener('error', onError, false)
98
+ es.removeEventListener('channelError', onChannelError, false)
99
+ es.removeEventListener('disconnect', onDisconnect, false)
100
+ listenFor.forEach((type) => es.removeEventListener(type, onMessage, false))
101
+ es.close()
102
+ }
103
+
104
+ function emitReconnect() {
105
+ if (shouldEmitReconnect) {
106
+ observer.next({type: 'reconnect'})
107
+ }
108
+ }
109
+
110
+ function getEventSource() {
111
+ const evs = new EventSource(uri, esOptions)
112
+ evs.addEventListener('error', onError, false)
113
+ evs.addEventListener('channelError', onChannelError, false)
114
+ evs.addEventListener('disconnect', onDisconnect, false)
115
+ listenFor.forEach((type) => evs.addEventListener(type, onMessage, false))
116
+ return evs
117
+ }
118
+
119
+ function open() {
120
+ es = getEventSource()
121
+ }
122
+
123
+ function stop() {
124
+ stopped = true
125
+ unsubscribe()
126
+ }
127
+
128
+ return stop
129
+ })
130
+ }
131
+
132
+ function parseEvent(event) {
133
+ try {
134
+ const data = (event.data && JSON.parse(event.data)) || {}
135
+ return assign({type: event.type}, data)
136
+ } catch (err) {
137
+ return err
138
+ }
139
+ }
140
+
141
+ function cooerceError(err) {
142
+ if (err instanceof Error) {
143
+ return err
144
+ }
145
+
146
+ const evt = parseEvent(err)
147
+ return evt instanceof Error ? evt : new Error(extractErrorMessage(evt))
148
+ }
149
+
150
+ function extractErrorMessage(err) {
151
+ if (!err.error) {
152
+ return err.message || 'Unknown listener error'
153
+ }
154
+
155
+ if (err.error.description) {
156
+ return err.error.description
157
+ }
158
+
159
+ return typeof err.error === 'string' ? err.error : JSON.stringify(err.error, null, 2)
160
+ }
@@ -0,0 +1,124 @@
1
+ const assign = require('object-assign')
2
+ const getSelection = require('../util/getSelection')
3
+ const validate = require('../validators')
4
+ const validateObject = validate.validateObject
5
+ const validateInsert = validate.validateInsert
6
+
7
+ function Patch(selection, operations = {}, client = null) {
8
+ this.selection = selection
9
+ this.operations = assign({}, operations)
10
+ this.client = client
11
+ }
12
+
13
+ assign(Patch.prototype, {
14
+ clone() {
15
+ return new Patch(this.selection, assign({}, this.operations), this.client)
16
+ },
17
+
18
+ set(props) {
19
+ return this._assign('set', props)
20
+ },
21
+
22
+ diffMatchPatch(props) {
23
+ validateObject('diffMatchPatch', props)
24
+ return this._assign('diffMatchPatch', props)
25
+ },
26
+
27
+ unset(attrs) {
28
+ if (!Array.isArray(attrs)) {
29
+ throw new Error('unset(attrs) takes an array of attributes to unset, non-array given')
30
+ }
31
+
32
+ this.operations = assign({}, this.operations, {unset: attrs})
33
+ return this
34
+ },
35
+
36
+ setIfMissing(props) {
37
+ return this._assign('setIfMissing', props)
38
+ },
39
+
40
+ replace(props) {
41
+ validateObject('replace', props)
42
+ return this._set('set', {$: props}) // eslint-disable-line id-length
43
+ },
44
+
45
+ inc(props) {
46
+ return this._assign('inc', props)
47
+ },
48
+
49
+ dec(props) {
50
+ return this._assign('dec', props)
51
+ },
52
+
53
+ insert(at, selector, items) {
54
+ validateInsert(at, selector, items)
55
+ return this._assign('insert', {[at]: selector, items})
56
+ },
57
+
58
+ append(selector, items) {
59
+ return this.insert('after', `${selector}[-1]`, items)
60
+ },
61
+
62
+ prepend(selector, items) {
63
+ return this.insert('before', `${selector}[0]`, items)
64
+ },
65
+
66
+ splice(selector, start, deleteCount, items) {
67
+ // Negative indexes doesn't mean the same in Sanity as they do in JS;
68
+ // -1 means "actually at the end of the array", which allows inserting
69
+ // at the end of the array without knowing its length. We therefore have
70
+ // to substract negative indexes by one to match JS. If you want Sanity-
71
+ // behaviour, just use `insert('replace', selector, items)` directly
72
+ const delAll = typeof deleteCount === 'undefined' || deleteCount === -1
73
+ const startIndex = start < 0 ? start - 1 : start
74
+ const delCount = delAll ? -1 : Math.max(0, start + deleteCount)
75
+ const delRange = startIndex < 0 && delCount >= 0 ? '' : delCount
76
+ const rangeSelector = `${selector}[${startIndex}:${delRange}]`
77
+ return this.insert('replace', rangeSelector, items || [])
78
+ },
79
+
80
+ ifRevisionId(rev) {
81
+ this.operations.ifRevisionID = rev
82
+ return this
83
+ },
84
+
85
+ serialize() {
86
+ return assign(getSelection(this.selection), this.operations)
87
+ },
88
+
89
+ toJSON() {
90
+ return this.serialize()
91
+ },
92
+
93
+ commit(options = {}) {
94
+ if (!this.client) {
95
+ throw new Error(
96
+ 'No `client` passed to patch, either provide one or pass the ' +
97
+ 'patch to a clients `mutate()` method'
98
+ )
99
+ }
100
+
101
+ const returnFirst = typeof this.selection === 'string'
102
+ const opts = assign({returnFirst, returnDocuments: true}, options)
103
+ return this.client.mutate({patch: this.serialize()}, opts)
104
+ },
105
+
106
+ reset() {
107
+ this.operations = {}
108
+ return this
109
+ },
110
+
111
+ _set(op, props) {
112
+ return this._assign(op, props, false)
113
+ },
114
+
115
+ _assign(op, props, merge = true) {
116
+ validateObject(op, props)
117
+ this.operations = assign({}, this.operations, {
118
+ [op]: assign({}, (merge && this.operations[op]) || {}, props),
119
+ })
120
+ return this
121
+ },
122
+ })
123
+
124
+ module.exports = Patch
@@ -0,0 +1,106 @@
1
+ const assign = require('object-assign')
2
+ const validators = require('../validators')
3
+ const Patch = require('./patch')
4
+
5
+ const defaultMutateOptions = {returnDocuments: false}
6
+
7
+ function Transaction(operations = [], client, transactionId) {
8
+ this.trxId = transactionId
9
+ this.operations = operations
10
+ this.client = client
11
+ }
12
+
13
+ assign(Transaction.prototype, {
14
+ clone() {
15
+ return new Transaction(this.operations.slice(0), this.client, this.trxId)
16
+ },
17
+
18
+ create(doc) {
19
+ validators.validateObject('create', doc)
20
+ return this._add({create: doc})
21
+ },
22
+
23
+ createIfNotExists(doc) {
24
+ const op = 'createIfNotExists'
25
+ validators.validateObject(op, doc)
26
+ validators.requireDocumentId(op, doc)
27
+ return this._add({[op]: doc})
28
+ },
29
+
30
+ createOrReplace(doc) {
31
+ const op = 'createOrReplace'
32
+ validators.validateObject(op, doc)
33
+ validators.requireDocumentId(op, doc)
34
+ return this._add({[op]: doc})
35
+ },
36
+
37
+ delete(documentId) {
38
+ validators.validateDocumentId('delete', documentId)
39
+ return this._add({delete: {id: documentId}})
40
+ },
41
+
42
+ patch(documentId, patchOps) {
43
+ const isBuilder = typeof patchOps === 'function'
44
+ const isPatch = documentId instanceof Patch
45
+
46
+ // transaction.patch(client.patch('documentId').inc({visits: 1}))
47
+ if (isPatch) {
48
+ return this._add({patch: documentId.serialize()})
49
+ }
50
+
51
+ // patch => patch.inc({visits: 1}).set({foo: 'bar'})
52
+ if (isBuilder) {
53
+ const patch = patchOps(new Patch(documentId, {}, this.client))
54
+ if (!(patch instanceof Patch)) {
55
+ throw new Error('function passed to `patch()` must return the patch')
56
+ }
57
+
58
+ return this._add({patch: patch.serialize()})
59
+ }
60
+
61
+ return this._add({patch: assign({id: documentId}, patchOps)})
62
+ },
63
+
64
+ transactionId(id) {
65
+ if (!id) {
66
+ return this.trxId
67
+ }
68
+
69
+ this.trxId = id
70
+ return this
71
+ },
72
+
73
+ serialize() {
74
+ return this.operations.slice()
75
+ },
76
+
77
+ toJSON() {
78
+ return this.serialize()
79
+ },
80
+
81
+ commit(options) {
82
+ if (!this.client) {
83
+ throw new Error(
84
+ 'No `client` passed to transaction, either provide one or pass the ' +
85
+ 'transaction to a clients `mutate()` method'
86
+ )
87
+ }
88
+
89
+ return this.client.mutate(
90
+ this.serialize(),
91
+ assign({transactionId: this.trxId}, defaultMutateOptions, options || {})
92
+ )
93
+ },
94
+
95
+ reset() {
96
+ this.operations = []
97
+ return this
98
+ },
99
+
100
+ _add(mut) {
101
+ this.operations.push(mut)
102
+ return this
103
+ },
104
+ })
105
+
106
+ module.exports = Transaction
@@ -0,0 +1,31 @@
1
+ const assign = require('object-assign')
2
+ const validate = require('../validators')
3
+
4
+ function DatasetsClient(client) {
5
+ this.request = client.request.bind(client)
6
+ }
7
+
8
+ assign(DatasetsClient.prototype, {
9
+ create(name, options) {
10
+ return this._modify('PUT', name, options)
11
+ },
12
+
13
+ edit(name, options) {
14
+ return this._modify('PATCH', name, options)
15
+ },
16
+
17
+ delete(name) {
18
+ return this._modify('DELETE', name)
19
+ },
20
+
21
+ list() {
22
+ return this.request({uri: '/datasets'})
23
+ },
24
+
25
+ _modify(method, name, body) {
26
+ validate.dataset(name)
27
+ return this.request({method, uri: `/datasets/${name}`, body})
28
+ },
29
+ })
30
+
31
+ module.exports = DatasetsClient
@@ -0,0 +1 @@
1
+ module.exports = []
@@ -0,0 +1,57 @@
1
+ const makeError = require('make-error')
2
+ const assign = require('object-assign')
3
+
4
+ function ClientError(res) {
5
+ const props = extractErrorProps(res)
6
+ ClientError.super.call(this, props.message)
7
+ assign(this, props)
8
+ }
9
+
10
+ function ServerError(res) {
11
+ const props = extractErrorProps(res)
12
+ ServerError.super.call(this, props.message)
13
+ assign(this, props)
14
+ }
15
+
16
+ function extractErrorProps(res) {
17
+ const body = res.body
18
+ const props = {
19
+ response: res,
20
+ statusCode: res.statusCode,
21
+ responseBody: stringifyBody(body, res),
22
+ }
23
+
24
+ // API/Boom style errors ({statusCode, error, message})
25
+ if (body.error && body.message) {
26
+ props.message = `${body.error} - ${body.message}`
27
+ return props
28
+ }
29
+
30
+ // Query/database errors ({error: {description, other, arb, props}})
31
+ if (body.error && body.error.description) {
32
+ props.message = body.error.description
33
+ props.details = body.error
34
+ return props
35
+ }
36
+
37
+ // Other, more arbitrary errors
38
+ props.message = body.error || body.message || httpErrorMessage(res)
39
+ return props
40
+ }
41
+
42
+ function httpErrorMessage(res) {
43
+ const statusMessage = res.statusMessage ? ` ${res.statusMessage}` : ''
44
+ return `${res.method}-request to ${res.url} resulted in HTTP ${res.statusCode}${statusMessage}`
45
+ }
46
+
47
+ function stringifyBody(body, res) {
48
+ const contentType = (res.headers['content-type'] || '').toLowerCase()
49
+ const isJson = contentType.indexOf('application/json') !== -1
50
+ return isJson ? JSON.stringify(body, null, 2) : body
51
+ }
52
+
53
+ makeError(ClientError)
54
+ makeError(ServerError)
55
+
56
+ exports.ClientError = ClientError
57
+ exports.ServerError = ServerError
@@ -0,0 +1,13 @@
1
+ const retry = require('get-it/lib-node/middleware/retry')
2
+ const debug = require('get-it/lib-node/middleware/debug')
3
+ const headers = require('get-it/lib-node/middleware/headers')
4
+
5
+ const pkg = require('../../package.json')
6
+
7
+ const middleware = [
8
+ debug({verbose: true, namespace: 'sanity:client'}),
9
+ headers({'User-Agent': `${pkg.name} ${pkg.version}`}),
10
+ retry({maxRetries: 3}),
11
+ ]
12
+
13
+ module.exports = middleware