type-r2
Version:
Serializable, validated, and observable data layer for modern JS applications
171 lines (140 loc) • 5.78 kB
text/typescript
import { IOEndpoint, IOOptions, log, isProduction } from 'type-r'
import { memoryIO, MemoryEndpoint } from '../../memory'
export function create( url : string, fetchOptions? : Partial<RestfulFetchOptions> ){
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<any> {
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}`);
}