diff --git a/pwiki/index.js b/pwiki/index.js old mode 100644 new mode 100755 index 70c39bb..a13dae6 --- a/pwiki/index.js +++ b/pwiki/index.js @@ -307,22 +307,66 @@ function(name, generate, options={}){ })) } + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // XXX var iter = module.iter = -function*(){ +function*(obj){ + for(var key of object.deepKeys(obj)){ + var d = object.values(obj, key, true).next().value.value + // XXX should makeIndex(..) be a constructor -- i.e. an instanceof test??? + if(typeof(d) == 'function' + && d.indexed){ + yield key } } } -} +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +// +// .index(obj) +// .index(obj, 'get') +// -> +// +// ... +// +// .index(obj, , ...) +// -> +// +// +// .index('obj, new', , [, ]) +// -> +// // XXX var index = module.index = -function(){ - -} +async function(obj, action='get', ...args){ + // create a new index... + if(action == 'new'){ + var res = module.makeIndex(...args) + var [name, _, options={}] = args + var attr = name + if(options.attr){ + var attr = `__${name}` + Object.defineProperty(obj, name, { + get: function(){ + return obj[attr] }, }) } + return (obj[attr] = res) } + // propagate action... + return Object.fromEntries( + await Promise.all( + module.iter(obj) + .map(async function(name){ + return [ + obj[name].index, + await obj[name](action, ...args), + ] }))) } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + var IndexManagerMixin = module.IndexManagerMixin = object.Mixin('IndexManagerMixin', { @@ -330,59 +374,23 @@ object.Mixin('IndexManagerMixin', { // // 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', , [, ]) - // -> - // + return [...module.iter(this)] }, 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), - ] }))) }, + return module.index(this, ...arguments) }, }) + +//--------------------------------------------------------------------- + var indexTest = module.indexTest = IndexManagerMixin({ // tests... // - moo: makeIndex('moo', () => 123), + moo: module.makeIndex('moo', () => 123), - foo_index: makeIndex('foo', () => 123, { + foo_index: module.makeIndex('foo', () => 123, { attr: true, add: function(cur, val){ return cur + val }, @@ -390,15 +398,15 @@ IndexManagerMixin({ __boo_add__: function(cur, val){ return cur + val }, - boo: makeIndex('boo', () => 123), + boo: module.makeIndex('boo', () => 123), __soo_add__: async function(cur, val){ return await cur + val }, - __soo: makeIndex('soo', async () => 123), + __soo: module.makeIndex('soo', async () => 123), get soo(){ return this.__soo() }, - __sum: makeIndex('sum', + __sum: module.makeIndex('sum', async function(){ return await this.moo() + await this.foo_index() @@ -417,7 +425,7 @@ IndexManagerMixin({ return 777 }, __merged_merge__: async function(data){ return (await data) + 777 }, - __merged: makeIndex('merged'), + __merged: module.makeIndex('merged'), get merged(){ return this.__merged() }, }) diff --git a/pwiki/store/base.js b/pwiki/store/base.js index 1e47703..30a9800 100755 --- a/pwiki/store/base.js +++ b/pwiki/store/base.js @@ -11,413 +11,10 @@ var object = require('ig-object') var types = require('ig-types') var pwpath = require('../path') +var index = require('../index') //--------------------------------------------------------------------- -// 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, - })) } - -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('clear') - // -> - // .index('reset') - // -> - // .index('local') - // -> - // - // .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() }, -}) - - - - -//--------------------------------------------------------------------- - // // cached(, [, ...]) @@ -556,6 +153,10 @@ module.BaseStore = { set data(value){ this.__data = value }, + get index_attrs(){ + return [...index.iter(this)] }, + index: async function(action='get', ...args){ + return index.index(this, ...arguments) }, // XXX INDEX... __xpaths__: async function(){ @@ -574,7 +175,7 @@ module.BaseStore = { && this.next.__xpaths_modified > t) this.__xpaths_next_exists = !this.next return changed }, - __xpaths: makeIndex('xpaths', { + __xpaths: index.makeIndex('xpaths', { update: async function(data, path){ data = await data // XXX normalize??? @@ -593,7 +194,7 @@ module.BaseStore = { // NOTE: this is build from .paths so there is no need to define a // way to merge... - __xnames: makeIndex('xnames', + __xnames: index.makeIndex('xnames', function(){ return this.xpaths .iter() @@ -607,13 +208,15 @@ module.BaseStore = { // XXX normalize??? var n = pwpath.basename(path) if(!n.includes('*') - && !data[n].includes(path)){ + && !(data[n] ?? []).includes(path)){ (data[n] = data[n] ?? []).push(path) } return data }, remove: async function(data, path){ data = await data // XXX normalize??? var n = pwpath.basename(path) + if(!(n in data)){ + return data } data[n].includes(path) && data[n].splice(data[n].indexOf(path), 1) data[n].length == 0 @@ -1067,7 +670,7 @@ module.BaseStore = { : res }, } -IndexManagerMixin(BaseStore) +//index.IndexManagerMixin(BaseStore) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -