From f95fd82f1fe14c1a84f9e33fb3fc53ac40c6f6af Mon Sep 17 00:00:00 2001 From: "Alex A. Naanou" Date: Sun, 23 Oct 2022 02:21:34 +0300 Subject: [PATCH] splitting out index.js... Signed-off-by: Alex A. Naanou --- pwiki/index.js | 429 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 429 insertions(+) create mode 100644 pwiki/index.js diff --git a/pwiki/index.js b/pwiki/index.js new file mode 100644 index 0000000..70c39bb --- /dev/null +++ b/pwiki/index.js @@ -0,0 +1,429 @@ +/********************************************************************** +* +* +* +**********************************************************************/ +((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define) +(function(require){ var module={} // make module AMD/node compatible... +/*********************************************************************/ + +var object = require('ig-object') + + +//--------------------------------------------------------------------- +// XXX move this to a separate module (???) +// XXX TODO: +// - define (name, generate, merge) - DONE +// inline - DONE +// online - DONE +// - undefine (online) - DONE +// ...simply delete the method/prop... +// - enumerate/list - DONE +// - group operations: +// - reset (cache) - DONE +// - custom - DONE +// XXX move .paths()/.names() to this... +// + +// +// makeIndex([, ]) +// makeIndex(, [, ]) +// -> +// +// Get merged data (cached) +// () +// ('get') +// -> +// -> +// NOTE: when a getter is pending (promise), all consecutive calls +// will resolve the original getter return value... +// +// Get sync or cached result and do "lazy" background update... +// ('lazy') +// -> +// -> +// NOTE: if (..) is synchronous, this will wait till +// it returns and will return the result. +// NOTE: 'lazy' mode is generally faster as it does all the checks and +// updating (if needed) in a background promise, but can return +// outdated cached results. +// NOTE: as a side-effect this avoids returning promises if a cached +// value is available. i.e. a promise is returned only when +// getting/generating a value for the first time. +// +// Get cached result and trigger a background update... +// ('cached') +// -> +// -> +// -> undefined +// NOTE: this is like 'lazy' but will not wait for )(..) +// to return, making it even faster but as a trade off it will +// return the cached and possibly outdated result even if +// (..) is synchronous. +// +// Get local data (uncached)... +// ('local') +// -> +// -> +// +// Clear cache... +// ('clear') +// +// Reset cache (clear then get)... +// ('reset') +// -> +// -> +// +// Get index status... +// ('status') +// -> 'empty' +// -> 'pending' +// -> 'cached' +// -> 'outdated' +// +// Run custom action... +// (), ...) +// -> +// -> +// +// NOTE: the main differences between the 'get', 'lazy' and 'cached' actions: +// 'get' +// generate/merge are all sync/async as defined +// when cached value available validate and return either the cached value or generate +// 'lazy' +// XXX +// 'cached' +// call get in background +// return cached value or undefined +// +// +// +// Special methods: +// +// Special method to generate local ... +// .____() +// -> +// +// Merge local data with other sources... +// .___merge__() +// -> +// +// Test if cache is valid... +// .___isvalid__() +// -> +// +// Handle custom action... +// ._____(. ...) +// -> +// +// +// +// Special attributes: +// +// Cached data... +// .___cache / . +// +// Modification time... +// .___modified +// +// Pending generator promise... +// .___promise +// +// +// Options format: +// { +// // XXX +// attr: false +// | true +// | , +// +// // list of dependencies that when changed will trigger a cache +// // drop on current index... +// // NOTE: dependency checking is done via .modified time, if value +// // is changed manually and not via an action then the system +// // will not catch the change. +// depends: [ +// , +// ... +// ], +// +// // custom action... +// // NOTE: this is the same as defining ._____(..) +// // method... +// : , +// } +// +// +// XXX do we separate internal methods and actions??? +// i.e. ___merge__(..) / ___isvalid__(..) and the rest... +var makeIndex = +module.makeIndex = +function(name, generate, options={}){ + // makeIndex(, ) + if(generate + && typeof(generate) != 'function'){ + options = generate + generate = options.generate } + + // attr names... + var cache = + typeof(options.attr) == 'string' ? + options.attr + // XXX revise default... + : !!options.attr ? + name + : `__${name}_cache` + var modified = `__${name}_modified` + var promise = `__${name}_promise` + var test = `__${name}_isvalid__` + var merge = `__${name}_merge__` + var special = `__${name}__` + + // set modified time... + var _stamp = function(that, res){ + res instanceof Promise ? + res.then(function(){ + that[modified] = Date.now() }) + : (that[modified] = Date.now()) + return res } + // make local cache... + var _make = function(that){ + return that[special] != null ? + that[special]() + : (generate + && generate.call(that)) } + var _smake = function(that){ + return _stamp(that, _make(that)) } + // unwrap a promised value into cache... + var _await = function(obj, val){ + if(val instanceof Promise){ + // NOTE: this avoids a race condition when a getter is called + // while a previous getter is still pending... + if(obj[promise] == null){ + obj[promise] = val + val.then( + function(value){ + delete obj[promise] + obj[cache] = value }, + function(err){ + // XXX should we report this??? + delete obj[promise] }) } + val = obj[promise] } + return val } + var _deferred = async function(obj, ...args){ + return meth.call(obj, ...args) } + + // build the method... + var meth + return (meth = Object.assign( + function(action='get', ...args){ + var that = this + + // action: status... + if(action == 'status'){ + if(this[cache] instanceof Promise){ + return 'pending' } + if(cache in this){ + var cur = this[modified] + // user test... + if(test in this + && !this[test](cur)){ + return 'outdated' + // check dependencies... + } else if(meth.options.depends){ + for(var dep of meth.options.depends){ + if(this[`__${this[dep].index}_modified`] > cur){ + return 'outdated' } } } + return 'cached' } + return 'empty' } + + // action: lazy... + if(action == 'lazy'){ + if(this[cache] instanceof Promise){ + return this[cache] } + var res = meth.call(this, 'get') + return (this[cache] + && res instanceof Promise) ? + this[cache] + : res } + // action: cached... + if(action == 'cached'){ + _deferred(this, 'get') + return this[cache] } + // action: local... + // NOTE: this is intentionally not cached... + if(action == 'local'){ + return _make(this) } + + // action: clear/reset... + if(action == 'clear' + || action == 'reset'){ + delete this[cache] } + if(action == 'clear'){ + return } + + // validate cache... + if(cache in this + && meth.call(this, 'status') == 'outdated'){ + delete this[cache] } + + // action: other... + if(action != 'get' + && action != 'reset'){ + var action_meth = `__${name}_${action}__` + // generate cache if not available... + var cur = cache in this ? + this[cache] + : meth.call(this, 'reset') + var res = _await(this, this[cache] = + // NOTE: this[action_meth] will fully shadow options[action]... + action_meth in this ? + this[action_meth](cur, ...args) + : (action in options + && typeof(options[action]) == 'function') ? + options[action].call(this, cur, ...args) + : cur) + res !== cur + && _stamp(this, res) + return res } + + // action: get... + return _await(this, + this[cache] = + // cached... + this[cache] != null ? + this[cache] + // generate + merge... + : this[merge] != null ? + // NOTE: need to set the timestamp after the merge... + _stamp(this, + this[merge](_make(this))) + // generate... + : _smake(this)) }, + { + index: name, + indexed: true, + options, + })) } + + +// XXX +var iter = +module.iter = +function*(){ + +} + +// XXX +var index = +module.index = +function(){ + +} + + + +var IndexManagerMixin = +module.IndexManagerMixin = +object.Mixin('IndexManagerMixin', { + // List of index handler attribute names... + // + // XXX rename??? + get index_attrs(){ + var that = this + return object.deepKeys(this) + .filter(function(key){ + var d = object.values(that, key, true).next().value.value + return typeof(d) == 'function' + && d.indexed }) }, + // + // .index() + // .index('get') + // -> + // + // ... + // + // .index(, ...) + // -> + // + // + // .index('new', , [, ]) + // -> + // + index: async function(action='get', ...args){ + // create a new index... + if(action == 'new'){ + var res = makeIndex(...args) + var [name, _, options={}] = args + var attr = name + if(options.attr){ + var attr = `__${name}` + Object.defineProperty(this, name, { + get: function(){ + return this[attr] }, }) } + return (this[attr] = res) } + // propagate action... + var that = this + return Object.fromEntries( + await Promise.all( + this.index_attrs + .map(async function(name){ + return [ + that[name].index, + await that[name](action, ...args), + ] }))) }, +}) + + +var indexTest = +module.indexTest = +IndexManagerMixin({ + // tests... + // + moo: makeIndex('moo', () => 123), + + foo_index: makeIndex('foo', () => 123, { + attr: true, + add: function(cur, val){ + return cur + val }, + }), + + __boo_add__: function(cur, val){ + return cur + val }, + boo: makeIndex('boo', () => 123), + + __soo_add__: async function(cur, val){ + return await cur + val }, + __soo: makeIndex('soo', async () => 123), + get soo(){ + return this.__soo() }, + + __sum: makeIndex('sum', + async function(){ + return await this.moo() + + await this.foo_index() + + await this.boo() + + await this.soo }, + { depends: [ + 'moo', + 'foo_index', + 'boo', + '__soo', + ], }), + get sum(){ + return this.__sum() }, + + __merged__: function(){ + return 777 }, + __merged_merge__: async function(data){ + return (await data) + 777 }, + __merged: makeIndex('merged'), + get merged(){ + return this.__merged() }, +}) + + + + +/********************************************************************** +* vim:set ts=4 sw=4 nowrap : */ return module })