import { IOEndpoint, IOOptions, log, isProduction } from 'type-r' import { memoryIO, MemoryEndpoint } from '../../memory' export function create( url : string, fetchOptions? : Partial ){ return new RestfulEndpoint( url, fetchOptions ); } export { create as restfulIO } export type HttpMethod = 'GET' | 'POST' | 'UPDATE' | 'DELETE' | 'PUT' export interface RestfulIOOptions extends IOOptions { params? : object, options? : RequestInit } export type RestfulFetchOptions = /* subset of RequestInit */{ cache?: RequestCache; credentials?: RequestCredentials; mode?: RequestMode; redirect?: RequestRedirect; referrerPolicy?: ReferrerPolicy; mockData? : any simulateDelay? : number } export class RestfulEndpoint implements IOEndpoint { constructor( public url : string, { mockData, simulateDelay = 1000, ...fetchOptions } : RestfulFetchOptions = {}) { this.fetchOptions = fetchOptions this.memoryIO = mockData ? memoryIO( mockData, simulateDelay ) : null; } fetchOptions : RestfulFetchOptions memoryIO : MemoryEndpoint public static defaultFetchOptions : RestfulFetchOptions = { cache: "no-cache", credentials: "same-origin", mode: "cors", redirect: "error", } create( json, options : RestfulIOOptions, record ) { const url = this.collectionUrl( record, options ); return this.memoryIO ? this.simulateIO( 'create', 'POST', url, arguments ) : this.request( 'POST', url, options, json ); } update( id, json, options : RestfulIOOptions, record ) { const url = this.objectUrl( record, id, options ) return this.memoryIO ? this.simulateIO( 'update', 'PUT', url, arguments ) : this.request( 'PUT', url, options, json ); } read( id, options : IOOptions, record ){ const url = this.objectUrl( record, id, options ); return this.memoryIO ? this.simulateIO( 'read', 'GET', url, arguments ) : this.request( 'GET', url, options ); } destroy( id, options : RestfulIOOptions, record ){ const url = this.objectUrl( record, id, options ); return this.memoryIO ? this.simulateIO( 'destroy', 'DELETE', url, arguments ) : this.request( 'DELETE', url, options ); } list( options : RestfulIOOptions, collection ) { const url = this.collectionUrl( collection, options ); return this.memoryIO ? this.simulateIO( 'list', 'GET', url, arguments ) : this.request( 'GET', url , options ); } subscribe( events ) : any {} unsubscribe( events ) : any {} async simulateIO( method : string, httpMethod : string, url : string, args ){ log( isProduction ? "error" : "info", 'Type-R:SimulatedIO', `${httpMethod} ${url}`); return this.memoryIO[ method ].apply( this.memoryIO, args ); } protected isRelativeUrl( url ) { return url.indexOf( './' ) === 0; } protected removeTrailingSlash( url : string ) { const endsWithSlash = url.charAt( url.length - 1 ) === '/'; return endsWithSlash ? url.substr( 0, url.length - 1 ) : url; } protected getRootUrl( recordOrCollection ) { const { url } = this if( this.isRelativeUrl( url ) ) { const owner = recordOrCollection.getOwner(), ownerUrl = owner.getEndpoint().getUrl( owner ); return this.removeTrailingSlash( ownerUrl ) + '/' + url.substr( 2 ) } else { return url; } } protected getUrl( record ) { const url = this.getRootUrl( record ); return record.isNew() ? url : this.removeTrailingSlash( url ) + '/' + record.id } protected objectUrl( record, id, options ){ return appendParams( this.getUrl( record ), options.params ); } protected collectionUrl( collection, options ){ return appendParams( this.getRootUrl( collection ), options.params ); } protected buildRequestOptions( method : string, options? : RequestInit, body? ) : RequestInit { const mergedOptions : RequestInit = { ...RestfulEndpoint.defaultFetchOptions, ...this.fetchOptions, ...options }; const {headers, ...rest} = mergedOptions, resultOptions : RequestInit = { method, headers: { 'Content-Type': 'application/json', ...headers }, ...rest }; if( body ) { resultOptions.body = JSON.stringify( body ); } return resultOptions; } protected request( method : HttpMethod, url : string, {options} : RestfulIOOptions, body? ) : Promise { return fetch( url, this.buildRequestOptions( method, options, body ) ) .then( response => { if( response.ok ) { return response.json() } else { throw new Error( response.statusText ) } } ); } } function appendParams( url, params? ) { var esc = encodeURIComponent; return params ? url + '?' + Object.keys( params ) .map( k => esc( k ) + '=' + esc( params[ k ] ) ) .join( '&' ) : url; } function simulateIO(){ log( "info", 'SimulatedIO', `GET ${this.url}`); }