Skip to content

Commit 602ef35

Browse files
authored
Make client API CDN aware (#150)
* [client] Make client CDN API aware * [client] Add documentation for createOrReplace/createIfNotExists methods
1 parent 5bf646a commit 602ef35

File tree

5 files changed

+179
-6
lines changed

5 files changed

+179
-6
lines changed

packages/@sanity/client/README.md

+41-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ const sanityClient = require('@sanity/client')
2222
const client = sanityClient({
2323
projectId: 'your-project-id',
2424
dataset: 'bikeshop',
25-
token: 'sanity-auth-token' // or leave blank to be anonymous user
25+
token: 'sanity-auth-token', // or leave blank to be anonymous user
26+
useCdn: true // `false` if you want to ensure fresh data
2627
})
2728
```
2829

@@ -97,6 +98,45 @@ client.create(doc).then(res => {
9798
Create a document. Argument is a plain JS object representing the document. It must contain a `_type` attribute. It *may* contain an `_id`. If an ID is not specified, it will automatically be created.
9899

99100

101+
### Creating/replacing documents
102+
103+
```js
104+
const doc = {
105+
_id: 'my-bike',
106+
_type: 'bike',
107+
name: 'Bengler Tandem Extraordinaire',
108+
seats: 2
109+
}
110+
111+
client.createOrReplace(doc).then(res => {
112+
console.log(`Bike was created, document ID is ${res._id}`)
113+
})
114+
```
115+
116+
`client.createOrReplace(doc)`
117+
118+
If you are not sure whether or not a document exists but want to overwrite it if it does, you can use the `createOrReplace()` method. When using this method, the document must contain an `_id` attribute.
119+
120+
### Creating if not already present
121+
122+
```js
123+
const doc = {
124+
_id: 'my-bike',
125+
_type: 'bike',
126+
name: 'Bengler Tandem Extraordinaire',
127+
seats: 2
128+
}
129+
130+
client.createIfNotExists(doc).then(res => {
131+
console.log('Bike was created (or was already present)')
132+
})
133+
```
134+
135+
`client.createIfNotExists(doc)`
136+
137+
If you want to create a document if it does not already exist, but fall back without error if it does, you can use the `createIfNotExists()` method. When using this method, the document must contain an `_id` attribute.
138+
139+
100140
### Patch/update a document
101141

102142
```js

packages/@sanity/client/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
},
1919
"dependencies": {
2020
"@sanity/eventsource": "^0.108.0",
21-
"@sanity/observable": "0.110.0",
21+
"@sanity/generate-help-url": "^0.108.0",
22+
"@sanity/observable": "^0.110.0",
2223
"deep-assign": "^2.0.0",
2324
"get-it": "^2.0.2",
2425
"in-publish": "^2.0.0",

packages/@sanity/client/src/config.js

+33
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,35 @@
1+
const generateHelpUrl = require('@sanity/generate-help-url')
12
const assign = require('object-assign')
23
const validate = require('./validators')
34

5+
const defaultCdnHost = 'cdnapi.sanity.io'
46
const defaultConfig = {
57
apiHost: 'https://api.sanity.io',
68
useProjectHostname: true,
79
gradientMode: false,
810
isPromiseAPI: true
911
}
1012

13+
const cdnWarning = [
14+
'You are not using the Sanity CDN. That means your data is always fresh, but the CDN is faster and',
15+
`cheaper. Think about it! For more info, see ${generateHelpUrl('js-client-cdn-configuration')}.`,
16+
'To hide this warning, please set the `useCdn` option to either `true` or `false` when creating',
17+
'the client.'
18+
]
19+
20+
const printCdnWarning = (() => {
21+
let hasWarned = false
22+
return () => {
23+
if (hasWarned) {
24+
return
25+
}
26+
27+
// eslint-disable-next-line no-console
28+
console.warn(cdnWarning.join(' '))
29+
hasWarned = true
30+
}
31+
})()
32+
1133
exports.defaultConfig = defaultConfig
1234

1335
exports.initConfig = (config, prevConfig) => {
@@ -36,19 +58,30 @@ exports.initConfig = (config, prevConfig) => {
3658
validate.dataset(newConfig.dataset, newConfig.gradientMode)
3759
}
3860

61+
newConfig.isDefaultApi = newConfig.apiHost === defaultConfig.apiHost
62+
newConfig.useCdn = Boolean(newConfig.useCdn) && !newConfig.token && !newConfig.withCredentials
63+
3964
if (newConfig.gradientMode) {
4065
newConfig.url = newConfig.apiHost
66+
newConfig.cdnUrl = newConfig.apiHost
4167
} else {
4268
const hostParts = newConfig.apiHost.split('://', 2)
4369
const protocol = hostParts[0]
4470
const host = hostParts[1]
71+
const cdnHost = newConfig.isDefaultApi ? defaultCdnHost : host
4572

4673
if (newConfig.useProjectHostname) {
4774
newConfig.url = `${protocol}://${newConfig.projectId}.${host}/v1`
75+
newConfig.cdnUrl = `${protocol}://${newConfig.projectId}.${cdnHost}/v1`
4876
} else {
4977
newConfig.url = `${newConfig.apiHost}/v1`
78+
newConfig.cdnUrl = newConfig.url
5079
}
5180
}
5281

82+
if (typeof config.useCdn === 'undefined') {
83+
printCdnWarning()
84+
}
85+
5386
return newConfig
5487
}

packages/@sanity/client/src/sanityClient.js

+11-3
Original file line numberDiff line numberDiff line change
@@ -52,19 +52,27 @@ assign(SanityClient.prototype, {
5252
return this
5353
},
5454

55-
getUrl(uri) {
56-
return `${this.clientConfig.url}/${uri.replace(/^\//, '')}`
55+
getUrl(uri, canUseCdn = false) {
56+
const base = canUseCdn ? this.clientConfig.cdnUrl : this.clientConfig.url
57+
return `${base}/${uri.replace(/^\//, '')}`
5758
},
5859

5960
isPromiseAPI() {
6061
return this.clientConfig.isPromiseAPI
6162
},
6263

6364
_requestObservable(options) {
65+
const uri = options.url || options.uri
66+
const canUseCdn = (
67+
this.clientConfig.useCdn
68+
&& ['GET', 'HEAD'].indexOf(options.method || 'GET') >= 0
69+
&& uri.indexOf('/data/') === 0
70+
)
71+
6472
return httpRequest(mergeOptions(
6573
getRequestOptions(this.clientConfig),
6674
options,
67-
{url: this.getUrl(options.url || options.uri)}
75+
{url: this.getUrl(uri, canUseCdn)}
6876
), this.clientConfig.requester)
6977
},
7078

packages/@sanity/client/test/client.test.js

+92-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,13 @@ const noop = () => {} // eslint-disable-line no-empty-function
1919
const apiHost = 'api.sanity.url'
2020
const defaultProjectId = 'bf1942'
2121
const projectHost = projectId => `https://${projectId || defaultProjectId}.${apiHost}`
22-
const clientConfig = {apiHost: `https://${apiHost}`, projectId: 'bf1942', dataset: 'foo'}
22+
const clientConfig = {
23+
apiHost: `https://${apiHost}`,
24+
projectId: 'bf1942',
25+
dataset: 'foo',
26+
useCdn: false
27+
}
28+
2329
const getClient = conf => sanityClient(assign({}, clientConfig, conf || {}))
2430
const fixture = name => path.join(__dirname, 'fixtures', name)
2531
const ifError = t => err => {
@@ -1255,6 +1261,91 @@ test('can retrieve user by id', t => {
12551261
}, ifError(t))
12561262
})
12571263

1264+
/*****************
1265+
* CDN API USAGE *
1266+
*****************/
1267+
test('will use live API by default', t => {
1268+
const client = sanityClient({projectId: 'abc123', dataset: 'foo'})
1269+
1270+
const response = {result: []}
1271+
nock('https://abc123.api.sanity.io')
1272+
.get('/v1/data/query/foo?query=*')
1273+
.reply(200, response)
1274+
1275+
client.fetch('*')
1276+
.then(docs => {
1277+
t.equal(docs.length, 0)
1278+
})
1279+
.catch(t.ifError)
1280+
.then(t.end)
1281+
})
1282+
1283+
test('will use CDN API if told to', t => {
1284+
const client = sanityClient({projectId: 'abc123', dataset: 'foo', useCdn: true})
1285+
1286+
const response = {result: []}
1287+
nock('https://abc123.cdnapi.sanity.io')
1288+
.get('/v1/data/query/foo?query=*')
1289+
.reply(200, response)
1290+
1291+
client.fetch('*')
1292+
.then(docs => {
1293+
t.equal(docs.length, 0)
1294+
})
1295+
.catch(t.ifError)
1296+
.then(t.end)
1297+
})
1298+
1299+
test('will use live API for mutations', t => {
1300+
const client = sanityClient({projectId: 'abc123', dataset: 'foo', useCdn: true})
1301+
1302+
nock('https://abc123.api.sanity.io')
1303+
.post('/v1/data/mutate/foo?returnIds=true&returnDocuments=true&visibility=sync')
1304+
.reply(200, {})
1305+
1306+
client.create({_type: 'foo', title: 'yep'})
1307+
.then(noop)
1308+
.catch(t.ifError)
1309+
.then(t.end)
1310+
})
1311+
1312+
test('will use live API if token is specified', t => {
1313+
const client = sanityClient({
1314+
projectId: 'abc123',
1315+
dataset: 'foo',
1316+
useCdn: true,
1317+
token: 'foo'
1318+
})
1319+
1320+
const reqheaders = {Authorization: 'Bearer foo'}
1321+
nock('https://abc123.api.sanity.io', {reqheaders})
1322+
.get('/v1/data/query/foo?query=*')
1323+
.reply(200, {result: []})
1324+
1325+
client.fetch('*')
1326+
.then(noop)
1327+
.catch(t.ifError)
1328+
.then(t.end)
1329+
})
1330+
1331+
test('will use live API if withCredentials is set to true', t => {
1332+
const client = sanityClient({
1333+
withCredentials: true,
1334+
projectId: 'abc123',
1335+
dataset: 'foo',
1336+
useCdn: true,
1337+
})
1338+
1339+
nock('https://abc123.api.sanity.io')
1340+
.get('/v1/data/query/foo?query=*')
1341+
.reply(200, {result: []})
1342+
1343+
client.fetch('*')
1344+
.then(noop)
1345+
.catch(t.ifError)
1346+
.then(t.end)
1347+
})
1348+
12581349
/*****************
12591350
* HTTP REQUESTS *
12601351
*****************/

0 commit comments

Comments
 (0)