import { IModelConstructor } from 'mobx-collection-store' ; import ParamArrayType from './enums/ParamArrayType' ; import IDictionary from './interfaces/IDictionary' ; import IFilters from './interfaces/IFilters' ; import IHeaders from './interfaces/IHeaders' ; import IRawResponse from './interfaces/IRawResponse' ; import IRequestOptions from './interfaces/IRequestOptions' ; import IResponseHeaders from './interfaces/IResponseHeaders' ; import * as JsonApi from './interfaces/JsonApi' ; import { Record } from './Record' ; import { Response as LibResponse } from './Response' ; import { Store } from './Store' ; import { assign , getValue , isBrowser , objectForEach } from './utils' ; export type FetchType = ( method : string , url : string , body ? : object , requestHeaders ? : IHeaders ) => Promise < IRawResponse > ; export interface IStoreFetchOpts { url : string ; options ? : IRequestOptions ; data ? : object ; method : string ; store : Store ; } export type StoreFetchType = ( options : IStoreFetchOpts ) => Promise < LibResponse > ; export interface IConfigType { baseFetch : FetchType ; baseUrl : string ; defaultHeaders : IHeaders ; defaultFetchOptions : IDictionary < any > ; fetchReference : Function ; paramArrayType : ParamArrayType ; storeFetch : StoreFetchType ; transformRequest : ( options : IStoreFetchOpts ) => IStoreFetchOpts ; transformResponse : ( response : IRawResponse ) => IRawResponse ; } export const config = { /** Base URL for all API calls */ baseUrl : '/' , /** Default headers that will be sent to the server */ defaultHeaders : { 'content-type' : 'application/vnd.api+json' , } , defaultFetchOptions : { } , /** Reference of the fetch method that should be used */ fetchReference : isBrowser && window . fetch && window . fetch . bind ( window ) , /** Determines how will the request param arrays be stringified */ paramArrayType : ParamArrayType . COMMA_SEPARATED , param { string } method param { string } url param { object } body param { IHeaders } requestHeaders returns { Promise < IRawResponse > } baseFetch ( method , url , body ? , requestHeaders ? ) { let data ; let status ; let headers ; const request = Promise . resolve ( ) ; const uppercaseMethod = method . toUpperCase ( ) ; const isBodySupported = uppercaseMethod !== 'GET' && uppercaseMethod !== 'HEAD' ; return request . then ( ( ) => { const reqHeaders = assign ( { } , config . defaultHeaders , requestHeaders ) as IHeaders ; const options = assign ( { } , config . defaultFetchOptions , { body : isBodySupported && JSON . stringify ( body ) || undefined , headers : reqHeaders , method , } ) ; return this . fetchReference ( url , options ) ; } ) . then ( ( response ) => { status = response . status ; headers = response . headers ; return response . json ( ) ; } ) . catch ( ( e ) => { if ( status === 204 ) { return null ; } throw e ; } ) . then ( ( responseData ) => { data = responseData ; if ( status >= 400 ) { throw { message : `Invalid HTTP status: ${ status }` , status , } ; } return { data , headers , requestHeaders , status } ; } ) . catch ( ( error ) => { return { data , error , headers , requestHeaders , status } ; } ) ; } , param { IStoreFetchOpts } reqOptions returns { Promise < Response > } storeFetch ( reqOptions ) { const { url , options , data , method = 'GET' , store , } = config . transformRequest ( reqOptions ) ; return config . baseFetch ( method , url , data , options && options . headers ) . then ( ( response ) => { const storeResponse = assign ( response , { store } ) ; return new LibResponse ( config . transformResponse ( storeResponse ) , store , options ) ; } ) ; } , transformRequest ( options ) { return options ; } , transformResponse ( response ) { return response ; } , } ; export function fetch ( options ) { return config . storeFetch ( options ) ; } export param { Store } store param { string } url param { IHeaders } headers param { IRequestOptions } options returns { Promise < Response > } export function read ( store , url , headers ? , options ? ) { return config . storeFetch ( { data : null , method : 'GET' , options : { ... options , headers } , store , url , } ) ; } export param { Store } store param { string } url param { object } data param { IHeaders } headers param { IRequestOptions } options returns { Promise < Response > } export function create ( store , url , data ? , headers ? , options ? ) { return config . storeFetch ( { data , method : 'POST' , options : { ... options , headers } , store , url , } ) ; } export param { Store } store param { string } url param { object } data param { IHeaders } headers param { IRequestOptions } options returns { Promise < Response > } export function update ( store , url , data ? , headers ? , options ? ) { return config . storeFetch ( { data , method : 'PATCH' , options : { ... options , headers } , store , url , } ) ; } export param { Store } store param { string } url param { IHeaders } headers param { IRequestOptions } options returns { Promise < Response > } export function remove ( store , url , headers ? , options ? ) { return config . storeFetch ( { data : null , method : 'DELETE' , options : { ... options , headers } , store , url , } ) ; } export param { JsonApi . ILink } link param { Store } store param { IDictionary < string > } requestHeaders param { IRequestOptions } options returns { Promise < LibResponse > } export function fetchLink ( link , store , requestHeaders ? , options ? ) { if ( link ) { const href = typeof link === 'object' ? link . href : link ; if ( href ) { return read ( store , href , requestHeaders , options ) ; } } return Promise . resolve ( new LibResponse ( { data : null } , store ) ) ; } export function handleResponse ( record , prop ? ) { return ( response ) : Record => { if ( response . error ) { throw response . error ; } if ( response . status === 204 ) { record [ '__persisted' ] = true ; return record as Record ; } else if ( response . status === 202 ) { ( response . data as Record ) . update ( { __prop__ : prop , __queue__ : true , __related__ : record , } as Object ) ; return response . data as Record ; } else { record [ '__persisted' ] = true ; return response . replaceData ( record ) . data as Record ; } } ; } function __prepareFilters ( filters ) { return __parametrize ( filters ) . map ( ( item ) => `filter[${ item . key }]=${ item . value }` ) ; } function __prepareSort ( sort ? ) { return sort ? [ `sort=${ sort }` ] : [ ] ; } function __prepareIncludes ( include ? ) { return include ? [ `include=${ include }` ] : [ ] ; } function __prepareFields ( fields ) { const list = [ ] ; objectForEach ( fields , ( key ) => { list . push ( `fields[${ key }]=${ fields [ key ] }` ) ; } ) ; return list ; } function __prepareRawParams ( params ) { return params . map ( ( param ) => { if ( typeof param === 'string' ) { return param ; } return `${ param . key }=${ param . value }` ; } ) ; } export function prefixUrl ( url ) { return `${ config . baseUrl }${ url }` ; } function __appendParams ( url , params ) { if ( params . length ) { url += '?' + params . join ( '&' ) ; } return url ; } function __parametrize ( params , scope = '' ) { const list = [ ] ; objectForEach ( params , ( key ) => { if ( params [ key ] instanceof Array ) { if ( config . paramArrayType === ParamArrayType . OBJECT_PATH ) { list . push ( ... __parametrize ( params [ key ] , `${ key }.` ) ) ; } else if ( config . paramArrayType === ParamArrayType . COMMA_SEPARATED ) { list . push ( { key : `${ scope }${ key }` , value : params [ key ] . join ( ',' ) } ) ; } else if ( config . paramArrayType === ParamArrayType . MULTIPLE_PARAMS ) { list . push ( ... params [ key ] . map ( ( param ) => ( { key : `${ scope }${ key }` , value : param } ) ) ) ; } else if ( config . paramArrayType === ParamArrayType . PARAM_ARRAY ) { list . push ( ... params [ key ] . map ( ( param ) => ( { key : `${ scope }${ key }][` , value : param } ) ) ) ; } } else if ( typeof params [ key ] === 'object' ) { list . push ( ... __parametrize ( params [ key ] , `${ key }.` ) ) ; } else { list . push ( { key : `${ scope }${ key }` , value : params [ key ] } ) ; } } ) ; return list ; } export function buildUrl ( type , id ? , model ? , options ? ) { const path = model ? ( getValue < string > ( model [ 'endpoint' ] ) || model [ 'baseUrl' ] || model . type ) : type ; const url = id ? `${ path }/${ id }` : `${ path }` ; const params = [ ... __prepareFilters ( ( options && options . filter ) || { } ) , ... __prepareSort ( options && options . sort ) , ... __prepareIncludes ( options && options . include ) , ... __prepareFields ( ( options && options . fields ) || { } ) , ... __prepareRawParams ( ( options && options . params ) || [ ] ) , ] ; return __appendParams ( prefixUrl ( url ) , params ) ; }