|
@@ -0,0 +1,183 @@ |
|
|
|
|
|
/* building a model by postgrest |
|
|
|
|
|
* |
|
|
|
|
|
* a model is simply a cache layer between client |
|
|
|
|
|
* and the database behind an api |
|
|
|
|
|
* |
|
|
|
|
|
* options: { |
|
|
|
|
|
* endpoint: 'resource location', |
|
|
|
|
|
* selects: [ |
|
|
|
|
|
* { |
|
|
|
|
|
* 'label': 'column name', |
|
|
|
|
|
* 'alias': 'displayed name', |
|
|
|
|
|
* 'filter': v => value_processor(v) |
|
|
|
|
|
* } |
|
|
|
|
|
* ] |
|
|
|
|
|
* } |
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
|
|
|
|
export default (options={}) => { |
|
|
|
|
|
// TODO |
|
|
|
|
|
// 1. sigular or prural |
|
|
|
|
|
|
|
|
|
|
|
// sanity checks |
|
|
|
|
|
if (typeof options.api == 'undefined' || typeof options.endpoint == 'undefined') |
|
|
|
|
|
throw 'api and endpoint are required options' |
|
|
|
|
|
|
|
|
|
|
|
// private model configs |
|
|
|
|
|
let _configs = { |
|
|
|
|
|
// url part |
|
|
|
|
|
api: options.api, |
|
|
|
|
|
endpoint: options.endpoint, |
|
|
|
|
|
endpoint_type: options.endpoint.startsWith('/rpc') ? 'function' : 'relation', |
|
|
|
|
|
// query part |
|
|
|
|
|
selects: options.selects || [], |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// private model cache & meta-info |
|
|
|
|
|
let _cache = { |
|
|
|
|
|
data: null, |
|
|
|
|
|
count: null |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// some random places to make things work |
|
|
|
|
|
let _xhr = null |
|
|
|
|
|
|
|
|
|
|
|
// construct the model |
|
|
|
|
|
return { |
|
|
|
|
|
// reflection methods |
|
|
|
|
|
configs() { return _configs }, |
|
|
|
|
|
cache() { return _cache }, |
|
|
|
|
|
|
|
|
|
|
|
// main methods |
|
|
|
|
|
select(from=0, to) { |
|
|
|
|
|
// initialize data (singular or plural) |
|
|
|
|
|
_cache.data = _cache.data || [] |
|
|
|
|
|
|
|
|
|
|
|
// now the hard work |
|
|
|
|
|
return _configs.api.request({ |
|
|
|
|
|
method: 'GET', |
|
|
|
|
|
url: _configs.endpoint, |
|
|
|
|
|
headers: _cache.count == null ? { |
|
|
|
|
|
Prefer: 'count=exact' |
|
|
|
|
|
} : {}, |
|
|
|
|
|
config: xhr => _xhr = xhr, |
|
|
|
|
|
// |
|
|
|
|
|
// transform model state to postgest queries |
|
|
|
|
|
//queries: [...ambient_queries, ...paging_queries] |
|
|
|
|
|
}).then(response => { |
|
|
|
|
|
// gather begin/end/count |
|
|
|
|
|
let [_range, _count] = _xhr.getResponseHeader('content-range').split('/') |
|
|
|
|
|
let [_begin, _end] = _range.split('-').map(v => ~~v) |
|
|
|
|
|
|
|
|
|
|
|
// update count if presented |
|
|
|
|
|
if (_count != '*') _cache.count = _count |
|
|
|
|
|
|
|
|
|
|
|
// fill the data cache |
|
|
|
|
|
response.forEach((data, i) => { |
|
|
|
|
|
// _value_filters.forEach(([label, config]) => data[config.alias || label] = config.value_filter(data[config.alias || label])) |
|
|
|
|
|
_cache.data[_begin + i] = data |
|
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
|
|
// for promise |
|
|
|
|
|
return _cache.data.slice(_begin, _end + 1) |
|
|
|
|
|
}) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
export const default1 = (args) => { |
|
|
|
|
|
// private model configs |
|
|
|
|
|
let _configs = { |
|
|
|
|
|
queries: args.queries || [], |
|
|
|
|
|
order: args.order || [] |
|
|
|
|
|
} |
|
|
|
|
|
_configs.selects.forEach((config, label, selects) => { |
|
|
|
|
|
selects.set(label, config || {}) |
|
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
|
|
// prepare the model value filters |
|
|
|
|
|
let _value_filters = Array.from(_configs.selects).filter(([label, config]) => config.value_filter) |
|
|
|
|
|
|
|
|
|
|
|
return { |
|
|
|
|
|
// model & model state |
|
|
|
|
|
list: [], |
|
|
|
|
|
count: null, |
|
|
|
|
|
fully_loaded: false, |
|
|
|
|
|
xhr: null, |
|
|
|
|
|
|
|
|
|
|
|
// ambient |
|
|
|
|
|
ambient: null, |
|
|
|
|
|
last_ambient: null, |
|
|
|
|
|
ambient_changed() { |
|
|
|
|
|
return this.ambient != this.last_ambient |
|
|
|
|
|
}, |
|
|
|
|
|
reset() { |
|
|
|
|
|
// assemble queries for api.request |
|
|
|
|
|
let ambient_queries = [ |
|
|
|
|
|
...(_configs.selects ? [{label: 'select', value: Array.from(_configs.selects, ([label, config]) => config.alias ? config.alias + ':' + label : label).join(',')}] : []), |
|
|
|
|
|
...(_configs.queries ? _configs.queries.map(query => ({label: query.label, op: query.op, value: typeof query.value == 'function' ? query.value() : query.value})) : []), |
|
|
|
|
|
...(_configs.order ? [{label: 'order', value: _configs.order.map(o => [o.label, o.direction, o.nulls ? 'nulls'+o.nulls : ''].filter(a => a).join('.')).join(',')}] : []) |
|
|
|
|
|
] |
|
|
|
|
|
|
|
|
|
|
|
this.last_ambient = this.ambient |
|
|
|
|
|
this.ambient =JSON.stringify(ambient_queries) |
|
|
|
|
|
|
|
|
|
|
|
if (this.ambient_changed()) { |
|
|
|
|
|
this.list = [] |
|
|
|
|
|
this.count = null |
|
|
|
|
|
this.fully_loaded = false |
|
|
|
|
|
this.xhr = null |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return ambient_queries |
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
// load data |
|
|
|
|
|
load(args) { |
|
|
|
|
|
args = args || {begin: 0} |
|
|
|
|
|
|
|
|
|
|
|
let ambient_queries = this.reset() |
|
|
|
|
|
|
|
|
|
|
|
// be lazy: if the data is presented, return the value immediately |
|
|
|
|
|
if (this.list.slice(args.begin, args.end).length == args.limit && this.list.slice(args.begin, args.end).every(data => data != undefined)) |
|
|
|
|
|
return Promise.resolve(this.list.slice(args.begin, args.end)) |
|
|
|
|
|
|
|
|
|
|
|
// create limit&offset for postgrest |
|
|
|
|
|
let paging_queries = [] |
|
|
|
|
|
if (args.begin != undefined) { |
|
|
|
|
|
paging_queries.push({label: 'offset', value: args.begin}) |
|
|
|
|
|
if (args.end != undefined) |
|
|
|
|
|
paging_queries.push({label: 'limit', value: args.end - args.begin}) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// now the hard work |
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
// load full model if privileged |
|
|
|
|
|
load_full() { |
|
|
|
|
|
this.reset() |
|
|
|
|
|
|
|
|
|
|
|
if (this.fully_loaded) |
|
|
|
|
|
return Promise.resolve(this.list) |
|
|
|
|
|
|
|
|
|
|
|
return api.request({ |
|
|
|
|
|
method: 'POST', |
|
|
|
|
|
url: '/rpc/query_all', |
|
|
|
|
|
body: { |
|
|
|
|
|
endpoint: _configs.endpoint, |
|
|
|
|
|
selects: Array.from(_configs.selects, ([label, config]) => ({label: label, alias: config.alias})), |
|
|
|
|
|
queries: _configs.queries ? _configs.queries.map(query => ({label: query.label, op: query.op, value: typeof query.value == 'function' ? query.value() : query.value})) : [], |
|
|
|
|
|
order: _configs.order |
|
|
|
|
|
} |
|
|
|
|
|
}).then(response => { |
|
|
|
|
|
this.list = response.map(data => { |
|
|
|
|
|
_value_filters.forEach(([label, config]) => data[config.alias || label] = config.value_filter(data[config.alias || label])) |
|
|
|
|
|
return data |
|
|
|
|
|
}) |
|
|
|
|
|
this.fully_loaded = true |
|
|
|
|
|
|
|
|
|
|
|
return this.list |
|
|
|
|
|
}) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |