| 
									
										
										
										
											2022-08-03 01:35:19 +03:00
										 |  |  | /********************************************************************** | 
					
						
							|  |  |  | *  | 
					
						
							|  |  |  | * | 
					
						
							|  |  |  | * | 
					
						
							|  |  |  | **********************************************************************/ | 
					
						
							|  |  |  | ((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') | 
					
						
							|  |  |  | var types = require('ig-types') | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-05 18:21:18 +03:00
										 |  |  | var pwpath = require('../path') | 
					
						
							| 
									
										
										
										
											2022-08-03 01:35:19 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-19 19:00:20 +03:00
										 |  |  | //---------------------------------------------------------------------
 | 
					
						
							|  |  |  | //
 | 
					
						
							| 
									
										
										
										
											2022-10-20 00:59:19 +03:00
										 |  |  | // - define (name, generate, merge) 	- DONE
 | 
					
						
							|  |  |  | // 		inline							- DONE
 | 
					
						
							|  |  |  | // 		online							- DONE
 | 
					
						
							|  |  |  | // - undefine (online)					- ???
 | 
					
						
							|  |  |  | // - enumerate/list						- DONE
 | 
					
						
							| 
									
										
										
										
											2022-10-19 19:00:20 +03:00
										 |  |  | // - group operations:
 | 
					
						
							| 
									
										
										
										
											2022-10-20 00:59:19 +03:00
										 |  |  | // 		- reset (cache)					- DONE
 | 
					
						
							|  |  |  | // 		- custom						- DONE
 | 
					
						
							| 
									
										
										
										
											2022-10-19 19:00:20 +03:00
										 |  |  | //
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-19 19:34:10 +03:00
										 |  |  | //
 | 
					
						
							|  |  |  | //	makeIndexed(<name>, <generate>[, <options>])
 | 
					
						
							|  |  |  | //		-> <index-handler> 
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | //	Get merged data (cached)
 | 
					
						
							|  |  |  | //	<index-handler>()
 | 
					
						
							|  |  |  | //	<index-handler>('get')
 | 
					
						
							|  |  |  | //		-> <data>
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | //	Get local data (uncached)...
 | 
					
						
							|  |  |  | //	<index-handler>('local')
 | 
					
						
							|  |  |  | //		-> <data>
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | //	Clear cache...
 | 
					
						
							|  |  |  | //	<index-handler>('clear')
 | 
					
						
							|  |  |  | //		-> <data>
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | //	Reset cache (clear then get)...
 | 
					
						
							|  |  |  | //	<index-handler>('reset')
 | 
					
						
							|  |  |  | //		-> <data>
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | //	Run custom action...
 | 
					
						
							|  |  |  | //	<index-handler>(<action-name>), ...)
 | 
					
						
							|  |  |  | //		-> <data>
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // Special methods:
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // 	Special method to generate local <data>...
 | 
					
						
							|  |  |  | // 	.__<name>__()
 | 
					
						
							|  |  |  | // 		-> <data>
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // 	Merge local data with other sources...
 | 
					
						
							|  |  |  | // 	.__<name>_merge__(<data>)
 | 
					
						
							|  |  |  | // 		-> <data>
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // 	Handle custom action...
 | 
					
						
							|  |  |  | // 	.__<name>_<action-name>__(<data>. ...)
 | 
					
						
							|  |  |  | // 		-> <data>
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // Special attributes:
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // 	Cached data...
 | 
					
						
							|  |  |  | // 	.__<name>_cache / .<name>
 | 
					
						
							|  |  |  | //
 | 
					
						
							| 
									
										
										
										
											2022-10-19 19:00:20 +03:00
										 |  |  | //
 | 
					
						
							| 
									
										
										
										
											2022-10-20 00:59:19 +03:00
										 |  |  | // Options format:
 | 
					
						
							|  |  |  | // 	{
 | 
					
						
							|  |  |  | // 		// XXX
 | 
					
						
							|  |  |  | // 		attr: false 
 | 
					
						
							|  |  |  | // 			| true 
 | 
					
						
							|  |  |  | // 			| <name>,
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // 		// 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: [ 
 | 
					
						
							|  |  |  | // 			<index-name>, 
 | 
					
						
							|  |  |  | // 			... 
 | 
					
						
							|  |  |  | // 		],
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // 		// custom action...
 | 
					
						
							|  |  |  | // 		// NOTE: this is the same as defining .__<name>_<action-name>__(..)
 | 
					
						
							|  |  |  | // 		//		method...
 | 
					
						
							|  |  |  | // 		<action-name>: <func>,
 | 
					
						
							|  |  |  | // 	}
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // XXX move this to a separate module (???)
 | 
					
						
							| 
									
										
										
										
											2022-10-19 19:00:20 +03:00
										 |  |  | var makeIndexed =  | 
					
						
							| 
									
										
										
										
											2022-10-20 00:59:19 +03:00
										 |  |  | module.makeIndexed = | 
					
						
							| 
									
										
										
										
											2022-10-19 19:00:20 +03:00
										 |  |  | function(name, generate, options={}){ | 
					
						
							| 
									
										
										
										
											2022-10-20 00:59:19 +03:00
										 |  |  | 	// attr names...
 | 
					
						
							|  |  |  | 	var cache =  | 
					
						
							|  |  |  | 		typeof(options.attr) == 'string' ? | 
					
						
							|  |  |  | 			options.attr | 
					
						
							|  |  |  | 		// XXX revise default...
 | 
					
						
							|  |  |  | 		: !!options.attr ? | 
					
						
							|  |  |  | 			name | 
					
						
							| 
									
										
										
										
											2022-10-19 19:00:20 +03:00
										 |  |  | 		: `__${name}_cache` | 
					
						
							|  |  |  | 	var merge = `__${name}_merge__` | 
					
						
							|  |  |  | 	var special = `__${name}__` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// make local cache...
 | 
					
						
							|  |  |  | 	var _make = function(){ | 
					
						
							| 
									
										
										
										
											2022-10-20 00:59:19 +03:00
										 |  |  | 		var res = this[special] != null ? | 
					
						
							| 
									
										
										
										
											2022-10-19 19:00:20 +03:00
										 |  |  | 			this[special]() | 
					
						
							| 
									
										
										
										
											2022-10-20 00:59:19 +03:00
										 |  |  | 			: generate.call(this)  | 
					
						
							|  |  |  | 		meth.modified = Date.now() | 
					
						
							|  |  |  | 		return res } | 
					
						
							| 
									
										
										
										
											2022-10-19 19:00:20 +03:00
										 |  |  | 	// unwrap a promised value into cache...
 | 
					
						
							|  |  |  | 	var _await = function(obj, val){ | 
					
						
							|  |  |  | 		if(val instanceof Promise){ | 
					
						
							|  |  |  | 			val.then(function(value){ | 
					
						
							|  |  |  | 				obj[cache] = value }) } | 
					
						
							|  |  |  | 		return val } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-20 00:59:19 +03:00
										 |  |  | 	// build the method...
 | 
					
						
							| 
									
										
										
										
											2022-10-19 19:00:20 +03:00
										 |  |  | 	var meth | 
					
						
							|  |  |  | 	return (meth = Object.assign( | 
					
						
							|  |  |  | 		function(action='get', ...args){ | 
					
						
							|  |  |  | 			var that = this | 
					
						
							| 
									
										
										
										
											2022-10-20 00:59:19 +03:00
										 |  |  | 			// action: clear/reset...
 | 
					
						
							| 
									
										
										
										
											2022-10-19 19:00:20 +03:00
										 |  |  | 			if(action == 'clear'  | 
					
						
							|  |  |  | 					|| action == 'reset'){ | 
					
						
							|  |  |  | 				delete this[cache] } | 
					
						
							| 
									
										
										
										
											2022-10-20 00:59:19 +03:00
										 |  |  | 			// action: clear...
 | 
					
						
							| 
									
										
										
										
											2022-10-19 19:00:20 +03:00
										 |  |  | 			if(action == 'clear'){ | 
					
						
							|  |  |  | 				return } | 
					
						
							| 
									
										
										
										
											2022-10-20 00:59:19 +03:00
										 |  |  | 			// check dependencies (timestamps)...
 | 
					
						
							|  |  |  | 			if(cache in this  | 
					
						
							|  |  |  | 					&& meth.options.depends){ | 
					
						
							|  |  |  | 				var cur = meth.modified | 
					
						
							|  |  |  | 				for(var dep of meth.options.depends){ | 
					
						
							|  |  |  | 					if(this[dep].modified > cur){ | 
					
						
							|  |  |  | 						delete this[cache] | 
					
						
							|  |  |  | 						break } } } | 
					
						
							|  |  |  | 			// action: other...
 | 
					
						
							| 
									
										
										
										
											2022-10-19 19:00:20 +03:00
										 |  |  | 			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') | 
					
						
							| 
									
										
										
										
											2022-10-20 00:59:19 +03:00
										 |  |  | 				var res = _await(this, this[cache] =  | 
					
						
							| 
									
										
										
										
											2022-10-19 19:34:10 +03:00
										 |  |  | 					// NOTE: this[action_meth] will fully shadow options[action]...
 | 
					
						
							|  |  |  | 					action_meth in this ? | 
					
						
							|  |  |  | 						this[action_meth](cur, ...args) | 
					
						
							|  |  |  | 					: (action in options  | 
					
						
							| 
									
										
										
										
											2022-10-19 19:00:20 +03:00
										 |  |  | 							&& typeof(options[action]) == 'function') ? | 
					
						
							|  |  |  | 						options[action].call(this, cur, ...args) | 
					
						
							| 
									
										
										
										
											2022-10-20 00:59:19 +03:00
										 |  |  | 					: cur)  | 
					
						
							|  |  |  | 				res !== cur | 
					
						
							|  |  |  | 					&& (meth.modified = Date.now()) | 
					
						
							|  |  |  | 				return res } | 
					
						
							|  |  |  | 			// action: get/local...
 | 
					
						
							| 
									
										
										
										
											2022-10-19 19:00:20 +03:00
										 |  |  | 			return _await(this, | 
					
						
							|  |  |  | 				// NOTE: this is intentionally not cached...
 | 
					
						
							|  |  |  | 				action == 'local' ? | 
					
						
							|  |  |  | 					_make.call(this) | 
					
						
							|  |  |  | 				// get...
 | 
					
						
							|  |  |  | 				: (this[cache] = | 
					
						
							|  |  |  | 					// cached...
 | 
					
						
							|  |  |  | 					this[cache] != null ? | 
					
						
							|  |  |  | 						this[cache]  | 
					
						
							|  |  |  | 					// generate + merge...
 | 
					
						
							|  |  |  | 					: this[merge] != null ? | 
					
						
							|  |  |  | 						this[merge](_make.call(this)) | 
					
						
							|  |  |  | 					// generate...
 | 
					
						
							|  |  |  | 					: _make.call(this)) ) }, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			attr: name, | 
					
						
							|  |  |  | 			indexed: true, | 
					
						
							|  |  |  | 			options, | 
					
						
							|  |  |  | 		})) } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-20 00:59:19 +03:00
										 |  |  | // XXX make this a mixin...
 | 
					
						
							|  |  |  | var IndexManagerMixin =  | 
					
						
							|  |  |  | module.IndexManagerMixin = | 
					
						
							|  |  |  | object.Mixin('IndexManagerMixin', { | 
					
						
							|  |  |  | 	// List of index handler attribute names...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2022-10-19 19:34:10 +03:00
										 |  |  | 	// XXX rename???
 | 
					
						
							| 
									
										
										
										
											2022-10-20 00:59:19 +03:00
										 |  |  | 	get index_attrs(){ | 
					
						
							| 
									
										
										
										
											2022-10-19 19:00:20 +03:00
										 |  |  | 		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 }) }, | 
					
						
							| 
									
										
										
										
											2022-10-20 00:59:19 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	// 	.index()
 | 
					
						
							|  |  |  | 	// 	.index('get')
 | 
					
						
							|  |  |  | 	// 		-> <indexi>
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 	.index('clear')
 | 
					
						
							|  |  |  | 	// 		-> <indexi>
 | 
					
						
							|  |  |  | 	// 	.index('reset')
 | 
					
						
							|  |  |  | 	// 		-> <indexi>
 | 
					
						
							|  |  |  | 	// 	.index('local')
 | 
					
						
							|  |  |  | 	// 		-> <indexi>
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 	.index(<action>, ...)
 | 
					
						
							|  |  |  | 	// 		-> <indexi>
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2022-10-19 19:00:20 +03:00
										 |  |  | 	index: async function(action='get', ...args){ | 
					
						
							| 
									
										
										
										
											2022-10-20 00:59:19 +03:00
										 |  |  | 		// create a new index...
 | 
					
						
							|  |  |  | 		if(action == 'new'){ | 
					
						
							|  |  |  | 			var res = makeIndexed(...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...
 | 
					
						
							| 
									
										
										
										
											2022-10-19 19:00:20 +03:00
										 |  |  | 		var that = this | 
					
						
							|  |  |  | 		return Object.fromEntries( | 
					
						
							|  |  |  | 			await Promise.all( | 
					
						
							| 
									
										
										
										
											2022-10-20 00:59:19 +03:00
										 |  |  | 				this.index_attrs | 
					
						
							| 
									
										
										
										
											2022-10-19 19:00:20 +03:00
										 |  |  | 					.map(async function(name){ | 
					
						
							|  |  |  | 						return [ | 
					
						
							|  |  |  | 							that[name].attr,  | 
					
						
							|  |  |  | 							await that[name](action, ...args), | 
					
						
							|  |  |  | 						] }))) }, | 
					
						
							| 
									
										
										
										
											2022-10-20 00:59:19 +03:00
										 |  |  | }) | 
					
						
							| 
									
										
										
										
											2022-10-19 19:00:20 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-20 00:59:19 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var indexTest =  | 
					
						
							|  |  |  | module.indexTest = | 
					
						
							|  |  |  | IndexManagerMixin({ | 
					
						
							| 
									
										
										
										
											2022-10-19 19:00:20 +03:00
										 |  |  | 	// tests...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	moo: makeIndexed('moo', () => 123), | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-19 19:34:10 +03:00
										 |  |  | 	foo_index: makeIndexed('foo', () => 123, { | 
					
						
							| 
									
										
										
										
											2022-10-19 19:00:20 +03:00
										 |  |  | 		attr: true, | 
					
						
							|  |  |  | 		add: function(cur, val){ | 
					
						
							|  |  |  | 			return cur + val }, | 
					
						
							|  |  |  | 	}), | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	__boo_add__: function(cur, val){ | 
					
						
							|  |  |  | 		return cur + val }, | 
					
						
							|  |  |  | 	boo: makeIndexed('boo', () => 123), | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-19 19:34:10 +03:00
										 |  |  | 	__soo_add__: async function(cur, val){ | 
					
						
							| 
									
										
										
										
											2022-10-19 19:00:20 +03:00
										 |  |  | 		return await cur + val }, | 
					
						
							| 
									
										
										
										
											2022-10-19 19:34:10 +03:00
										 |  |  | 	__soo: makeIndexed('soo', async () => 123), | 
					
						
							|  |  |  | 	get soo(){ | 
					
						
							| 
									
										
										
										
											2022-10-20 00:59:19 +03:00
										 |  |  | 		return this.__soo() }, | 
					
						
							| 
									
										
										
										
											2022-10-19 19:34:10 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-20 00:59:19 +03:00
										 |  |  | 	__sum: makeIndexed('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() }, | 
					
						
							|  |  |  | }) | 
					
						
							| 
									
										
										
										
											2022-10-19 19:00:20 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-24 23:13:24 +03:00
										 |  |  | //---------------------------------------------------------------------
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // 	cached(<name>, <update>[, ...<args>])
 | 
					
						
							|  |  |  | // 	cached(<name>, <get>, <update>[, ...<args>])
 | 
					
						
							|  |  |  | // 		-> <func>
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // NOTE: in the first case (no <get>) the first <args> item can not be 
 | 
					
						
							|  |  |  | // 		a function...
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // XXX better introspection???
 | 
					
						
							|  |  |  | var cached =  | 
					
						
							|  |  |  | module.cached = | 
					
						
							|  |  |  | function(name, get, update, ...args){ | 
					
						
							|  |  |  | 	name = `__${name}_cache` | 
					
						
							|  |  |  | 	if(typeof(update) != 'function'){ | 
					
						
							|  |  |  | 		args.unshift(update) | 
					
						
							|  |  |  | 		update = get | 
					
						
							|  |  |  | 		get = null } | 
					
						
							|  |  |  | 	return update instanceof types.AsyncFunction ? | 
					
						
							|  |  |  | 		async function(){ | 
					
						
							|  |  |  | 			var cache = this[name] =  | 
					
						
							|  |  |  | 				this[name]  | 
					
						
							|  |  |  | 					?? await update.call(this) | 
					
						
							|  |  |  | 			return get ? | 
					
						
							|  |  |  | 				get.call(this, cache, ...arguments) | 
					
						
							|  |  |  | 			: cache } | 
					
						
							|  |  |  | 		: function(){ | 
					
						
							|  |  |  | 			var cache = this[name] =  | 
					
						
							|  |  |  | 				this[name]  | 
					
						
							|  |  |  | 					?? update.call(this) | 
					
						
							|  |  |  | 			return get ? | 
					
						
							|  |  |  | 				get.call(this, cache, ...arguments) | 
					
						
							|  |  |  | 			: cache } } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-03 01:35:19 +03:00
										 |  |  | //---------------------------------------------------------------------
 | 
					
						
							|  |  |  | // Store...
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-01 11:12:13 +03:00
										 |  |  | //
 | 
					
						
							|  |  |  | // API levels:
 | 
					
						
							|  |  |  | // 	Level 1 -- implementation API
 | 
					
						
							|  |  |  | // 		This level is the base API, this is used by all other Levels.
 | 
					
						
							|  |  |  | // 		This is the only level that needs to be fully overloaded by store 
 | 
					
						
							|  |  |  | // 		implementations (no super calls necessary).
 | 
					
						
							|  |  |  | // 		The base methods that need to be overloaded for the store to work:
 | 
					
						
							|  |  |  | // 			.__paths__()
 | 
					
						
							|  |  |  | // 				-> <keys>
 | 
					
						
							|  |  |  | // 			.__exists__(path, ..)
 | 
					
						
							|  |  |  | // 				-> <path>
 | 
					
						
							|  |  |  | // 				-> false
 | 
					
						
							|  |  |  | // 			.__get__(path, ..)
 | 
					
						
							|  |  |  | // 				-> <data>
 | 
					
						
							|  |  |  | // 		Optional for r/w stores:
 | 
					
						
							|  |  |  | // 			.__update__(path, ..)
 | 
					
						
							|  |  |  | // 			.__delete__(path, ..)
 | 
					
						
							|  |  |  | // 	Level 2 -- feature API
 | 
					
						
							|  |  |  | // 		This can use Level 1 and Level 2 internally.
 | 
					
						
							|  |  |  | // 		When overloading it is needed to to call the super method to 
 | 
					
						
							|  |  |  | // 		retain base functionality.
 | 
					
						
							|  |  |  | // 		All overloading here is optional.
 | 
					
						
							|  |  |  | // 			.paths()
 | 
					
						
							|  |  |  | // 				-> <path-list>
 | 
					
						
							|  |  |  | // 			.names()
 | 
					
						
							|  |  |  | // 				-> <name-index>
 | 
					
						
							|  |  |  | // 			.exists(<path>)
 | 
					
						
							|  |  |  | // 				-> <real-path>
 | 
					
						
							|  |  |  | // 				-> false
 | 
					
						
							|  |  |  | // 			.get(<path>)
 | 
					
						
							|  |  |  | // 				-> <data>
 | 
					
						
							|  |  |  | // 				-> undefined
 | 
					
						
							|  |  |  | // 			.metadata(<path>[, <data>])
 | 
					
						
							|  |  |  | // 				-> <store> -- on write
 | 
					
						
							|  |  |  | // 				-> <data>
 | 
					
						
							|  |  |  | // 				-> undefined
 | 
					
						
							|  |  |  | // 			.update(<path>, <data>)
 | 
					
						
							|  |  |  | // 				-> <store>
 | 
					
						
							|  |  |  | // 			.delete(<path>)
 | 
					
						
							|  |  |  | // 				-> <store>
 | 
					
						
							|  |  |  | // 			.load(..)
 | 
					
						
							|  |  |  | // 				-> <store>
 | 
					
						
							|  |  |  | // 			.json(..)
 | 
					
						
							|  |  |  | // 				-> <json>
 | 
					
						
							|  |  |  | // 	Level 3
 | 
					
						
							|  |  |  | // 		...
 | 
					
						
							|  |  |  | //
 | 
					
						
							| 
									
										
										
										
											2022-08-03 01:35:19 +03:00
										 |  |  | //
 | 
					
						
							|  |  |  | // To create a store adapter:
 | 
					
						
							|  |  |  | // 		- inherit from BaseStore
 | 
					
						
							|  |  |  | // 		- overload:
 | 
					
						
							|  |  |  | // 			.__paths__()
 | 
					
						
							|  |  |  | // 				-> <keys>
 | 
					
						
							| 
									
										
										
										
											2022-08-29 18:22:35 +03:00
										 |  |  | // 			.__exists__(path, ..)
 | 
					
						
							| 
									
										
										
										
											2022-08-03 01:35:19 +03:00
										 |  |  | // 				-> <path>
 | 
					
						
							|  |  |  | // 				-> false
 | 
					
						
							| 
									
										
										
										
											2022-08-29 18:22:35 +03:00
										 |  |  | // 			.__get__(path, ..)
 | 
					
						
							| 
									
										
										
										
											2022-08-03 01:35:19 +03:00
										 |  |  | // 				-> <data>
 | 
					
						
							|  |  |  | // 		- optionally (for writable stores)
 | 
					
						
							| 
									
										
										
										
											2022-08-29 18:22:35 +03:00
										 |  |  | // 			.__update__(path, ..)
 | 
					
						
							|  |  |  | // 			.__delete__(path, ..)
 | 
					
						
							| 
									
										
										
										
											2022-08-03 01:35:19 +03:00
										 |  |  | // 			.load(..)
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // NOTE: store keys must be normalized to avoid conditions where two
 | 
					
						
							|  |  |  | // 		forms of the same path exist at the same time...
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // XXX potential architectural problems:
 | 
					
						
							|  |  |  | // 		- .paths()
 | 
					
						
							|  |  |  | // 			external index -- is this good???
 | 
					
						
							|  |  |  | // 			bottleneck??
 | 
					
						
							|  |  |  | // 			cache/index???
 | 
					
						
							|  |  |  | // 			...can we avoid this??
 | 
					
						
							|  |  |  | //
 | 
					
						
							| 
									
										
										
										
											2022-08-03 11:33:23 +03:00
										 |  |  | // XXX might be a good idea to split this into a generic store and a MemStore...
 | 
					
						
							| 
									
										
										
										
											2022-08-03 01:35:19 +03:00
										 |  |  | // XXX LEADING_SLASH should this be strict about leading '/' in paths???
 | 
					
						
							|  |  |  | // 		...this may lead to duplicate paths created -- '/a/b' and 'a/b'
 | 
					
						
							|  |  |  | // XXX should we support page symlinking???
 | 
					
						
							|  |  |  | // XXX async: not sure if we need to return this from async methods...
 | 
					
						
							|  |  |  | var BaseStore =  | 
					
						
							|  |  |  | module.BaseStore = { | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-04 11:06:19 +03:00
										 |  |  | 	// XXX revise naming...
 | 
					
						
							| 
									
										
										
										
											2022-08-03 01:35:19 +03:00
										 |  |  | 	next: undefined, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-20 01:00:44 +03:00
										 |  |  | 	onUpdate: types.event.Event('update'), | 
					
						
							|  |  |  | 	onDelete: types.event.Event('delete'), | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-03 01:35:19 +03:00
										 |  |  | 	// NOTE: .data is not part of the spec and can be implementation-specific,
 | 
					
						
							|  |  |  | 	// 		only .__<name>__(..) use it internally... (XXX check this)
 | 
					
						
							|  |  |  | 	__data: undefined, | 
					
						
							|  |  |  | 	get data(){ | 
					
						
							|  |  |  | 		return this.__data  | 
					
						
							|  |  |  | 			?? (this.__data = {}) }, | 
					
						
							|  |  |  | 	set data(value){ | 
					
						
							|  |  |  | 		this.__data = value }, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// XXX might be a good idea to cache this...
 | 
					
						
							|  |  |  | 	__paths__: async function(){ | 
					
						
							|  |  |  | 		return Object.keys(this.data) }, | 
					
						
							| 
									
										
										
										
											2022-08-24 23:13:24 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// local paths...
 | 
					
						
							|  |  |  | 	__paths: cached('paths', async function(){ | 
					
						
							|  |  |  | 		return this.__paths__() }), | 
					
						
							| 
									
										
										
										
											2022-08-28 10:33:47 +03:00
										 |  |  | 	// NOTE: this produces an unsorted list...
 | 
					
						
							| 
									
										
										
										
											2022-08-24 23:13:24 +03:00
										 |  |  | 	// XXX should this also be cached???
 | 
					
						
							| 
									
										
										
										
											2022-08-03 01:35:19 +03:00
										 |  |  | 	paths: async function(local=false){ | 
					
						
							| 
									
										
										
										
											2022-08-24 23:13:24 +03:00
										 |  |  | 		return this.__paths() | 
					
						
							| 
									
										
										
										
											2022-08-03 01:35:19 +03:00
										 |  |  | 			.iter() | 
					
						
							|  |  |  | 			.concat((!local && (this.next || {}).paths) ?  | 
					
						
							|  |  |  | 				this.next.paths()  | 
					
						
							|  |  |  | 				: []) }, | 
					
						
							| 
									
										
										
										
											2022-10-12 03:25:07 +03:00
										 |  |  | 	// XXX BUG: after caching this will ignore the local argument....
 | 
					
						
							|  |  |  | 	names: cached('names', async function(local=false){ | 
					
						
							|  |  |  | 		return this.paths(local) | 
					
						
							| 
									
										
										
										
											2022-08-24 23:13:24 +03:00
										 |  |  | 			.iter() | 
					
						
							|  |  |  | 			.reduce(function(res, path){ | 
					
						
							|  |  |  | 				var n = pwpath.basename(path) | 
					
						
							|  |  |  | 				if(!n.includes('*')){ | 
					
						
							|  |  |  | 					(res[n] = res[n] ?? []).push(path) } | 
					
						
							|  |  |  | 				return res }, {}) }), | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-25 11:28:01 +03:00
										 |  |  | 	// XXX sort paths based on search order into three groups:
 | 
					
						
							|  |  |  | 	// 		- non-system
 | 
					
						
							|  |  |  | 	// 			...sorted by length?
 | 
					
						
							|  |  |  | 	// 		- system 
 | 
					
						
							|  |  |  | 	// 			...sort based on system search order?
 | 
					
						
							|  |  |  | 	__sort_names: function(){}, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-24 23:13:24 +03:00
										 |  |  | 	__cache_add: function(path){ | 
					
						
							|  |  |  | 		if(this.__paths_cache){ | 
					
						
							|  |  |  | 			this.__paths_cache.includes(path)  | 
					
						
							|  |  |  | 				|| this.__paths_cache.push(path) } | 
					
						
							|  |  |  | 		if(this.__names_cache){ | 
					
						
							|  |  |  | 			var name = pwpath.basename(path) | 
					
						
							|  |  |  | 			var names = (this.__names_cache[name] =  | 
					
						
							|  |  |  | 				this.__names_cache[name]  | 
					
						
							|  |  |  | 					?? []) | 
					
						
							|  |  |  | 			names.includes(path) | 
					
						
							|  |  |  | 				|| names.push(path) } | 
					
						
							|  |  |  | 		return this }, | 
					
						
							|  |  |  | 	__cache_remove: function(path){ | 
					
						
							|  |  |  | 		if(this.__paths_cache){ | 
					
						
							|  |  |  | 			var paths = this.__paths_cache | 
					
						
							|  |  |  | 			paths.splice( | 
					
						
							|  |  |  | 				paths.indexOf( | 
					
						
							|  |  |  | 					paths.includes(path) ?  | 
					
						
							|  |  |  | 						path | 
					
						
							|  |  |  | 					: path[0] == '/' ? | 
					
						
							|  |  |  | 						path.slice(1) | 
					
						
							|  |  |  | 					: '/'+path), | 
					
						
							|  |  |  | 				1) } | 
					
						
							|  |  |  | 		if(this.__names_cache){ | 
					
						
							|  |  |  | 			var name = pwpath.basename(path) | 
					
						
							|  |  |  | 			var names = (this.__names_cache[name] =  | 
					
						
							|  |  |  | 				this.__names_cache[name]  | 
					
						
							|  |  |  | 					?? []) | 
					
						
							|  |  |  | 			var i = names.indexOf(path) | 
					
						
							|  |  |  | 			i >= 0 | 
					
						
							|  |  |  | 				&& names.splice(i, 1) | 
					
						
							|  |  |  | 			if(names.length == 0){ | 
					
						
							|  |  |  | 				delete this.__names_cache[name] } } | 
					
						
							|  |  |  | 		return this }, | 
					
						
							| 
									
										
										
										
											2022-08-03 01:35:19 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 	.exists(<path>)
 | 
					
						
							|  |  |  | 	// 		-> <normalized-path>
 | 
					
						
							|  |  |  | 	// 		-> false
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// XXX might be a good idea to cache this...
 | 
					
						
							|  |  |  | 	__exists__: async function(path){ | 
					
						
							|  |  |  | 		return path in this.data | 
					
						
							|  |  |  | 				&& path }, | 
					
						
							|  |  |  | 	exists: async function(path){ | 
					
						
							| 
									
										
										
										
											2022-10-10 18:08:02 +03:00
										 |  |  | 		var {path, args} =  | 
					
						
							|  |  |  | 			pwpath.splitArgs( | 
					
						
							|  |  |  | 				pwpath.sanitize(path, 'string')) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// NOTE: all paths at this point and in store are 
 | 
					
						
							|  |  |  | 		// 		absolute, so we check both with the leading 
 | 
					
						
							|  |  |  | 		// 		'/' and without it to make things a bit more 
 | 
					
						
							|  |  |  | 		// 		relaxed and return the actual matching path...
 | 
					
						
							|  |  |  | 		var res = await this.__exists__(path) | 
					
						
							|  |  |  | 		// NOTE: res can be '' and thus we can't simply chain via || here...
 | 
					
						
							|  |  |  | 		typeof(res) != 'string' | 
					
						
							|  |  |  | 			&& (res = await this.__exists__('/'+ path)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// delegate to .next...
 | 
					
						
							|  |  |  | 		typeof(res) != 'string' | 
					
						
							|  |  |  | 			&& (this.next || {}).__exists__ | 
					
						
							|  |  |  | 			&& (res = await this.next.__exists__(path)) | 
					
						
							|  |  |  | 		typeof(res) != 'string' | 
					
						
							|  |  |  | 			&& (this.next || {}).__exists__ | 
					
						
							|  |  |  | 			&& (res = await this.next.__exists__('/'+path)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if(typeof(res) != 'string'){ | 
					
						
							| 
									
										
										
										
											2022-09-07 16:37:26 +03:00
										 |  |  | 			return false } | 
					
						
							|  |  |  | 		return pwpath.joinArgs(res, args) }, | 
					
						
							| 
									
										
										
										
											2022-08-03 01:35:19 +03:00
										 |  |  | 	// find the closest existing alternative path...
 | 
					
						
							| 
									
										
										
										
											2022-08-24 23:13:24 +03:00
										 |  |  | 	// XXX CACHED....
 | 
					
						
							|  |  |  | 	find: async function(path, strict=false){ | 
					
						
							| 
									
										
										
										
											2022-09-07 16:37:26 +03:00
										 |  |  | 		var {path, args} = pwpath.splitArgs(path) | 
					
						
							|  |  |  | 		args = pwpath.joinArgs('', args) | 
					
						
							| 
									
										
										
										
											2022-08-24 23:13:24 +03:00
										 |  |  | 		// build list of existing page candidates...
 | 
					
						
							|  |  |  | 		var names = await this.names() | 
					
						
							|  |  |  | 		var pages = new Set( | 
					
						
							|  |  |  | 			pwpath.names(path) | 
					
						
							|  |  |  | 				.map(function(name){ | 
					
						
							|  |  |  | 					return names[name] ?? [] }) | 
					
						
							|  |  |  | 				.flat()) | 
					
						
							|  |  |  | 		// select accessible candidate...
 | 
					
						
							|  |  |  | 		for(var p of pwpath.paths(path, !!strict)){ | 
					
						
							|  |  |  | 			if(pages.has(p)){ | 
					
						
							| 
									
										
										
										
											2022-09-07 16:37:26 +03:00
										 |  |  | 				return p+args } | 
					
						
							| 
									
										
										
										
											2022-08-24 23:13:24 +03:00
										 |  |  | 			p = p[0] == '/' ?  | 
					
						
							|  |  |  | 				p.slice(1)  | 
					
						
							|  |  |  | 				: '/'+p | 
					
						
							|  |  |  | 			if(pages.has(p)){ | 
					
						
							| 
									
										
										
										
											2022-09-07 16:37:26 +03:00
										 |  |  | 				return p+args } } }, | 
					
						
							| 
									
										
										
										
											2022-08-24 23:13:24 +03:00
										 |  |  | 	/*/ | 
					
						
							| 
									
										
										
										
											2022-08-05 12:46:00 +03:00
										 |  |  | 	find: async function(path, strict=false){ | 
					
						
							| 
									
										
										
										
											2022-08-16 11:00:23 +03:00
										 |  |  | 		for(var p of pwpath.paths(path, !!strict)){ | 
					
						
							|  |  |  | 			if(p = await this.exists(p)){ | 
					
						
							| 
									
										
										
										
											2022-08-03 01:35:19 +03:00
										 |  |  | 				return p } } }, | 
					
						
							| 
									
										
										
										
											2022-08-24 23:13:24 +03:00
										 |  |  | 	//*/
 | 
					
						
							| 
									
										
										
										
											2022-08-03 01:35:19 +03:00
										 |  |  | 	// 
 | 
					
						
							|  |  |  | 	// 	Resolve page for path
 | 
					
						
							|  |  |  | 	// 	.match(<path>)
 | 
					
						
							|  |  |  | 	// 		-> <path>
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 	Match paths (non-strict mode)
 | 
					
						
							|  |  |  | 	// 	.match(<pattern>)
 | 
					
						
							|  |  |  | 	// 	.match(<pattern>, false)
 | 
					
						
							|  |  |  | 	// 		-> [<path>, ...]
 | 
					
						
							|  |  |  | 	// 		-> []
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 	Match pages (paths in strict mode)
 | 
					
						
							|  |  |  | 	// 	.match(<pattern>, true)
 | 
					
						
							|  |  |  | 	// 		-> [<path>, ...]
 | 
					
						
							|  |  |  | 	// 		-> []
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// In strict mode the trailing star in the pattern will only match 
 | 
					
						
							|  |  |  | 	// actual existing pages, while in non-strict mode the pattern will 
 | 
					
						
							|  |  |  | 	// match all sub-paths.
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	match: async function(path, strict=false){ | 
					
						
							| 
									
										
										
										
											2022-09-10 19:07:25 +03:00
										 |  |  | 		var that = this | 
					
						
							| 
									
										
										
										
											2022-08-03 01:35:19 +03:00
										 |  |  | 		// pattern match * / **
 | 
					
						
							|  |  |  | 		if(path.includes('*')  | 
					
						
							|  |  |  | 				|| path.includes('**')){ | 
					
						
							|  |  |  | 			var order = (this.metadata(path) ?? {}).order || [] | 
					
						
							| 
									
										
										
										
											2022-09-07 16:37:26 +03:00
										 |  |  | 			var {path, args} = pwpath.splitArgs(path) | 
					
						
							| 
									
										
										
										
											2022-09-10 02:26:10 +03:00
										 |  |  | 			var all = args.all | 
					
						
							| 
									
										
										
										
											2022-09-07 16:37:26 +03:00
										 |  |  | 			args = pwpath.joinArgs('', args) | 
					
						
							| 
									
										
										
										
											2022-08-03 01:35:19 +03:00
										 |  |  | 			// NOTE: we are matching full paths only here so leading and 
 | 
					
						
							|  |  |  | 			// 		trainling '/' are optional...
 | 
					
						
							| 
									
										
										
										
											2022-09-14 03:16:26 +03:00
										 |  |  | 			var pattern = new RegExp(`^\\/?` | 
					
						
							|  |  |  | 				+RegExp.quoteRegExp( | 
					
						
							|  |  |  | 					// remove leading/trailing '/'
 | 
					
						
							|  |  |  | 					path.replace(/^\/|\/$/g, '')) | 
					
						
							|  |  |  | 					// pattern: **
 | 
					
						
							|  |  |  | 					.replace(/\\\*\\\*/g, '(.*)') | 
					
						
							|  |  |  | 					// pattern: *
 | 
					
						
							|  |  |  | 					// NOTE: we are prepping the leading '.' of a pattern 
 | 
					
						
							|  |  |  | 					// 		dir for hidden tests...
 | 
					
						
							|  |  |  | 					.replace(/(^|\\\/+)(\\\.|)([^\/]*)\\\*/g, '$1$2($3[^\\/]*)') | 
					
						
							|  |  |  | 				+'(?=[\\/]|$)', 'g') | 
					
						
							| 
									
										
										
										
											2022-08-24 23:13:24 +03:00
										 |  |  | 			/*/ XXX CACHED.... | 
					
						
							|  |  |  | 			var name = pwpath.basename(path) | 
					
						
							|  |  |  | 			return [...(name.includes('*') ? | 
					
						
							|  |  |  | 						await this.paths() | 
					
						
							|  |  |  | 						: await (this.names())[name]) | 
					
						
							|  |  |  | 			/*/ | 
					
						
							| 
									
										
										
										
											2022-08-03 01:35:19 +03:00
										 |  |  | 			return [...(await this.paths()) | 
					
						
							| 
									
										
										
										
											2022-08-24 23:13:24 +03:00
										 |  |  | 			//*/
 | 
					
						
							| 
									
										
										
										
											2022-08-03 01:35:19 +03:00
										 |  |  | 					// NOTE: we are not using .filter(..) here as wee 
 | 
					
						
							|  |  |  | 					// 		need to keep parts of the path only and not 
 | 
					
						
							|  |  |  | 					// 		return the whole thing...
 | 
					
						
							|  |  |  | 					.reduce(function(res, p){ | 
					
						
							|  |  |  | 						// skip metadata paths...
 | 
					
						
							|  |  |  | 						if(p.includes('*')){ | 
					
						
							|  |  |  | 							return res } | 
					
						
							| 
									
										
										
										
											2022-09-14 03:16:26 +03:00
										 |  |  | 						var m = [...p.matchAll(pattern)] | 
					
						
							|  |  |  | 						m.length > 0 | 
					
						
							|  |  |  | 							&& (!all ? | 
					
						
							|  |  |  | 								// test if we need to hide things....
 | 
					
						
							|  |  |  | 								m.reduce(function(res, m){ | 
					
						
							|  |  |  | 									return res === false ? | 
					
						
							|  |  |  | 										res | 
					
						
							|  |  |  | 										: !/(^\.|[\\\/]\.)/.test(m[1]) | 
					
						
							|  |  |  | 								}, true) | 
					
						
							|  |  |  | 								: true) | 
					
						
							|  |  |  | 							&& (m = m[0]) | 
					
						
							| 
									
										
										
										
											2022-08-03 01:35:19 +03:00
										 |  |  | 							&& (!strict  | 
					
						
							|  |  |  | 								|| m[0] == p)  | 
					
						
							| 
									
										
										
										
											2022-08-28 10:33:47 +03:00
										 |  |  | 							&& res.add( | 
					
						
							|  |  |  | 								// normalize the path elements...
 | 
					
						
							|  |  |  | 								m[0][0] == '/' ?  | 
					
						
							|  |  |  | 									m[0].slice(1)  | 
					
						
							|  |  |  | 									: m[0]) | 
					
						
							| 
									
										
										
										
											2022-08-03 01:35:19 +03:00
										 |  |  | 						return res }, new Set())] | 
					
						
							| 
									
										
										
										
											2022-09-07 16:37:26 +03:00
										 |  |  | 				.sortAs(order) | 
					
						
							|  |  |  | 				.map(function(p){ | 
					
						
							|  |  |  | 					return p+args })} | 
					
						
							| 
									
										
										
										
											2022-08-03 01:35:19 +03:00
										 |  |  | 		// direct search...
 | 
					
						
							| 
									
										
										
										
											2022-08-28 10:02:58 +03:00
										 |  |  | 		return this.find(path, strict) }, | 
					
						
							| 
									
										
										
										
											2022-08-03 01:35:19 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	// 	.resolve(<path>)
 | 
					
						
							|  |  |  | 	// 		-> <path>
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 	.resolve(<pattern>)
 | 
					
						
							|  |  |  | 	// 		-> [<path>, ...]
 | 
					
						
							|  |  |  | 	// 		-> []
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// This is like .match(..) for non-pattern paths and paths ending 
 | 
					
						
							|  |  |  | 	// with '/'; When patterns end with a non-pattern then match the 
 | 
					
						
							| 
									
										
										
										
											2022-08-24 23:13:24 +03:00
										 |  |  | 	// basedir and add the basename to each resulting path, e.g.:
 | 
					
						
							|  |  |  | 	// 		.match('/*/tree')
 | 
					
						
							|  |  |  | 	// 			-> ['System/tree']
 | 
					
						
							|  |  |  | 	// 		.resolve('/*/tree')
 | 
					
						
							|  |  |  | 	// 			-> ['System/tree', 'Dir/tree', ...]
 | 
					
						
							| 
									
										
										
										
											2022-08-03 01:35:19 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	// XXX should this be used by .get(..) instead of .match(..)???
 | 
					
						
							|  |  |  | 	// XXX EXPERIMENTAL 
 | 
					
						
							|  |  |  | 	resolve: async function(path, strict){ | 
					
						
							|  |  |  | 		// pattern match * / **
 | 
					
						
							|  |  |  | 		if(path.includes('*')  | 
					
						
							|  |  |  | 				|| path.includes('**')){ | 
					
						
							| 
									
										
										
										
											2022-09-07 16:37:26 +03:00
										 |  |  | 			var p = pwpath.splitArgs(path) | 
					
						
							| 
									
										
										
										
											2022-09-10 02:26:10 +03:00
										 |  |  | 			var all = p.args.all | 
					
						
							| 
									
										
										
										
											2022-09-07 16:37:26 +03:00
										 |  |  | 			var args = pwpath.joinArgs('', p.args) | 
					
						
							|  |  |  | 			p = pwpath.split(p.path) | 
					
						
							| 
									
										
										
										
											2022-09-02 17:27:40 +03:00
										 |  |  | 			var tail = [] | 
					
						
							|  |  |  | 			while(!p.at(-1).includes('*')){ | 
					
						
							|  |  |  | 				tail.unshift(p.pop()) } | 
					
						
							|  |  |  | 			tail = tail.join('/') | 
					
						
							|  |  |  | 			if(tail.length > 0){ | 
					
						
							| 
									
										
										
										
											2022-09-10 02:26:10 +03:00
										 |  |  | 				return (await this.match( | 
					
						
							|  |  |  | 						p.join('/') + (all ? ':all' : ''),  | 
					
						
							|  |  |  | 						strict)) | 
					
						
							| 
									
										
										
										
											2022-08-03 01:35:19 +03:00
										 |  |  | 					.map(function(p){ | 
					
						
							| 
									
										
										
										
											2022-09-10 02:26:10 +03:00
										 |  |  | 						all && | 
					
						
							|  |  |  | 							(p = p.replace(/:all/, '')) | 
					
						
							| 
									
										
										
										
											2022-09-07 16:37:26 +03:00
										 |  |  | 						return pwpath.join(p, tail) + args }) } } | 
					
						
							| 
									
										
										
										
											2022-08-03 01:35:19 +03:00
										 |  |  | 		// direct...
 | 
					
						
							|  |  |  | 		return this.match(path, strict) }, | 
					
						
							|  |  |  | 	// 
 | 
					
						
							|  |  |  | 	// 	Resolve page
 | 
					
						
							|  |  |  | 	// 	.get(<path>)
 | 
					
						
							|  |  |  | 	// 		-> <value>
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 	Resolve pages (non-strict mode)
 | 
					
						
							|  |  |  | 	// 	.get(<pattern>)
 | 
					
						
							|  |  |  | 	// 	.get(<pattern>, false)
 | 
					
						
							|  |  |  | 	// 		-> [<value>, .. ]
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 	Get pages (strict mode)
 | 
					
						
							|  |  |  | 	// 	.get(<pattern>, true)
 | 
					
						
							|  |  |  | 	// 		-> [<value>, .. ]
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// In strict mode this will not try to resolve pages and will not 
 | 
					
						
							|  |  |  | 	// return pages at paths that do not explicitly exist.
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// XXX should this call actions???
 | 
					
						
							|  |  |  | 	// XXX should this return a map for pattern matches???
 | 
					
						
							|  |  |  | 	__get__: async function(key){ | 
					
						
							|  |  |  | 		return this.data[key] }, | 
					
						
							| 
									
										
										
										
											2022-08-30 10:58:04 +03:00
										 |  |  | 	get: async function(path, strict=false, energetic=false){ | 
					
						
							| 
									
										
										
										
											2022-08-03 01:35:19 +03:00
										 |  |  | 		var that = this | 
					
						
							| 
									
										
										
										
											2022-09-04 11:06:19 +03:00
										 |  |  | 		path = pwpath.sanitize(path, 'string') | 
					
						
							| 
									
										
										
										
											2022-09-07 16:37:26 +03:00
										 |  |  | 		var path = pwpath.splitArgs(path).path | 
					
						
							| 
									
										
										
										
											2022-08-29 18:22:35 +03:00
										 |  |  | 		path = path.includes('*')  | 
					
						
							| 
									
										
										
										
											2022-08-30 10:58:04 +03:00
										 |  |  | 			&& (energetic == true ? | 
					
						
							|  |  |  | 				await this.find(path) | 
					
						
							|  |  |  | 				: await this.isEnergetic(path)) | 
					
						
							| 
									
										
										
										
											2022-08-29 18:22:35 +03:00
										 |  |  | 			|| await this.resolve(path, strict) | 
					
						
							|  |  |  | 		//*/
 | 
					
						
							| 
									
										
										
										
											2022-08-03 01:35:19 +03:00
										 |  |  | 		return path instanceof Array ? | 
					
						
							|  |  |  | 			// XXX should we return matched paths???
 | 
					
						
							|  |  |  |    			Promise.iter(path) | 
					
						
							|  |  |  | 				.map(function(p){ | 
					
						
							|  |  |  | 					// NOTE: p can match a non existing page at this point, 
 | 
					
						
							|  |  |  | 					// 		this can be the result of matching a/* in a a/b/c
 | 
					
						
							|  |  |  | 					// 		and returning a a/b which can be undefined...
 | 
					
						
							| 
									
										
										
										
											2022-08-28 10:02:58 +03:00
										 |  |  | 					return that.get(p, strict) }) | 
					
						
							| 
									
										
										
										
											2022-08-03 01:35:19 +03:00
										 |  |  | 			: (await this.__get__(path)  | 
					
						
							|  |  |  | 				?? ((this.next || {}).__get__  | 
					
						
							| 
									
										
										
										
											2022-08-28 10:02:58 +03:00
										 |  |  | 					&& this.next.get(path, strict))) }, | 
					
						
							| 
									
										
										
										
											2022-08-03 01:35:19 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-29 18:22:35 +03:00
										 |  |  | 	// XXX EXPERIMENTAL...
 | 
					
						
							|  |  |  | 	isEnergetic: async function(path){ | 
					
						
							|  |  |  | 		var p = await this.find(path) | 
					
						
							|  |  |  | 		return !!(await this.get(p, true) ?? {}).energetic  | 
					
						
							|  |  |  | 			&& p }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-03 01:35:19 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	// 	Get metadata...
 | 
					
						
							|  |  |  | 	// 	.metadata(<path>)
 | 
					
						
							|  |  |  | 	// 		-> <metadata>
 | 
					
						
							|  |  |  | 	// 		-> undefined 
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//	Set metadata...
 | 
					
						
							|  |  |  | 	//	.metadata(<path>, <data>[, <mode>])
 | 
					
						
							|  |  |  | 	//	.update(<path>, <data>[, <mode>])
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//	Delete metadata...
 | 
					
						
							|  |  |  | 	//	.delete(<path>)
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// NOTE: .metadata(..) is the same as .data but supports pattern paths 
 | 
					
						
							|  |  |  | 	// 		and does not try to acquire a target page.
 | 
					
						
							|  |  |  | 	// NOTE: setting/removing metadata is done via .update(..) / .delete(..)
 | 
					
						
							|  |  |  | 	// NOTE: this uses .__get__(..) internally...
 | 
					
						
							|  |  |  | 	metadata: async function(path, ...args){ | 
					
						
							| 
									
										
										
										
											2022-09-07 16:37:26 +03:00
										 |  |  | 		path = pwpath.splitArgs(path).path | 
					
						
							| 
									
										
										
										
											2022-08-03 01:35:19 +03:00
										 |  |  | 		// set...
 | 
					
						
							|  |  |  | 		if(args.length > 0){ | 
					
						
							|  |  |  | 			return this.update(path, ...args) } | 
					
						
							|  |  |  | 		// get...
 | 
					
						
							|  |  |  | 		path = await this.exists(path) | 
					
						
							|  |  |  | 		return path  | 
					
						
							| 
									
										
										
										
											2022-08-28 10:02:58 +03:00
										 |  |  | 			&& (await this.__get__(path)  | 
					
						
							| 
									
										
										
										
											2022-09-02 17:27:40 +03:00
										 |  |  | 				?? (this.next  | 
					
						
							|  |  |  | 					&& await this.next.metadata(path))) | 
					
						
							| 
									
										
										
										
											2022-08-03 01:35:19 +03:00
										 |  |  | 			|| undefined }, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// NOTE: deleting and updating only applies to explicit matching 
 | 
					
						
							|  |  |  | 	// 		paths -- no page acquisition is performed...
 | 
					
						
							|  |  |  | 	// NOTE: edit methods are local-only...
 | 
					
						
							|  |  |  | 	// NOTE: if .__update__ and .__delete__ are set to null/false this 
 | 
					
						
							|  |  |  | 	// 		will quietly go into read-only mode...
 | 
					
						
							|  |  |  | 	// XXX do we copy the data here or modify it????
 | 
					
						
							|  |  |  | 	__update__: async function(key, data, mode='update'){ | 
					
						
							|  |  |  | 		this.data[key] = data }, | 
					
						
							|  |  |  | 	update: async function(path, data, mode='update'){ | 
					
						
							|  |  |  | 		// read-only...
 | 
					
						
							|  |  |  | 		if(this.__update__ == null){ | 
					
						
							|  |  |  | 			return this } | 
					
						
							|  |  |  | 		var exists = await this.exists(path)  | 
					
						
							|  |  |  | 		path = exists | 
					
						
							| 
									
										
										
										
											2022-09-04 11:06:19 +03:00
										 |  |  | 			|| pwpath.sanitize(path, 'string') | 
					
						
							| 
									
										
										
										
											2022-09-07 16:37:26 +03:00
										 |  |  | 		path = pwpath.splitArgs(path).path | 
					
						
							| 
									
										
										
										
											2022-08-03 01:35:19 +03:00
										 |  |  | 		data = data instanceof Promise ? | 
					
						
							|  |  |  | 			await data | 
					
						
							|  |  |  | 			: data | 
					
						
							|  |  |  | 		data =  | 
					
						
							|  |  |  | 			typeof(data) == 'function' ? | 
					
						
							|  |  |  | 				data | 
					
						
							|  |  |  | 				: Object.assign( | 
					
						
							|  |  |  | 					{ | 
					
						
							|  |  |  | 						__proto__: data.__proto__, | 
					
						
							|  |  |  | 						ctime: Date.now(), | 
					
						
							|  |  |  | 					}, | 
					
						
							|  |  |  | 					(mode == 'update' && exists) ? | 
					
						
							| 
									
										
										
										
											2022-08-24 23:13:24 +03:00
										 |  |  | 						await this.__get__(path) | 
					
						
							| 
									
										
										
										
											2022-08-03 01:35:19 +03:00
										 |  |  | 						: {}, | 
					
						
							|  |  |  | 					data, | 
					
						
							|  |  |  | 					{mtime: Date.now()}) | 
					
						
							|  |  |  | 		await this.__update__(path, data, mode) | 
					
						
							| 
									
										
										
										
											2022-08-24 23:13:24 +03:00
										 |  |  | 		// XXX CACHED
 | 
					
						
							|  |  |  | 		this.__cache_add(path) | 
					
						
							| 
									
										
										
										
											2022-09-20 01:00:44 +03:00
										 |  |  | 		this.onUpdate(path) | 
					
						
							| 
									
										
										
										
											2022-08-03 01:35:19 +03:00
										 |  |  | 		return this }, | 
					
						
							|  |  |  | 	__delete__: async function(path){ | 
					
						
							|  |  |  | 		delete this.data[path] }, | 
					
						
							|  |  |  | 	delete: async function(path){ | 
					
						
							|  |  |  | 		// read-only...
 | 
					
						
							|  |  |  | 		if(this.__delete__ == null){ | 
					
						
							|  |  |  | 			return this } | 
					
						
							| 
									
										
										
										
											2022-09-07 16:37:26 +03:00
										 |  |  | 		path = pwpath.splitArgs(path).path | 
					
						
							| 
									
										
										
										
											2022-08-03 01:35:19 +03:00
										 |  |  | 		path = await this.exists(path) | 
					
						
							| 
									
										
										
										
											2022-10-10 18:08:02 +03:00
										 |  |  | 		if(typeof(path) == 'string'){ | 
					
						
							| 
									
										
										
										
											2022-08-24 23:13:24 +03:00
										 |  |  | 			await this.__delete__(path) | 
					
						
							|  |  |  | 			// XXX CACHED
 | 
					
						
							| 
									
										
										
										
											2022-09-20 01:00:44 +03:00
										 |  |  | 			this.__cache_remove(path)  | 
					
						
							|  |  |  | 			this.onDelete(path) } | 
					
						
							| 
									
										
										
										
											2022-08-03 01:35:19 +03:00
										 |  |  | 		return this }, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// XXX NEXT might be a good idea to have an API to move pages from 
 | 
					
						
							|  |  |  | 	// 		current store up the chain...
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// load/json protocol...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// The .load(..) / .json(..) methods have two levels of implementation:
 | 
					
						
							|  |  |  | 	// 	- generic
 | 
					
						
							|  |  |  | 	// 		uses .update(..) and .paths()/.get(..) and is usable as-is
 | 
					
						
							|  |  |  | 	// 		in any store adapter implementing the base protocol.
 | 
					
						
							|  |  |  | 	// 	- batch
 | 
					
						
							|  |  |  | 	// 		implemented via .__batch_load__(..) and .__batch_json__(..) 
 | 
					
						
							|  |  |  | 	// 		methods and can be adapter specific.
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// NOTE: the generic level does not care about the nested stores 
 | 
					
						
							|  |  |  | 	// 		and other details, as it uses the base API and will produce 
 | 
					
						
							|  |  |  | 	// 		full and generic result regardless of actual store topology.
 | 
					
						
							|  |  |  | 	// NOTE: implementations of the batch level need to handle nested 
 | 
					
						
							|  |  |  | 	// 		stores correctly.
 | 
					
						
							|  |  |  | 	// 		XXX not sure if we can avoid this at this stage...
 | 
					
						
							|  |  |  | 	// NOTE: care must be taken with inheriting the batch protocol methods
 | 
					
						
							|  |  |  | 	// 		as they take precedence over the generic protocol. It is 
 | 
					
						
							|  |  |  | 	// 		recommended to either overload them or simply assign null or
 | 
					
						
							|  |  |  | 	// 		undefined to them when inheriting from a non-base-store.
 | 
					
						
							|  |  |  | 	//__batch_load__: function(data){
 | 
					
						
							|  |  |  | 	//	// ...
 | 
					
						
							|  |  |  | 	//	return this }, 
 | 
					
						
							|  |  |  | 	load: async function(...data){ | 
					
						
							|  |  |  | 		var input = {} | 
					
						
							|  |  |  | 		for(var e of data){ | 
					
						
							|  |  |  | 			input = {...input, ...e} } | 
					
						
							|  |  |  | 		// batch loader (optional)...
 | 
					
						
							|  |  |  | 		if(this.__batch_load__){ | 
					
						
							|  |  |  | 			this.__batch_load__(input) | 
					
						
							|  |  |  | 		// one-by-one loader...
 | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			for(var [path, value] of Object.entries(input)){ | 
					
						
							|  |  |  | 				this.update(path, value) } } | 
					
						
							|  |  |  | 		return this }, | 
					
						
							| 
									
										
										
										
											2022-08-11 10:39:55 +03:00
										 |  |  | 	// NOTE: this will not serialize functions...
 | 
					
						
							| 
									
										
										
										
											2022-08-03 01:35:19 +03:00
										 |  |  | 	//__batch_json__: function(){
 | 
					
						
							|  |  |  | 	//	// ...
 | 
					
						
							|  |  |  | 	//	return json},
 | 
					
						
							| 
									
										
										
										
											2022-08-11 10:39:55 +03:00
										 |  |  | 	json: async function(options={}){ | 
					
						
							|  |  |  | 		if(options === true){ | 
					
						
							|  |  |  | 			options = {stringify: true} } | 
					
						
							|  |  |  | 		var {stringify, keep_funcs} = options | 
					
						
							| 
									
										
										
										
											2022-08-03 01:35:19 +03:00
										 |  |  | 		// batch...
 | 
					
						
							|  |  |  | 		if(this.__batch_json__){ | 
					
						
							| 
									
										
										
										
											2022-08-11 10:39:55 +03:00
										 |  |  | 			var res = this.__batch_json__(stringify) | 
					
						
							| 
									
										
										
										
											2022-08-03 01:35:19 +03:00
										 |  |  | 		// generic...
 | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			var res = {} | 
					
						
							|  |  |  | 			for(var path of await this.paths()){ | 
					
						
							| 
									
										
										
										
											2022-08-11 10:39:55 +03:00
										 |  |  | 				var page = await this.get(path)  | 
					
						
							|  |  |  | 				if(keep_funcs  | 
					
						
							|  |  |  | 						|| typeof(page) != 'function'){ | 
					
						
							|  |  |  | 					res[path] = page } } } | 
					
						
							|  |  |  | 		return (stringify  | 
					
						
							| 
									
										
										
										
											2022-08-03 01:35:19 +03:00
										 |  |  | 				&& typeof(res) != 'string') ? | 
					
						
							| 
									
										
										
										
											2022-08-15 14:29:45 +03:00
										 |  |  | 			JSON.stringify(res, options.replacer, options.space) | 
					
						
							| 
									
										
										
										
											2022-08-03 01:35:19 +03:00
										 |  |  | 			: res }, | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
 | 
					
						
							|  |  |  | // Meta-Store
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // Extends BaseStore to handle other stores as pages. i.e. sub-paths can 
 | 
					
						
							|  |  |  | // be handled by nested stores.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-28 10:02:58 +03:00
										 |  |  | // XXX see inside...
 | 
					
						
							| 
									
										
										
										
											2022-08-03 01:35:19 +03:00
										 |  |  | var metaProxy =  | 
					
						
							| 
									
										
										
										
											2022-08-24 23:13:24 +03:00
										 |  |  | function(name, pre, post){ | 
					
						
							| 
									
										
										
										
											2022-08-03 01:35:19 +03:00
										 |  |  | 	var func = async function(path, ...args){ | 
					
						
							| 
									
										
										
										
											2022-08-24 23:13:24 +03:00
										 |  |  | 		path = pre ? | 
					
						
							|  |  |  | 			await pre.call(this, path, ...args) | 
					
						
							|  |  |  | 			: path | 
					
						
							| 
									
										
										
										
											2022-08-03 01:35:19 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-28 10:02:58 +03:00
										 |  |  | 		var res | 
					
						
							| 
									
										
										
										
											2022-08-24 23:13:24 +03:00
										 |  |  | 		var p = this.substore(path) | 
					
						
							|  |  |  | 		if(p){ | 
					
						
							| 
									
										
										
										
											2022-08-28 10:02:58 +03:00
										 |  |  | 			// XXX can this be strict in all cases???
 | 
					
						
							| 
									
										
										
										
											2022-08-24 23:13:24 +03:00
										 |  |  | 			var res = this.substores[p][name]( | 
					
						
							|  |  |  | 				path.slice(path.indexOf(p)+p.length), | 
					
						
							| 
									
										
										
										
											2022-08-28 10:02:58 +03:00
										 |  |  | 				...args) } | 
					
						
							|  |  |  | 		res = res  | 
					
						
							|  |  |  | 			?? object.parentCall(MetaStore[name], this, ...arguments) | 
					
						
							| 
									
										
										
										
											2022-08-24 23:13:24 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		return post ? | 
					
						
							|  |  |  | 			post.call(this, await res, path, ...args) | 
					
						
							|  |  |  | 			: res } | 
					
						
							|  |  |  | 	Object.defineProperty(func, 'name', {value: name}) | 
					
						
							|  |  |  | 	return func } | 
					
						
							| 
									
										
										
										
											2022-08-03 01:35:19 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | // XXX not sure about the name...
 | 
					
						
							|  |  |  | // XXX should this be a mixin???
 | 
					
						
							|  |  |  | var MetaStore = | 
					
						
							|  |  |  | module.MetaStore = { | 
					
						
							|  |  |  | 	__proto__: BaseStore, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-24 23:13:24 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	// Format:
 | 
					
						
							|  |  |  | 	// 	{
 | 
					
						
							|  |  |  | 	// 		<path>: <store>,
 | 
					
						
							|  |  |  | 	// 		...
 | 
					
						
							|  |  |  | 	// 	}
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	substores: undefined, | 
					
						
							| 
									
										
										
										
											2022-08-03 01:35:19 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	substore: function(path){ | 
					
						
							| 
									
										
										
										
											2022-10-16 12:31:19 +03:00
										 |  |  | 		path = pwpath.sanitize(path, 'string') | 
					
						
							| 
									
										
										
										
											2022-08-24 23:13:24 +03:00
										 |  |  | 		if(path in (this.substores ?? {})){ | 
					
						
							| 
									
										
										
										
											2022-08-03 01:35:19 +03:00
										 |  |  | 			return path } | 
					
						
							| 
									
										
										
										
											2022-08-24 23:13:24 +03:00
										 |  |  | 		var store = Object.keys(this.substores ?? {}) | 
					
						
							| 
									
										
										
										
											2022-08-21 23:07:47 +03:00
										 |  |  | 			// normalize store paths to the given path...
 | 
					
						
							| 
									
										
										
										
											2022-08-03 01:35:19 +03:00
										 |  |  | 			.filter(function(p){ | 
					
						
							| 
									
										
										
										
											2022-08-27 14:34:08 +03:00
										 |  |  | 				return path.startsWith(p) | 
					
						
							| 
									
										
										
										
											2022-10-16 12:31:19 +03:00
										 |  |  | 					// only keep whole path elements...
 | 
					
						
							|  |  |  | 					// NOTE: this prevents matching 'a/b' with 'a/bbb', for example.
 | 
					
						
							| 
									
										
										
										
											2022-08-27 14:34:08 +03:00
										 |  |  | 		   			&& (path[p.length] == null | 
					
						
							|  |  |  | 						|| path[p.length] == '/' | 
					
						
							|  |  |  | 						|| path[p.length] == '\\')}) | 
					
						
							| 
									
										
										
										
											2022-08-03 01:35:19 +03:00
										 |  |  | 			.sort(function(a, b){ | 
					
						
							|  |  |  | 				return a.length - b.length }) | 
					
						
							|  |  |  | 			.pop()  | 
					
						
							|  |  |  | 		return store == path ? | 
					
						
							|  |  |  | 			// the actual store is not stored within itself...
 | 
					
						
							|  |  |  | 			undefined | 
					
						
							|  |  |  | 			: store }, | 
					
						
							|  |  |  | 	getstore: function(path){ | 
					
						
							| 
									
										
										
										
											2022-08-24 23:13:24 +03:00
										 |  |  | 		return (this.substores ?? {})[this.substore(path)] }, | 
					
						
							| 
									
										
										
										
											2022-08-21 23:07:47 +03:00
										 |  |  | 	isStore: function(path){ | 
					
						
							| 
									
										
										
										
											2022-08-24 23:13:24 +03:00
										 |  |  | 		if(!this.substores){ | 
					
						
							|  |  |  | 			return false } | 
					
						
							| 
									
										
										
										
											2022-10-16 12:31:19 +03:00
										 |  |  | 		path = pwpath.sanitize(path, 'string') | 
					
						
							|  |  |  | 		// XXX do we need this???
 | 
					
						
							| 
									
										
										
										
											2022-08-24 23:13:24 +03:00
										 |  |  | 		return !!this.substores[path] | 
					
						
							|  |  |  | 			|| !!this.substores['/'+ path] }, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// NOTE: we are using level2 API here to enable mixing this with 
 | 
					
						
							|  |  |  | 	// 		store adapters that can overload the level1 API to implement 
 | 
					
						
							|  |  |  | 	// 		their own stuff...
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	paths: async function(){ | 
					
						
							| 
									
										
										
										
											2022-08-03 01:35:19 +03:00
										 |  |  | 		var that = this | 
					
						
							| 
									
										
										
										
											2022-08-24 23:13:24 +03:00
										 |  |  | 		var stores = await Promise.iter( | 
					
						
							|  |  |  | 				Object.entries(this.substores ?? {}) | 
					
						
							|  |  |  | 					.map(function([path, store]){ | 
					
						
							|  |  |  | 						return store.paths() | 
					
						
							|  |  |  | 								.iter() | 
					
						
							|  |  |  | 								.map(function(s){ | 
					
						
							|  |  |  | 									return pwpath.join(path, s) }) })) | 
					
						
							|  |  |  | 			.flat() | 
					
						
							|  |  |  | 		return object.parentCall(MetaStore.paths, this, ...arguments) | 
					
						
							|  |  |  | 			.iter() | 
					
						
							|  |  |  | 			.concat(stores) }, | 
					
						
							| 
									
										
										
										
											2022-08-03 01:35:19 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-24 23:13:24 +03:00
										 |  |  | 	exists: metaProxy('exists', | 
					
						
							|  |  |  | 		//async function(path){
 | 
					
						
							|  |  |  | 		//	return this.resolve(path) },
 | 
					
						
							|  |  |  | 		null, | 
					
						
							|  |  |  | 		function(res, path){ | 
					
						
							|  |  |  | 			var s = this.substore(path) | 
					
						
							| 
									
										
										
										
											2022-10-10 18:08:02 +03:00
										 |  |  | 			return typeof(res) != 'string' ? | 
					
						
							| 
									
										
										
										
											2022-08-28 10:02:58 +03:00
										 |  |  | 					(this.next ? | 
					
						
							|  |  |  | 						this.next.exists(path) | 
					
						
							|  |  |  | 						: res) | 
					
						
							|  |  |  | 					//res
 | 
					
						
							| 
									
										
										
										
											2022-08-24 23:13:24 +03:00
										 |  |  | 				: s ? | 
					
						
							|  |  |  | 					pwpath.join(s, res) | 
					
						
							|  |  |  | 				: res }),  | 
					
						
							| 
									
										
										
										
											2022-08-28 10:02:58 +03:00
										 |  |  | 	get: async function(path, strict=false){ | 
					
						
							| 
									
										
										
										
											2022-08-28 16:34:46 +03:00
										 |  |  | 		path = await this.resolve(path, strict)  | 
					
						
							|  |  |  | 		if(path == undefined){ | 
					
						
							|  |  |  | 			return } | 
					
						
							| 
									
										
										
										
											2022-08-28 10:02:58 +03:00
										 |  |  | 		var res | 
					
						
							|  |  |  | 		var p = this.substore(path) | 
					
						
							|  |  |  | 		if(p){ | 
					
						
							|  |  |  | 			res = await this.substores[p].get( | 
					
						
							|  |  |  | 				path.slice(path.indexOf(p)+p.length), | 
					
						
							|  |  |  | 				true) } | 
					
						
							|  |  |  | 		return res  | 
					
						
							|  |  |  | 			?? object.parentCall(MetaStore.get, this, ...arguments) }, | 
					
						
							|  |  |  | 	// XXX can't reach .next on get but will cheerfully mess things up 
 | 
					
						
							|  |  |  | 	// 		on set (creating a local page)...
 | 
					
						
							|  |  |  | 	// 		...should copy and merge...
 | 
					
						
							| 
									
										
										
										
											2022-08-24 23:13:24 +03:00
										 |  |  | 	metadata: metaProxy('metadata'), | 
					
						
							| 
									
										
										
										
											2022-08-28 10:02:58 +03:00
										 |  |  | 	// NOTE: we intentionally do not delegate to .next here...
 | 
					
						
							| 
									
										
										
										
											2022-08-24 23:13:24 +03:00
										 |  |  | 	update: async function(path, data, mode='update'){ | 
					
						
							|  |  |  | 		data = data instanceof Promise ? | 
					
						
							|  |  |  | 			await data | 
					
						
							|  |  |  | 			: data | 
					
						
							|  |  |  | 		// add substore...
 | 
					
						
							|  |  |  | 		if(object.childOf(data, BaseStore)){ | 
					
						
							| 
									
										
										
										
											2022-10-15 13:42:51 +03:00
										 |  |  | 			path = pwpath.sanitize(path, 'string') | 
					
						
							| 
									
										
										
										
											2022-08-24 23:13:24 +03:00
										 |  |  | 			;(this.substores = this.substores ?? {})[path] = data | 
					
						
							|  |  |  | 			return this } | 
					
						
							|  |  |  | 		// add to substore...
 | 
					
						
							|  |  |  | 		var p = this.substore(path) | 
					
						
							|  |  |  | 		if(p){ | 
					
						
							|  |  |  | 			this.substores[p].update( | 
					
						
							|  |  |  | 				// trim path...
 | 
					
						
							|  |  |  | 				path.slice(path.indexOf(p)+p.length), | 
					
						
							|  |  |  | 				...[...arguments].slice(1)) | 
					
						
							| 
									
										
										
										
											2022-10-19 19:00:20 +03:00
										 |  |  | 			this.__cache_add(path) | 
					
						
							| 
									
										
										
										
											2022-08-24 23:13:24 +03:00
										 |  |  | 			return this } | 
					
						
							|  |  |  | 		// add local...
 | 
					
						
							|  |  |  | 		return object.parentCall(MetaStore.update, this, ...arguments) }, | 
					
						
							|  |  |  | 	// XXX Q: how do we delete a substore???
 | 
					
						
							| 
									
										
										
										
											2022-10-19 19:00:20 +03:00
										 |  |  | 	// XXX need to call .__cache_remove(..) here if we did not super-call...
 | 
					
						
							| 
									
										
										
										
											2022-08-24 23:13:24 +03:00
										 |  |  | 	delete: metaProxy('delete'),  | 
					
						
							| 
									
										
										
										
											2022-08-03 01:35:19 +03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-24 23:13:24 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-03 01:35:19 +03:00
										 |  |  | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-02 14:58:45 +03:00
										 |  |  | // XXX not used...
 | 
					
						
							|  |  |  | var cacheProxy = function(name){ | 
					
						
							|  |  |  | 	var func = function(path, ...args){ | 
					
						
							|  |  |  | 		var cache = (this.root ?? this).cache | 
					
						
							|  |  |  | 		return cache[path]  | 
					
						
							|  |  |  | 			?? (cache[path] =  | 
					
						
							|  |  |  | 				object.parentCall(CachedStore[name], this, ...arguments)) } | 
					
						
							|  |  |  | 	Object.defineProperty(func, 'name', {value: name}) | 
					
						
							|  |  |  | 	return func } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-01 01:14:34 +03:00
										 |  |  | // XXX should this be a level-1 or level-2???
 | 
					
						
							| 
									
										
										
										
											2022-08-03 01:35:19 +03:00
										 |  |  | // XXX make this a mixin...
 | 
					
						
							|  |  |  | // XXX add cache invalidation strategies...
 | 
					
						
							|  |  |  | // 		- timeout
 | 
					
						
							|  |  |  | // 		- count
 | 
					
						
							| 
									
										
										
										
											2022-10-14 16:27:19 +03:00
										 |  |  | // XXX BROKEN...
 | 
					
						
							| 
									
										
										
										
											2022-09-02 14:58:45 +03:00
										 |  |  | var CachedStore = | 
					
						
							|  |  |  | module.CachedStore = { | 
					
						
							|  |  |  | 	__proto__: MetaStore, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	__cache: undefined, | 
					
						
							|  |  |  | 	get cache(){ | 
					
						
							|  |  |  | 		return (this.__cache = this.__cache ?? {}) }, | 
					
						
							|  |  |  | 	set cache(value){ | 
					
						
							| 
									
										
										
										
											2022-09-02 22:09:51 +03:00
										 |  |  | 		this.__cache = value }, | 
					
						
							| 
									
										
										
										
											2022-08-03 01:35:19 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-02 14:58:45 +03:00
										 |  |  | 	clearCache: function(){ | 
					
						
							|  |  |  | 		this.cache = {}  | 
					
						
							| 
									
										
										
										
											2022-08-03 01:35:19 +03:00
										 |  |  | 		return this }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-02 14:58:45 +03:00
										 |  |  | 	exists: async function(path){ | 
					
						
							| 
									
										
										
										
											2022-09-03 10:57:54 +03:00
										 |  |  | 		return (path in this.cache ? | 
					
						
							|  |  |  | 				path | 
					
						
							|  |  |  | 				: false) | 
					
						
							|  |  |  | 			|| object.parentCall(CachedStore.exists, this, ...arguments) }, | 
					
						
							| 
									
										
										
										
											2022-09-02 14:58:45 +03:00
										 |  |  | 	// XXX this sometimes caches promises...
 | 
					
						
							|  |  |  | 	get: async function(path){ | 
					
						
							|  |  |  | 		return this.cache[path]  | 
					
						
							|  |  |  | 			?? (this.cache[path] =  | 
					
						
							|  |  |  | 				await object.parentCall(CachedStore.get, this, ...arguments)) }, | 
					
						
							|  |  |  | 	update: async function(path, data){ | 
					
						
							| 
									
										
										
										
											2022-09-02 22:09:51 +03:00
										 |  |  | 		var that = this | 
					
						
							| 
									
										
										
										
											2022-09-03 10:57:54 +03:00
										 |  |  | 		delete this.cache[path] | 
					
						
							| 
									
										
										
										
											2022-09-02 22:09:51 +03:00
										 |  |  | 		var res = object.parentCall(CachedStore.update, this, ...arguments)  | 
					
						
							| 
									
										
										
										
											2022-09-03 10:57:54 +03:00
										 |  |  | 		// re-cache in the background...
 | 
					
						
							| 
									
										
										
										
											2022-09-02 22:09:51 +03:00
										 |  |  | 		res.then(async function(){ | 
					
						
							|  |  |  | 			that.cache[path] = await that.get(path) }) | 
					
						
							|  |  |  | 		return res }, | 
					
						
							| 
									
										
										
										
											2022-09-02 14:58:45 +03:00
										 |  |  | 	/* XXX | 
					
						
							|  |  |  | 	metadata: async function(path, data){ | 
					
						
							|  |  |  | 		if(data){ | 
					
						
							|  |  |  | 			// XXX this is wrong -- get merged data...
 | 
					
						
							|  |  |  | 			this.cache[path] = data | 
					
						
							|  |  |  | 		 	return object.parentCall(CachedStore.metadata, this, ...arguments)  | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			return this.cache[path]  | 
					
						
							|  |  |  | 				?? (this.cache[path] =  | 
					
						
							|  |  |  | 					await object.parentCall(CachedStore.metadata, this, ...arguments)) } }, | 
					
						
							|  |  |  | 	//*/
 | 
					
						
							|  |  |  | 	delete: async function(path){ | 
					
						
							|  |  |  | 		delete this.cache[path] | 
					
						
							|  |  |  | 		return object.parentCall(CachedStore.delete, this, ...arguments) }, | 
					
						
							| 
									
										
										
										
											2022-08-03 01:35:19 +03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-01 01:14:34 +03:00
										 |  |  | //---------------------------------------------------------------------
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var Store = | 
					
						
							|  |  |  | module.Store = | 
					
						
							| 
									
										
										
										
											2022-10-10 18:08:02 +03:00
										 |  |  | 	MetaStore | 
					
						
							|  |  |  | 	//CachedStore
 | 
					
						
							| 
									
										
										
										
											2022-09-01 01:14:34 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-03 01:35:19 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | /********************************************************************** | 
					
						
							|  |  |  | * vim:set ts=4 sw=4 :                               */ return module }) |