onyx-services
The place where your API implementations go. Currently only VibeApi
is available but will likely include service provider classes for additional vendors in the future (ie: content, localization, etc.)
Typically used for accessing external resources from within your getStore
function -- onyx-app
's <StoreWrapper />
provides a vibeApi
instance to it's getStore
call.
Conventions for Services
-
Always return a
Promise
from your service calls.- This allows for chaining the success / failure result from the calling locationthe calling location
-
Avoid
async
/await
in favor ofPromise
-
Camelize returned data structure.
- Use
onyx-common/camelize
, just pass the obj in it takes care of the rest:
- Use
import camelize from 'onyx-common/camelize'
const obj = {
first_name: 'Bob',
last_name: 'Johnson'
}
const camelizedObj = camelize(obj)
/*
camelizedObj = {
firstName: 'Bob',
lastName: 'Johnson'
}
*/
-
Always use traditional
function ()
syntax for class methods- If arrow methods
() => {}
are used, thethis
context is lost from the compiled class.
- If arrow methods
fetchWrap
helper
fetchWrap
wraps the default fetch
function to make it a bit more friendly to use. It accepts the following options:
- url: The URL to use for this request
-
method:
POST
,DELETE
orGET
.GET
is default - data: an array of data to send with request
-
urlMerge: if you provide placeholders in the
url
in the form ofhttp://google.com/{foo}/{bar}
send in an array with{ 'foo': '1', 'bar': '2' }
and it becomeshttp://google.com/1/2
-
requestType:
json
orform
to change request headers. Has no impact onGET
requests - fetchOptions: direct fetch api options to pass through and override any convenience additions
It also wraps the return to make it easier to work with and ensure a promise is always returned.
Structure of a Service Class
To make services more manageable, they should be broken out into sub files for each area of responsibility (roughly mapped often to onyx-scenes-*
packages, but not neccessarily). responsibilities and then rolled up into a single namespace.
Here's an example of how the VibeApi
class is composed:
constructor
and other shared functionality
Base object with VibeApi/VibeApiBase.js
const VibeApiBase = function () {}
VibeApiBase.prototype.constructor = function ({ baseUrl }) {
this.baseUrl = baseUrl
}
VibeApiBase.prototype.getUrl = function (path) {
return this.baseUrl + path
}
export default VibeApiBase
prototype
decorators
Add one or more VibeAPi/VibeApiAuth.js
import formurlencoded from 'onyx-common/form-urlencoded'
import urlencode from 'onyx-common/urlencode'
import fetchWrap from 'onyx-common/fetchWrap'
import camelize from 'onyx-common/camelize'
const VibeApiAuth = ({ prototype }) => {
prototype.login = function ({ username, password }) {
// ...rest of function
// example of how to use fetchWrap helper
return fetchWrap(payload)
.then(res => Promise.resolve(normalize(res)))
.catch(error => Promise.reject(Error('loginError', error)))
}
prototype.logout = function ({ authenticationToken }) {
// ...rest of function
// example of how to use fetchWrap helper
return fetchWrap(payload)
.then(() => Promise.resolve())
.catch(() => Promise.reject(Error('logoutError')))
}
}
export default VibeApiAuth
Combine Base & decorator(s)
VibeApi/VibeApi.js
import VibeApi from './VibeApiBase'
import VibeApiAuth from './VibeApiAuth'
// add more decorators in the future
// import VibeApiResourceLibrary from './VibeApiResourceLibrary'
// decorate the prototype
VibeApiAuth(VibeApi)
// add more decorators in the future
// VibeApiResourceLibrary(VibeApi)
export default VibeApi
The final class is now usable like so:
import VibeApiFactory from 'onyx-services/VibeApi'
const VibeApi = new VibeApiFactory({
baseUrl: '/your/base/url'
})