src/collection/RestEndpointProperty.js
import URI from "urijs";
import { getOptions, extendOptions } from "./fetchOptions";
import RestQueryBuilder from "./RestQueryBuilder";
import ObjectProperty from "../ObjectProperty";
import PrimitiveProperty from "../PrimitiveProperty";
import shadow from "../decorators/shadow";
import StateType from "../StateType";
import Store from "../Store";
import appDebug, { CollectionRestEndpointPropertyKey as DebugKey } from "../debug";
const debug = appDebug(DebugKey);
/*
*/
export default class RestEndpointProperty extends ObjectProperty {
constructor(stateType=RestEndpointProperty.type) {
super(stateType);
this._keyed.addPropertyType("url", PrimitiveProperty.type);
}
static createFor(url) {
const type = RestEndpointProperty.type
.initialState({ url: url.endsWith("/") ?url :`${url}/` })
return new RestEndpointProperty(type);
}
static getGlobalOptions() {
return getOptions();
}
static extendGlobalOptions(options) {
extendOptions(options);
}
//------------------------------------------------------------------------------------------------------
// The Endpoint implementation API
//------------------------------------------------------------------------------------------------------
@shadow
get id() {
return this._().url;
}
@shadow
isConnected() {
return !!this.id;
}
@shadow
queryBuilder() {
return new RestQueryBuilder();
}
@shadow
doCreate(shadowModel, model) {
const url = this.id;
const options = getOptions("POST", {
body: JSON.stringify(model),
headers: {
'Content-Type': 'application/json'
},
});
return fetch(url, options)
.then( response => {
if (!response.ok) {
return rejectOnError(response, url.toString());
}
return response.json();
})
}
@shadow
doDelete(id, options={}) {
const uri = URI(`${id}`).absoluteTo(this.id);
return fetch(uri.toString(), getOptions("DELETE", options))
.then( response => {
if (!response.ok) {
return rejectOnError(response, uri.toString());
}
return id;
});
}
@shadow
doFetch(filter) {
const startTime = Date.now();
const url = filter ?filter.applyFilter(this.id) :this.id;
debug( d => d(`doFetch() - path=${ this.$().dotPath() }, url=${url}`) );
return fetch(url, getOptions("GET"))
.then( response => {
debug( d => d(`doFetch() response - path=${ this.$().dotPath() }, url=${url}, ok=${response.ok}, ` +
`elapsed=${Date.now() - startTime}`, response) );
if (!response.ok) {
return rejectOnError(response, url.toString());
}
return response.json();
})
.then( json => {
debug( d => d(`doFetch() success - path=${ this.$().dotPath() }, url=${url}, ` +
`elapsed=${ Date.now() - startTime }`, json) );
return json;
})
.catch( error => {
debug( d => d(`doFetch() Fetch Error - path=${ this.$().dotPath() }, url=${url}`, error) );
return Promise.reject(error);
})
}
@shadow
doFind(id) {
const uri = URI(`${id}`).absoluteTo(this.id);
return fetch(uri.toString(), getOptions("GET"))
.then( response => {
if (!response.ok) {
return rejectOnError(response, uri.toString());
}
return response.json();
});
}
@shadow
doUpdate(id, shadowModel, changedProps) {
const uri = URI(`${id}`).absoluteTo(this.id);
const options = {
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(changedProps),
};
return fetch(uri.toString(), getOptions("PUT", options))
.then( response => {
if (!response.ok) {
return rejectOnError(response, uri.toString());
}
return response.json();
});
}
}
// TODO: extract text and generate a standard error description value (like login) that will make
// sense across end point types
function rejectOnError(response, url) {
const error = new Error(`${response.statusText} - status=${response.status}`);
error.url = url;
error.status = response.status;
error.response = response;
debug( d => response.text( text => d(`Response Error - url=${url}, text=${ text }`, response) ) );
return Store.reject(error);
}
StateType.defineType(RestEndpointProperty, spec => {
spec.autoshadowOff
.initialState({})
.readonly;
});