|
@@ -37,20 +37,25 @@ export default (options={}) => { |
|
|
|
|
|
|
|
|
// private model cache & meta-info |
|
|
// private model cache & meta-info |
|
|
let _cache = { |
|
|
let _cache = { |
|
|
// upstream (data) cache |
|
|
|
|
|
|
|
|
// upstream data cache |
|
|
data: null, |
|
|
data: null, |
|
|
count: null, |
|
|
count: null, |
|
|
upstream_limit: null, |
|
|
|
|
|
|
|
|
upstream_limit: null |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
// local cache |
|
|
|
|
|
|
|
|
// model state |
|
|
|
|
|
let _state = { |
|
|
|
|
|
// model ambient that determines the data content |
|
|
ambient: null, |
|
|
ambient: null, |
|
|
last_ambient: null, |
|
|
last_ambient: null, |
|
|
|
|
|
// async states |
|
|
|
|
|
xhr: null, |
|
|
|
|
|
promise: null, |
|
|
|
|
|
loading: false, |
|
|
|
|
|
loading_offset: null, |
|
|
|
|
|
loading_limit: null |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// some random variables to make things work |
|
|
|
|
|
let _xhr = null |
|
|
|
|
|
let _promise = null |
|
|
|
|
|
|
|
|
|
|
|
// construct the model |
|
|
// construct the model |
|
|
let _model = { |
|
|
let _model = { |
|
|
// reflection methods |
|
|
// reflection methods |
|
@@ -58,24 +63,34 @@ export default (options={}) => { |
|
|
cache() { return _cache }, |
|
|
cache() { return _cache }, |
|
|
data(offset=0, limit=Infinity) { return _cache.data && _cache.data.slice(offset, offset+limit) || [] }, |
|
|
data(offset=0, limit=Infinity) { return _cache.data && _cache.data.slice(offset, offset+limit) || [] }, |
|
|
ambient_changed() { |
|
|
ambient_changed() { |
|
|
return _cache.ambient != _cache.last_ambient |
|
|
|
|
|
|
|
|
return _state.ambient != _state.last_ambient |
|
|
|
|
|
}, |
|
|
|
|
|
loading() { |
|
|
|
|
|
return _state.loading |
|
|
}, |
|
|
}, |
|
|
reset() { |
|
|
|
|
|
let ambient_queries = [ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// clean state and cache if necessary |
|
|
|
|
|
clean() { |
|
|
|
|
|
// always clean async state |
|
|
|
|
|
_state.xhr = null |
|
|
|
|
|
_state.promise = null |
|
|
|
|
|
_state.loading = false |
|
|
|
|
|
_state.loading_offset = null |
|
|
|
|
|
_state.loading_limit = null |
|
|
|
|
|
|
|
|
|
|
|
// update ambient state |
|
|
|
|
|
_state.last_ambient = _state.ambient |
|
|
|
|
|
_state.ambient =JSON.stringify([ |
|
|
...(_configs.selects ? _configs.selects : []), |
|
|
...(_configs.selects ? _configs.selects : []), |
|
|
...(_configs.wheres ? _configs.wheres.map(where => ({label: where.label, op: where.op, value: typeof where.value == 'function' ? where.value() : where.value})) : []), |
|
|
...(_configs.wheres ? _configs.wheres.map(where => ({label: where.label, op: where.op, value: typeof where.value == 'function' ? where.value() : where.value})) : []), |
|
|
...(_configs.order ? _configs.order : []) |
|
|
...(_configs.order ? _configs.order : []) |
|
|
] |
|
|
|
|
|
|
|
|
|
|
|
_cache.last_ambient = _cache.ambient |
|
|
|
|
|
_cache.ambient =JSON.stringify(ambient_queries) |
|
|
|
|
|
|
|
|
]) |
|
|
|
|
|
|
|
|
|
|
|
// clean cache if ambient changes |
|
|
if (this.ambient_changed()) { |
|
|
if (this.ambient_changed()) { |
|
|
_cache.data = [] |
|
|
_cache.data = [] |
|
|
_cache.count = null |
|
|
_cache.count = null |
|
|
_cache.upstream_limit = null |
|
|
_cache.upstream_limit = null |
|
|
_xhr = null |
|
|
|
|
|
//this.fully_loaded = false |
|
|
|
|
|
} |
|
|
} |
|
|
}, |
|
|
}, |
|
|
|
|
|
|
|
@@ -89,24 +104,30 @@ export default (options={}) => { |
|
|
if (this.data(offset, limit).length > 0 && !this.data(offset, limit).includes(undefined)) |
|
|
if (this.data(offset, limit).length > 0 && !this.data(offset, limit).includes(undefined)) |
|
|
return Promise.resolve(this.data(offset, limit)) |
|
|
return Promise.resolve(this.data(offset, limit)) |
|
|
|
|
|
|
|
|
// be lazy 2: if there is a promise, return it if ambient matches or |
|
|
|
|
|
// cancel it |
|
|
|
|
|
if (_promise != null) |
|
|
|
|
|
return _promise |
|
|
|
|
|
|
|
|
// be lazy 2: if the model is loading the same slice of data with the |
|
|
|
|
|
// same ambient, return the current promise; otherwise cancel it |
|
|
|
|
|
if (this.loading()) { |
|
|
|
|
|
if (!this.ambient_changed() && _state.loading_offset == offset && _state.loading_limit == limit) |
|
|
|
|
|
return _state.promise |
|
|
|
|
|
else { |
|
|
|
|
|
if (_state.xhr != null) _state.xhr.abort() |
|
|
|
|
|
_state.xhr = null |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
// TODO |
|
|
|
|
|
// ambient = select + order + limit/offset |
|
|
|
|
|
// if ambient is changed, cancel currently loading xhr; otherwise return |
|
|
|
|
|
// current promise |
|
|
|
|
|
|
|
|
// update state |
|
|
|
|
|
_state.loading = true |
|
|
|
|
|
_state.loading_offset = offset |
|
|
|
|
|
_state.loading_limit = limit |
|
|
|
|
|
|
|
|
// now the hard work |
|
|
|
|
|
_promise = _configs.api.request({ |
|
|
|
|
|
|
|
|
// construct the promise |
|
|
|
|
|
_state.promise = _configs.api.request({ |
|
|
method: 'GET', |
|
|
method: 'GET', |
|
|
url: _configs.endpoint, |
|
|
url: _configs.endpoint, |
|
|
headers: _cache.count == null ? { |
|
|
headers: _cache.count == null ? { |
|
|
Prefer: 'count=exact' |
|
|
Prefer: 'count=exact' |
|
|
} : {}, |
|
|
} : {}, |
|
|
config: xhr => _xhr = xhr, |
|
|
|
|
|
|
|
|
config: xhr => _state.xhr = xhr, |
|
|
queries: [ |
|
|
queries: [ |
|
|
// transform model state to postgest queries |
|
|
// transform model state to postgest queries |
|
|
// selects |
|
|
// selects |
|
@@ -135,7 +156,7 @@ export default (options={}) => { |
|
|
] |
|
|
] |
|
|
}).then(response => { |
|
|
}).then(response => { |
|
|
// gather begin/end/count |
|
|
// gather begin/end/count |
|
|
let [_range, _count] = _xhr.getResponseHeader('content-range').split('/') |
|
|
|
|
|
|
|
|
let [_range, _count] = _state.xhr.getResponseHeader('content-range').split('/') |
|
|
let [_begin, _end] = _range.split('-').map(v => ~~v) |
|
|
let [_begin, _end] = _range.split('-').map(v => ~~v) |
|
|
|
|
|
|
|
|
// update count if presented |
|
|
// update count if presented |
|
@@ -161,15 +182,13 @@ export default (options={}) => { |
|
|
if (_end - _begin + 1 < limit) |
|
|
if (_end - _begin + 1 < limit) |
|
|
console.warn('The response range is narrower than requested, probably due to an upstream hard limit.') |
|
|
console.warn('The response range is narrower than requested, probably due to an upstream hard limit.') |
|
|
|
|
|
|
|
|
// clean model state |
|
|
|
|
|
_promise = null |
|
|
|
|
|
this.reset() |
|
|
|
|
|
|
|
|
|
|
|
// return data |
|
|
// return data |
|
|
return _cache.data.slice(_begin, _end + 1) |
|
|
return _cache.data.slice(_begin, _end + 1) |
|
|
|
|
|
}).finally(() => { |
|
|
|
|
|
this.clean() |
|
|
}) |
|
|
}) |
|
|
|
|
|
|
|
|
return _promise |
|
|
|
|
|
|
|
|
return _state.promise |
|
|
}, |
|
|
}, |
|
|
|
|
|
|
|
|
select_all() { |
|
|
select_all() { |
|
@@ -177,14 +196,27 @@ export default (options={}) => { |
|
|
if (this.data().length == _cache.count && !this.data().includes(undefined)) |
|
|
if (this.data().length == _cache.count && !this.data().includes(undefined)) |
|
|
return Promise.resolve(this.data()) |
|
|
return Promise.resolve(this.data()) |
|
|
|
|
|
|
|
|
// be lazy 2: if there is a promise, return it if ambient matches or |
|
|
|
|
|
// cancel it |
|
|
|
|
|
if (_promise != null) |
|
|
|
|
|
return _promise |
|
|
|
|
|
|
|
|
// be lazy 2: if the model is loading the same slice of data with the |
|
|
|
|
|
// same ambient, return the current promise; otherwise cancel it |
|
|
|
|
|
if (this.loading()) { |
|
|
|
|
|
if (!this.ambient_changed() && _state.loading_offset == offset && _state.loading_limit == limit) |
|
|
|
|
|
return _state.promise |
|
|
|
|
|
else { |
|
|
|
|
|
if (_state.xhr != null) _state.xhr.abort() |
|
|
|
|
|
_state.xhr = null |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// update state |
|
|
|
|
|
_state.loading = true |
|
|
|
|
|
_state.loading_offset = 0 |
|
|
|
|
|
_state.loading_limit = Infinity |
|
|
|
|
|
|
|
|
_promise = _configs.api.request({ |
|
|
|
|
|
|
|
|
// construct the promise |
|
|
|
|
|
_state.promise = _configs.api.request({ |
|
|
method: 'POST', |
|
|
method: 'POST', |
|
|
url: '/rpc/select_all', |
|
|
url: '/rpc/select_all', |
|
|
|
|
|
config: xhr => _state.xhr = xhr, |
|
|
body: { |
|
|
body: { |
|
|
endpoint: _configs.endpoint, |
|
|
endpoint: _configs.endpoint, |
|
|
selects: _configs.selects || [], |
|
|
selects: _configs.selects || [], |
|
@@ -201,13 +233,11 @@ export default (options={}) => { |
|
|
|
|
|
|
|
|
return data |
|
|
return data |
|
|
}) |
|
|
}) |
|
|
|
|
|
|
|
|
// clean state |
|
|
|
|
|
_promise = null |
|
|
|
|
|
this.reset() |
|
|
|
|
|
|
|
|
}).finally(() => { |
|
|
|
|
|
this.clean() |
|
|
}) |
|
|
}) |
|
|
|
|
|
|
|
|
return _promise |
|
|
|
|
|
|
|
|
return _state.promise |
|
|
}, |
|
|
}, |
|
|
|
|
|
|
|
|
export(options={}) { |
|
|
export(options={}) { |
|
@@ -268,6 +298,6 @@ export default (options={}) => { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// initialize model |
|
|
// initialize model |
|
|
_model.reset() |
|
|
|
|
|
|
|
|
_model.clean() |
|
|
return _model |
|
|
return _model |
|
|
} |
|
|
} |