lycabinet
Version:
A simple small JSON Object storage helper with good performance.
274 lines (253 loc) • 8.29 kB
text/typescript
/**
* Utils.js.
* By lozyue.
*/
/**
* Get the item in an array with index. Support negative index.
* @param {...any} objs
*/
export const arrayIndex = function (arr, index) {
index = (arr.length + index) % arr.length;
if (arr[index] === undefined) {
DEBUG&& console.error(`The index ${index} in array ${arr.toString()} is overflowed!`);
}
return arr[index];
}
/**
* Get the target curve path value of the source Object.
* The curve path is a sequenced array
* @param source
* @param objPathes
*/
export function curveGet(source: Object, objPathes: string[]){
let interim = source, item = '';
for(let index=0; index<objPathes.length; index++){
item = objPathes[index];
interim = interim && interim[item]
if(interim === void 0 ){
return void 0;
};
}
return interim;
}
/**
* Set the consistent even curve path of the source Object
* The curve path is a sequenced array // dot split strings.
* @param source
* @param objPathes
* @param {unknown|Function} value The value assign for the curve object target. Support callback that if target value is a function you should set it in call back.
* @returns { number|true } The number indicator the failed position of the conflict path.
*/
export function curveSet(source: Object, objPathes: string[], value: ((target: Object, name: string)=>any)| unknown= null){
let interim = source, item = '';
// not the last one.
let index=0;
for(; index<objPathes.length-1; index++){
item = objPathes[index];
if(is_Defined(interim[item]) ){
if(is_Object(interim[item])){
interim = interim[item];
} else {
// Unexpected non-object value.
return index;
}
} else
interim = interim[item] = {};
};
// the last
item = objPathes[index];
// assign the value.
if(is_Function(value))
(value as Function)(interim, item);
else
interim[item] = value;
return true;
}
/**
* Centralized management.
* Add a listener to window storage event.
* @param { Function } invoke Target invoke function or handle.
* @param { Boolean } remove wheather the action is to remove added storage listener.
*/
export const addStoreListener = (()=>{
const invokeQueue: Function[]= [];
window.addEventListener("storage", (eve)=>{
invokeQueue.forEach(func=>{
func(eve);
});
}, false); // default bubble.
return (invoke, remove = false)=>{
if(remove)
removeArrayItem(invokeQueue, invoke);
else invokeQueue.push(invoke);
}
})();
/**
* Deep Object.assign source to target.
* @param { null|Object, Object... }...objs
*/
export const deepAssign = function (...objs) {
let merged;
objs.reduce((target, source) => {
for (let item in source) {
if (!(target[item] && is_PlainObject(target[item])) ) {
target[item] = source[item];
} else {
deepAssign(target[item], source[item]);
}
}
merged = target;
return target;
}, objs[0]); // The third param is to set default value.
return merged;
}
/**
* Deep Object.assign source to target.
* @param target
* @param source
* @param condition Receive the to contacted source and target value and returns the custom result.
*/
export const deepConditionalAssign = function (source, target, condition:null|Function=null ) {
for (let item in target) {
if (!(source[item] && is_PlainObject(source[item])) ) {
// The source Item won't be PlainObject or FalseValue.
if(!condition)
source[item] = target[item];
else
source[item] = condition(source[item], target[item]);
} else {
deepConditionalAssign(source[item], target[item]);
}
}
return source;
}
/**
* Just assign the item in supplement which not defined in target.
* If you don't want to override the value of origin Object, supplement is the high performance choice.
* Not deep mode.
* @param {*} target
* @param {*} supplement
*/
export function objectSupplement(target, supplement) {
let current = null;
for (let item in supplement) {
current = target[item];
if (is_Defined(current))
continue;
target[item] = supplement[item];
}
return target;
}
/**
* Just assign the item in supplement which not defined in target.
* If you don't want to override the value of origin Object, supplement is the high performance choice.
* Deep mode by iterate each inner Object.
* @param {*} target
* @param {*} supplement
*/
export function deepSupplement<R extends Object, T extends Object> (target: R|null, supplement: T) {
if(!target) return supplement;
let current: unknown = null;
for (let item in supplement) {
current = (target as unknown as T)[item];
if (is_Defined(current)) {
if (!is_PlainObject(current as Object)) continue;
deepSupplement(current as Object, supplement[item]); // The `current` is a reference which could be assigned.
}
else
// current = supplement[item];
(target as unknown as T)[item] = supplement[item];
}
return target as (R & T);
}
/**
* Simple deepClone with optional Function clone
*/
export function deepClone(val, substituteObj = Object.create(null), cloneFunc = true) {
if (is_PlainObject(val)) {
var res = substituteObj;
for (var key in val) {
res[key] = deepClone(val[key]);
}
return res;
} else if (is_Array(val)) {
return val.slice()
} else if (cloneFunc && is_Function(val)) {
return Object.create(val.prototype).constructor;
} else {
return val;
}
}
export function iterateObject(source: Object, iterate: Function){
iterate(source);
for(let item in source){
if( is_PlainObject(source[item]) )
iterate(source, iterate);
}
}
export const is_Defined = (v: unknown):Boolean => (v !== undefined && v !== null);
export const is_Object = (obj: unknown):Boolean => (obj instanceof Object || typeof obj === "object");
export const is_PlainObject = (obj: unknown):Boolean => (Object.prototype.toString.call(obj) === '[object Object]');
export const is_Array = (obj: unknown):Boolean => (Array.isArray && Array.isArray(obj) || obj instanceof Array || (typeof obj === 'object') && Object.prototype.toString.call(obj).slice(-6,-1)=== 'Array' );
export const is_String = (str: Object):Boolean => ((typeof str === 'string') && str.constructor == String);
export const is_Function = (obj: unknown):Boolean => (obj instanceof Function);
export const is_Empty = (val: unknown)=>{
if(!val) return true;
if(is_Array(val)){
return !(val as Array<unknown>).length;
}else{
return !Object.keys((val) as Object).length;
}
}
/*
* Delete the Item in an Array, returning the new Array.
*/
export var removeArrayItem = (arr, item) => {
if (arr.length) {
let index = arr.indexOf(item);
if (index > -1) {
return arr.splice(index, 1);
}
}
}
/**
* Provide with a processor accept a list of stuff or single stuff
* Give it the action to its inner iterator.
* The original Stuff can not be an Array!
*/
export function arbitraryFree(input, func) {
if (input.forEach) {
return input.forEach(func);
} else {
return func(input, 0);
}
}
export function storageAvailable(type) {
var storage;
try {
storage = window[type];
var x = '__storage_test__';
storage.setItem(x, x);
storage.removeArrayItem(x);
return true;
}
catch (e) {
return e instanceof DOMException && (
// everything except Firefox
e.code === 22 ||
// Firefox
e.code === 1014 ||
// test name field too, because code might not be present
// everything except Firefox
e.name === 'QuotaExceededError' ||
// Firefox
e.name === 'NS_ERROR_DOM_QUOTA_REACHED') &&
// acknowledge QuotaExceededError only if there's something already stored
(storage && storage.length !== 0);
}
}
export const LogToken = "[Lycabinet]: ";
export const DEBUG = process.env.NODE_ENV !== 'production';
export const EnvAssociate = {
Light: false, // light mode.
};