Skip to content

Commit

Permalink
Make client API CDN aware (#150)
Browse files Browse the repository at this point in the history
* [client] Make client CDN API aware

* [client] Add documentation for createOrReplace/createIfNotExists methods
  • Loading branch information
rexxars committed Aug 31, 2017
1 parent 5bf646a commit 602ef35
Show file tree
Hide file tree
Showing 5 changed files with 179 additions and 6 deletions.
42 changes: 41 additions & 1 deletion packages/@sanity/client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ const sanityClient = require('@sanity/client')
const client = sanityClient({
projectId: 'your-project-id',
dataset: 'bikeshop',
token: 'sanity-auth-token' // or leave blank to be anonymous user
token: 'sanity-auth-token', // or leave blank to be anonymous user
useCdn: true // `false` if you want to ensure fresh data
})
```

Expand Down Expand Up @@ -97,6 +98,45 @@ client.create(doc).then(res => {
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.


### Creating/replacing documents

```js
const doc = {
_id: 'my-bike',
_type: 'bike',
name: 'Bengler Tandem Extraordinaire',
seats: 2
}

client.createOrReplace(doc).then(res => {
console.log(`Bike was created, document ID is ${res._id}`)
})
```

`client.createOrReplace(doc)`

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.

### Creating if not already present

```js
const doc = {
_id: 'my-bike',
_type: 'bike',
name: 'Bengler Tandem Extraordinaire',
seats: 2
}

client.createIfNotExists(doc).then(res => {
console.log('Bike was created (or was already present)')
})
```

`client.createIfNotExists(doc)`

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.


### Patch/update a document

```js
Expand Down
3 changes: 2 additions & 1 deletion packages/@sanity/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
},
"dependencies": {
"@sanity/eventsource": "^0.108.0",
"@sanity/observable": "0.110.0",
"@sanity/generate-help-url": "^0.108.0",
"@sanity/observable": "^0.110.0",
"deep-assign": "^2.0.0",
"get-it": "^2.0.2",
"in-publish": "^2.0.0",
Expand Down
33 changes: 33 additions & 0 deletions packages/@sanity/client/src/config.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,35 @@
const generateHelpUrl = require('@sanity/generate-help-url')
const assign = require('object-assign')
const validate = require('./validators')

const defaultCdnHost = 'cdnapi.sanity.io'
const defaultConfig = {
apiHost: 'https://api.sanity.io',
useProjectHostname: true,
gradientMode: false,
isPromiseAPI: true
}

const cdnWarning = [
'You are not using the Sanity CDN. That means your data is always fresh, but the CDN is faster and',
`cheaper. Think about it! For more info, see ${generateHelpUrl('js-client-cdn-configuration')}.`,
'To hide this warning, please set the `useCdn` option to either `true` or `false` when creating',
'the client.'
]

const printCdnWarning = (() => {
let hasWarned = false
return () => {
if (hasWarned) {
return
}

// eslint-disable-next-line no-console
console.warn(cdnWarning.join(' '))
hasWarned = true
}
})()

exports.defaultConfig = defaultConfig

exports.initConfig = (config, prevConfig) => {
Expand Down Expand Up @@ -36,19 +58,30 @@ exports.initConfig = (config, prevConfig) => {
validate.dataset(newConfig.dataset, newConfig.gradientMode)
}

newConfig.isDefaultApi = newConfig.apiHost === defaultConfig.apiHost
newConfig.useCdn = Boolean(newConfig.useCdn) && !newConfig.token && !newConfig.withCredentials

if (newConfig.gradientMode) {
newConfig.url = newConfig.apiHost
newConfig.cdnUrl = newConfig.apiHost
} else {
const hostParts = newConfig.apiHost.split('://', 2)
const protocol = hostParts[0]
const host = hostParts[1]
const cdnHost = newConfig.isDefaultApi ? defaultCdnHost : host

if (newConfig.useProjectHostname) {
newConfig.url = `${protocol}://${newConfig.projectId}.${host}/v1`
newConfig.cdnUrl = `${protocol}://${newConfig.projectId}.${cdnHost}/v1`
} else {
newConfig.url = `${newConfig.apiHost}/v1`
newConfig.cdnUrl = newConfig.url
}
}

if (typeof config.useCdn === 'undefined') {
printCdnWarning()
}

return newConfig
}
14 changes: 11 additions & 3 deletions packages/@sanity/client/src/sanityClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,19 +52,27 @@ assign(SanityClient.prototype, {
return this
},

getUrl(uri) {
return `${this.clientConfig.url}/${uri.replace(/^\//, '')}`
getUrl(uri, canUseCdn = false) {
const base = canUseCdn ? this.clientConfig.cdnUrl : this.clientConfig.url
return `${base}/${uri.replace(/^\//, '')}`
},

isPromiseAPI() {
return this.clientConfig.isPromiseAPI
},

_requestObservable(options) {
const uri = options.url || options.uri
const canUseCdn = (
this.clientConfig.useCdn
&& ['GET', 'HEAD'].indexOf(options.method || 'GET') >= 0
&& uri.indexOf('/data/') === 0
)

return httpRequest(mergeOptions(
getRequestOptions(this.clientConfig),
options,
{url: this.getUrl(options.url || options.uri)}
{url: this.getUrl(uri, canUseCdn)}
), this.clientConfig.requester)
},

Expand Down
93 changes: 92 additions & 1 deletion packages/@sanity/client/test/client.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,13 @@ const noop = () => {} // eslint-disable-line no-empty-function
const apiHost = 'api.sanity.url'
const defaultProjectId = 'bf1942'
const projectHost = projectId => `https://${projectId || defaultProjectId}.${apiHost}`
const clientConfig = {apiHost: `https://${apiHost}`, projectId: 'bf1942', dataset: 'foo'}
const clientConfig = {
apiHost: `https://${apiHost}`,
projectId: 'bf1942',
dataset: 'foo',
useCdn: false
}

const getClient = conf => sanityClient(assign({}, clientConfig, conf || {}))
const fixture = name => path.join(__dirname, 'fixtures', name)
const ifError = t => err => {
Expand Down Expand Up @@ -1255,6 +1261,91 @@ test('can retrieve user by id', t => {
}, ifError(t))
})

/*****************
* CDN API USAGE *
*****************/
test('will use live API by default', t => {
const client = sanityClient({projectId: 'abc123', dataset: 'foo'})

const response = {result: []}
nock('https://abc123.api.sanity.io')
.get('/v1/data/query/foo?query=*')
.reply(200, response)

client.fetch('*')
.then(docs => {
t.equal(docs.length, 0)
})
.catch(t.ifError)
.then(t.end)
})

test('will use CDN API if told to', t => {
const client = sanityClient({projectId: 'abc123', dataset: 'foo', useCdn: true})

const response = {result: []}
nock('https://abc123.cdnapi.sanity.io')
.get('/v1/data/query/foo?query=*')
.reply(200, response)

client.fetch('*')
.then(docs => {
t.equal(docs.length, 0)
})
.catch(t.ifError)
.then(t.end)
})

test('will use live API for mutations', t => {
const client = sanityClient({projectId: 'abc123', dataset: 'foo', useCdn: true})

nock('https://abc123.api.sanity.io')
.post('/v1/data/mutate/foo?returnIds=true&returnDocuments=true&visibility=sync')
.reply(200, {})

client.create({_type: 'foo', title: 'yep'})
.then(noop)
.catch(t.ifError)
.then(t.end)
})

test('will use live API if token is specified', t => {
const client = sanityClient({
projectId: 'abc123',
dataset: 'foo',
useCdn: true,
token: 'foo'
})

const reqheaders = {Authorization: 'Bearer foo'}
nock('https://abc123.api.sanity.io', {reqheaders})
.get('/v1/data/query/foo?query=*')
.reply(200, {result: []})

client.fetch('*')
.then(noop)
.catch(t.ifError)
.then(t.end)
})

test('will use live API if withCredentials is set to true', t => {
const client = sanityClient({
withCredentials: true,
projectId: 'abc123',
dataset: 'foo',
useCdn: true,
})

nock('https://abc123.api.sanity.io')
.get('/v1/data/query/foo?query=*')
.reply(200, {result: []})

client.fetch('*')
.then(noop)
.catch(t.ifError)
.then(t.end)
})

/*****************
* HTTP REQUESTS *
*****************/
Expand Down

0 comments on commit 602ef35

Please sign in to comment.