Repository URL to install this package:
|
Version:
1.2.7 ▾
|
/**
* @file @todo
* 1. fork axios
* 2. make axios compat with fetch interface
*/
// import axios from 'axios'
import { isObj, isArray, EMPTY_OBJ } from 'exotic'
import { always, cloneJSON } from 'chain-able'
import {
stringifyParamsRecursively,
stringifyProperties,
} from '@skava/modules/___dist/oneRequest/deps/queryStringify'
import { config } from '@skava/modules/___dist/oneRequest/config'
const IS_BROWSER = typeof window === 'object'
// @todo @@perf freeze default headers
const defaultPostHeaders = {
'Content-Type': 'application/x-www-form-urlencoded',
}
interface ApolloRequest {
httpRequest: Request
}
type ApolloRequestOrRequest = ApolloRequest | Request
/**
* @type {Map}
*/
class OneRequest {
/**
* @description map of middleware we can call on lifecycle hooks
* @typedef {IntersectionObserverCallback}
*
* @note could also be .subscribe() to add, since it's quite observable-like
*
* ...for now though...
*
* @example
* import Request from 'modules/oneRequest'
* function middlewareForLocalStorage(request: Request, eventData: {}): void {}
* Request.middleware.set('name', middlewareForLocalStorage)
*/
static middleware = new Map();
constructor() {
// store is a standard name for every piece of data being set and get in a class
this.store = {}
// until we fix, set debug always true manually
this.setDebug(true)
// default to POST -- 50/1 POST TO GET
// this.setMethod(POST)
// could be done onCreate
const POST = config.get('POST')
this.setMethod(POST)
this.emit('onCreate')
}
/**
* @event lifecycle middleware called for this event
* @param {String} eventName
* @param {Object} [eventData=""]
* @return {Request} @chainable
*/
emit(eventName, eventData = EMPTY_OBJ) {
// @todo @perf can use Object.seal
const type = { type: eventName }
const arg =
eventData === EMPTY_OBJ
? type
: {
...eventData,
...type,
}
const callMiddleware = (middleware, index) => {
// if (isFunction(middleware.when)) / isSatisfied / @todo
middleware(this, arg)
}
OneRequest.middleware.forEach(callMiddleware)
if (isArray(this.middlewareList)) {
this.middlewareList.forEach(callMiddleware)
}
// !!!!!!!!!
// this.setParams(defaultParams)
return this
}
use(middleware) {
this.middlewareList.push(middleware)
return this
}
/**
* @private
*/
_set(name, value) {
this.store[name] = value
return this
}
_get(name) {
return this.store[name]
}
_has(name) {
return !!this.store[name]
}
/**
*
*/
del() {
//
}
/**
*
*/
get() {
//
}
/**
* patch, put
*/
post() {
//
}
/**
*
*/
then() {
//
}
/**
* ===
*/
type(type) {
this._set('type', type)
}
// cert() {}
// key() {}
// json() {}
// xml() {}
// responseType() {}
// auth('tobi', 'learnboost')
// .withCredentials()
// .redirects(2)
// .on('error', handle)
// --- setters
setDebug(shouldDebug = false) {
return this._set('debug', shouldDebug)
}
url(url) {
return this.setUrl(url)
}
setUrl(url) {
return this._set('url', url)
}
/**
* === sadly is ALSO for query ===
* === matters for POST ===
*/
setParams(params) {
return this._set('params', params)
}
params(params) {
return this.setParams(params)
// return this._set('params', params)
}
/**
* @tutorial https://github.com/node-nock/nock#specifying-request-query-string
* @param {Object} params
* @return {Request} @chainable
*/
setDefaultParams(params) {
// for now just sets params since we object assign
// return this._set('params', params)
return this.setParams(params)
// return this._set('_defaultParams', params)
// return this._set('params', params)
}
/**
* @deprecated - is handled in setData for now
* @TODO
* @description for POST calls
* @param {*} body
*/
setBody(body) {
return this._set('body', body)
}
setData(fixtureData) {
return this._set('data', fixtureData)
}
setFormData(formData) {
return this._set('formData', formData)
}
transformData(fn, data) {
return this._set('data', fn(data))
}
setMethod(method) {
return this._set('method', method)
}
// --- helpers
log() {
if (this.store.debug === true) {
console.log.apply(console, arguments)
}
return this
}
validateRequestStore() {
if (!this._has('url')) {
throw new Error('missing url, use request.setUrl(urlForThisEndpoint)')
}
if (!this._has('data')) {
throw new Error('missing fixture data, use request.setData(fixtureData)')
}
// if (!this._has('params')) {
// console.log(
// 'did not pass in params, might want to disable this log because not every request needs params?'
// )
// }
}
// PATCH, PUT, REQUEST, AXIOSER
get isPost(): boolean {
const methodName = this.store.method.methodName || this.store.methodName
return (
methodName === 'POST' ||
methodName === 'REQUEST' ||
methodName === 'AXIOSER' ||
methodName === 'post'
)
}
get isGet(): boolean {
const methodName = this.store.method.methodName || this.store.methodName
return methodName === 'GET' || methodName === 'get'
// return this.store.method.methodType === 'GET'
}
setMethodType(type) {
console.warn('@deprecated .setMethodType')
return this
}
/**
* @deprecated
*/
setMethodName(methodName) {
console.warn('@deprecated .setMethodName')
this.store.methodName = name
// The actual string representation of 'post'|'get'
return this._set('methodName', methodName)
}
stringifyParams(paramArg = undefined): string {
let params = paramArg || this._get('params')
/**
* HORRIBLE_BAD_SHOULD_BE_FIXED_BY_BACKEND_SHAME
*/
// params.storeId = params.storeid
// @todo @perf
params = cloneJSON(params)
params = stringifyParamsRecursively(params)
return params
}
/**
* @event toRequest
* @event ->onRequest
*
* @see https://github.com/mzabriskie/axios
* @return {AsyncFunction} call to get data
*/
toRequest() {
// @todo - add cloning through chaining or scoped props
// @todo add dynamic url
// add a property to not create fn each time
if (this.dynamicRequest) {
return this.dynamicRequest
}
this.emit('toRequest')
/**
* @note could accept string param as first arg
* @todo could chain set the params or url
*/
// @lint @todo @fixme @split
// eslint-disable-next-line
const dynamicAxiosRequest = async(
dynamicParams = undefined,
dynamicUrl = undefined
) => {
// @NOTE this is how to return mocks
// return this.store.data
let { url, debug, method, onError, onSuccess } = this.store
console.log('[1request]: ' + url)
const FETCHREQUEST = config.get('FETCHREQUEST')
// dynamicParams => formBody
// defaultParams/queryParams => string for the end of the url
let queryParams = this.store.params
let params = queryParams
// merge in dynamic params.. but not everything has params
if (dynamicParams !== undefined) {
// if (params !== undefined) {
// params = Object.assign(params, dynamicParams)
// } else {
// params = Object.assign({}, dynamicParams)
// }
if (params !== undefined) {
params = { ...queryParams, ...dynamicParams }
} else {
params = { ...dynamicParams }
}
}
const constantParams = config.get('constantParams')
Object.assign(params, constantParams)
Object.assign(queryParams, constantParams)
if (process.env.LOG_REQUEST_PARAMS) {
console.log('_______constantParams', constantParams)
console.log('___MERGED_PARAMS', queryParams)
}
if (dynamicUrl !== undefined) {
url = url + '/' + dynamicUrl
}
// handle the transform
if (isObj(params)) {
params = stringifyProperties(params)
}
// NOW CHANGED BACK
// if (clientDomain === 'reactdemo.skavaone.com') {
// queryParams.storeId = storeId
// } else {
// queryParams.storeid = storeId
// }
// queryParams.storeId = queryParams.storeid
queryParams = this.stringifyParams(queryParams || EMPTY_OBJ)
// does it just remove at top level...?
queryParams = decodeURIComponent(queryParams)
/**
* @type {AxiosData}
*/
const axiosData = {
params,
}
// @@perf use .has & cleanup @james @@fork
if (this._get('headers')) {
// console.log('[1request] had headers')
axiosData.headers = this._get('headers')
}
/**
* @todo split here ===
*
* @type {Array<Data>}
*/
const args = [url, axiosData]
// const ENABLE_FOR_POST = true
if (this.isPost) {
// @todo - should work for every request get and post
url = `${url}?${queryParams}`
const formData = this.stringifyParams(dynamicParams)
const axiosPostData = {
data: formData,
}
const axiosHeaders = this._get('headers')
? {
...defaultPostHeaders,
...this._get('headers'),
}
: {
...defaultPostHeaders,
}
// console.log('[1request] axiosHeaders: ', axiosHeaders)
/**
* @type {AxiosRequestConfig}
*/
const axiosRequestConfig = {
withCredentials: true,
// @@perf use .has & cleanup @james @@fork
headers: axiosHeaders,
}
const axioserRequestConfig = {
url,
method: 'post',
...axiosPostData,
...axiosRequestConfig,
}
if (process.env.LOG_REQUEST_PARAMS) {
console.log('[oneRequest] params: ', axioserRequestConfig)
}
const [error, response] = await FETCHREQUEST.call(
this,
axioserRequestConfig
)
// !!!!! @@fork @@todo @james THIS IS TO CLEAR IT FOR EACH REQUEST
this._set('headers', undefined)
// console.dev({ error, response })
if (process.env.LOG_REQUEST_RESPONSE) {
console.log('[oneRequest] response: ', response)
}
if (error && onError) {
if (process.env.LOG_REQUEST_ERROR) {
console.log('[oneRequest] error: ', error)
}
// , response, this
return onError(error) || response.data
} else if (!error && onSuccess) {
// or .data...
onSuccess(response.data, response)
}
return response.data
}
// =========
if (process.env.LOG_REQUEST_PARAMS) {
console.log('[oneRequest] params: ', args)
}
/**
* @description do our post call
* @see chain/lego/oneRequest
*/
const [error, response] = await method.apply(this, args)
if (process.env.LOG_REQUEST_RESPONSE) {
console.log('[oneRequest] response: ', response)
}
if (process.env.LOG_REQUEST_ERROR) {
console.log('[oneRequest] error: ', error)
}
// if we have an error, for now, log it
if (error) {
// this.log({ error, response, ...this.store })
if (error && onError) {
// , response, this
return onError(error) || response.data
} else if (!error && onSuccess) {
// or .data...
onSuccess(response.data, response)
return response.data
}
// return onCall(response.data, response) || response.data
return response.data
}
// if (debug) {
// this.log({ error, response, params, ...this.store })
// }
// autofix(response.data)
return response.data
}
this.dynamicAxiosRequest = dynamicAxiosRequest
dynamicAxiosRequest.setHeaders = (headers = defaultPostHeaders) => {
this._set('headers', headers)
return dynamicAxiosRequest
}
dynamicAxiosRequest.setCookie = (cookieToSet: string) => {
this._set('headers', {
'Cookie': Array.isArray(cookieToSet) ? cookieToSet.join('') : cookieToSet,
'cookie': Array.isArray(cookieToSet) ? cookieToSet.join('') : cookieToSet,
'_cookie': Array.isArray(cookieToSet) ? cookieToSet.join('') : cookieToSet,
})
return dynamicAxiosRequest
}
dynamicAxiosRequest.forwardRequest = (request: ApolloRequestOrRequest) => {
const finalRequest: Request = request.httpRequest ? request.httpRequest : request
// console.log('[1request] forwarding')
const cookieHeader = finalRequest.headers.get('Cookie')
console.log('[1request] trying cookie:', cookieHeader)
if (cookieHeader) {
// console.log('[1request] has cookie')
dynamicAxiosRequest.setCookie(cookieHeader)
const headers = this._get('headers')
headers['user-agent'] = finalRequest.headers.get('user-agent')
// we don't want this
// finalRequest.headers.forEach((value, key) => {
// console.log('setting header: ', {[key]: value})
// headers[key] = value
// })
}
console.log('[1request] headers:', this._get('headers'))
console.log('[1request] set headers, back to base')
return dynamicAxiosRequest
}
dynamicAxiosRequest.toChain = always(this)
dynamicAxiosRequest.onError = this.onError
dynamicAxiosRequest.onSuccess = this.onSuccess
// just for a better name than .forwardRequest(context)()
dynamicAxiosRequest.doRequest = (dynamicParams, dynamicUrl) => dynamicAxiosRequest(dynamicParams, dynamicUrl)
return dynamicAxiosRequest
}
// query<T>(options: WatchQueryOptions): Promise<ApolloQueryResult<T>>
// mutate<T>(options: MutationOptions<T>): Promise<FetchResult<T>>
// subscribe(options: SubscriptionOptions): Observable<any>
// readQuery<T>(options: DataProxy.Query): T | null
// readFragment<T>(options: DataProxy.Fragment): T | null
// writeQuery(options: DataProxy.WriteQueryOptions): void
// writeFragment(options: DataProxy.WriteFragmentOptions): void
// gql = {
// // CRUD
// query: 'QueryHere',
// // dynamic
// // variables: {},
// }
/* @TODO split this out once it works as it broke all the tests :( */
toMock() {
let { url, params, data, method } = this.store
// eslint-disable-next-line
const nock = require("nock");
const mockUrl = process.env.BASE_URL || 'http://localhost:4000'
// console.log('mock data', this.store)
// @NOTE naming it apiUrl was better, oops
// show helpful messages to make sure people use it
this.validateRequestStore()
// for now, autofix it to include the baseUrl
if (!url.includes('http')) {
url = mockUrl + url
}
const handleGet = urlRequested => {
// `api/${url}`
const doesRequestIncludeUrl =
urlRequested.includes(url) || url.includes(urlRequested)
// this.log('getCall', { url, urlRequested, doesRequestIncludeUrl })
return doesRequestIncludeUrl
}
if (this.isPost) {
// console.dev(`${this.store.method} MOCKED`)
return nock(mockUrl)
.post(url)
.query(params)
.reply(201, data)
} else {
// console.dev(`${url} GET Crocked`)
return nock(mockUrl)
.get(handleGet)
.query(params)
.reply(200, data)
}
}
setVariables(variables) {
return this._set('variables', variables)
}
setQuery(Query) {
return this._set('query', Query)
}
gql() {
// all the things
}
/**
* (subscriber)
* same as () / .call() / .end() / .fetch()
*/
send() {
// @todo
// const response: ApolloQueryResult<Query> = await client.query(args)
// subscriber
return this.toRequest().apply(this, arguments)
}
onBrowser = handler => {
// this.store.onBrowser = handler
const onBrowser = (oneRequest, arg) => {
if (IS_BROWSER === false) {
handler(oneRequest, arg)
}
}
this.use(onBrowser)
return this
}
onServer = handler => {
// this.store.onServer = handler
const onServer = (oneRequest, arg) => {
if (IS_BROWSER === false) {
handler(oneRequest, arg)
}
}
this.use(onServer)
return this
}
onSuccess = handler => {
return this._set('onSuccess', handler)
}
onError = handler => {
return this._set('onError', handler)
}
}
export { OneRequest }
export { OneRequest as Request }
// export default Request