@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.
- package/README.md +3 -5
- package/dist/sanityClient.browser.mjs +4317 -0
- package/dist/sanityClient.browser.mjs.map +7 -0
- package/dist/sanityClient.node.js +1129 -0
- package/dist/sanityClient.node.js.map +7 -0
- package/package.json +26 -6
- package/sanityClient.d.ts +3 -2
- package/src/assets/assetsClient.js +135 -0
- package/src/auth/authClient.js +17 -0
- package/src/config.js +96 -0
- package/src/data/dataMethods.js +183 -0
- package/src/data/encodeQueryString.js +18 -0
- package/src/data/listen.js +160 -0
- package/src/data/patch.js +124 -0
- package/src/data/transaction.js +106 -0
- package/src/datasets/datasetsClient.js +31 -0
- package/src/http/browserMiddleware.js +1 -0
- package/src/http/errors.js +57 -0
- package/src/http/nodeMiddleware.js +13 -0
- package/src/http/queryString.js +10 -0
- package/src/http/request.js +54 -0
- package/src/http/requestOptions.js +31 -0
- package/src/projects/projectsClient.js +17 -0
- package/src/sanityClient.js +119 -0
- package/src/users/usersClient.js +13 -0
- package/src/util/defaults.js +8 -0
- package/src/util/getSelection.js +17 -0
- package/src/util/observable.js +11 -0
- package/src/util/once.js +12 -0
- package/src/util/pick.js +9 -0
- package/src/validators.js +76 -0
- package/src/warnings.js +25 -0
- package/test/client.test.js +0 -2561
- package/test/encodeQueryString.test.js +0 -36
- package/test/fixtures/horsehead-nebula.jpg +0 -0
- package/test/fixtures/pdf-sample.pdf +0 -0
- package/test/helpers/sseServer.js +0 -26
- package/test/listen.test.js +0 -205
- 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
|