diff --git a/src/util/make_model.js b/src/util/make_model.js new file mode 100644 index 0000000..111e9e4 --- /dev/null +++ b/src/util/make_model.js @@ -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 + }) + } + } + }