boxed-immutable
Advanced tools
Comparing version 0.1.6 to 0.2.0
@@ -12,2 +12,5 @@ /** internal | ||
const isNumericInteger = util.isNumericInteger; | ||
const isArrayIndex = util.isArrayIndex; | ||
const toArrayIndex = util.toArrayIndex; | ||
const toNumberIfArrayIndex = util.toNumberIfArrayIndex; | ||
const unwrap = util.unwrap; | ||
@@ -32,54 +35,54 @@ const wrap = util.wrap; | ||
const RESERVED_PROPS = { | ||
// "@@BOXED_THIS": { | ||
// get: function () {return this;}, | ||
// set: function (value) {return false;}, | ||
// delete: function () {return false;}, | ||
// ownPropertyDescriptor: function () {return { value: this, /*writable: false, enumerable: false, configurable: false, get:UNDEFINED, set:UNDEFINED,*/ };}, | ||
// }, | ||
forEach: { | ||
get: function () {return this.boxedForEach;}, | ||
set: function (value) {return false;}, | ||
delete: function () {return false;}, | ||
ownPropertyDescriptor: function () {return { value: this.boxedForEach, /*writable: false, enumerable: false, configurable: false, get:UNDEFINED, set:UNDEFINED,*/ };}, | ||
forEachKey: { | ||
get: function (wrapped) {return wrapped ? this.forEachKey : this.forEachKeyBoxed;}, | ||
set: function (value, wrapped) {return false;}, | ||
delete: function (wrapped) {return false;}, | ||
ownPropertyDescriptor: function () {return { value: this.value, /*writable: false, enumerable: false, configurable: false, get:UNDEFINED, set:UNDEFINED,*/ };}, | ||
}, | ||
unboxed: { | ||
get: function () {return this.valueOf();}, | ||
set: function (value) {return this.setValueOf(value);}, | ||
delete: function () {return this.deleteValueOf();}, | ||
get: function (wrapped) {return this.valueOf();}, | ||
set: function (value, wrapped) {return this.setValueOf(value);}, | ||
delete: function (wrapped) {return this.deleteValueOf();}, | ||
ownPropertyDescriptor: function () {return { value: this.value, writable: true, /*enumerable: false, configurable: false, get:UNDEFINED, set:UNDEFINED,*/ };}, | ||
}, | ||
get: { | ||
get: function () {return this.getProp;}, | ||
set: function (value) {return false;}, | ||
delete: function () {return false;}, | ||
get: function (wrapped) {return this.getProp;}, | ||
set: function (value, wrapped) {return false;}, | ||
delete: function (wrapped) {return false;}, | ||
ownPropertyDescriptor: function () {return { value: this.getProp, /* writable: true, enumerable: false, configurable: false, get:UNDEFINED, set:UNDEFINED,*/ };}, | ||
}, | ||
default: { | ||
get: function () {return this.setDefaultValue;}, | ||
set: function (value) {return this.setDefaultValue(value);}, | ||
delete: function () {return false;}, | ||
get: function (wrapped) {return wrapped ? this.setValueOfDefaultBoxed : this.setValueOfDefault;}, | ||
set: function (value, wrapped) {return this.setValueOfDefaultMagic(value);}, | ||
delete: function (wrapped) {return false;}, | ||
ownPropertyDescriptor: function () {return { value: this.value, writable: true, /*enumerable: false, configurable: false, get:UNDEFINED, set:UNDEFINED,*/ };}, | ||
}, | ||
boolean: { | ||
get: function (wrapped) {return this.valueOfBoolean();}, | ||
set: function (value, wrapped) {return this.setValueOfBoolean(value);}, | ||
delete: function (wrapped) {return false;}, | ||
ownPropertyDescriptor: function () {return { value: this.valueOfBoolean(), writable: true, /*enumerable: false, configurable: false, get:UNDEFINED, set:UNDEFINED,*/ };}, | ||
}, | ||
"": { // $boxed with after stripping off prefix/suffix _$ | ||
get: function () {return this.proxiedThis;}, | ||
set: function (value) {return false;}, // this does not get here, intercepted in set, it is boxedValue[$] = ..., or boxedValue.$ = ..., ie. we treat it as append to end of array! | ||
delete: function () {return false;}, // this is delete boxedValue[$]; or delete boxedValue.$; in either case we don't have a meaning for this | ||
ownPropertyDescriptor: function () {return { value: this.boxedWith, /*writable: false, enumerable: false, configurable: false, get:UNDEFINED, set:UNDEFINED,*/ };}, | ||
get: function (wrapped) {return this.proxiedThis;}, | ||
set: function (value, wrapped) {return false;}, // this does not get here, intercepted in set, it is boxedValue[$] = ..., or boxedValue.$ = ..., ie. we treat it as append to end of array! | ||
delete: function (wrapped) {return false;}, // this is delete boxedValue[$]; or delete boxedValue.$; in either case we don't have a meaning for this | ||
ownPropertyDescriptor: function () {return { value: this.proxiedThis, /*writable: false, enumerable: false, configurable: false, get:UNDEFINED, set:UNDEFINED,*/ };}, | ||
}, | ||
modified: { | ||
get: function () {return this.valueOfModified();}, | ||
set: function (value) {return this.setValueOfModified(value);}, | ||
delete: function () {return this.deleteValueOfModified();}, | ||
get: function (wrapped) {return this.valueOfModified();}, | ||
set: function (value, wrapped) {return this.setValueOfModified(value);}, | ||
delete: function (wrapped) {return this.deleteValueOfModified();}, | ||
ownPropertyDescriptor: function () {return { value: this.value, /*writable: false, enumerable: false, configurable: false, get:UNDEFINED, set:UNDEFINED,*/ };}, | ||
}, | ||
delta: { | ||
get: function () {return this.unboxedDelta();}, | ||
set: function (value) {return this.setUnboxedDelta(value);}, | ||
delete: function () {return this.deleteUnboxedDelta();}, | ||
get: function (wrapped) {return this.unboxedDelta();}, | ||
set: function (value, wrapped) {return this.setUnboxedDelta(value);}, | ||
delete: function (wrapped) {return this.deleteUnboxedDelta();}, | ||
ownPropertyDescriptor: function () {return { value: this.value, /*writable: false, enumerable: false, configurable: false, get:UNDEFINED, set:UNDEFINED,*/ };}, | ||
}, | ||
deepDelta: { | ||
get: function () {return this.unboxedDeepDelta();}, | ||
set: function (value) {return this.setUnboxedDeepDelta(value);}, | ||
delete: function () {return this.deleteUnboxedDeepDelta();}, | ||
get: function (wrapped) {return this.unboxedDeepDelta();}, | ||
set: function (value, wrapped) {return this.setUnboxedDeepDelta(value);}, | ||
delete: function (wrapped) {return this.deleteUnboxedDeepDelta();}, | ||
ownPropertyDescriptor: function () {return { value: this.value, /*writable: false, enumerable: false, configurable: false, get:UNDEFINED, set:UNDEFINED,*/ };}, | ||
@@ -108,3 +111,3 @@ }, | ||
} else { | ||
this.globalBox = undefined; // will be set by box | ||
this.globalBox = UNDEFINED; // will be set by box | ||
} | ||
@@ -117,15 +120,15 @@ | ||
this.arrayDeltaPartials = firstDefined(options.arrayDeltaPartials, false); | ||
this.arrayDeltaObjectMarker = firstDefined(options.arrayDeltaObjectMarker, undefined); | ||
this.arrayDeltaObjectMarkerValue = firstDefined(options.arrayDeltaObjectMarkerValue, undefined); | ||
this.arrayDeltaObjectMarker = firstDefined(options.arrayDeltaObjectMarker, UNDEFINED); | ||
this.arrayDeltaObjectMarkerValue = firstDefined(options.arrayDeltaObjectMarkerValue, UNDEFINED); | ||
this.arrayDeepDeltaObjects = firstDefined(options.arrayDeepDeltaObjects, false); | ||
this.arrayDeepDeltaObjectMarker = firstDefined(options.arrayDeepDeltaObjectMarker, undefined); | ||
this.arrayDeepDeltaObjectMarkerValue = firstDefined(options.arrayDeepDeltaObjectMarkerValue, undefined); | ||
this.arrayDeepDeltaPartials = firstDefined(options.arrayDeepDeltaPartials, false); | ||
this.arrayDeepDeltaObjectMarker = firstDefined(options.arrayDeepDeltaObjectMarker, UNDEFINED); | ||
this.arrayDeepDeltaObjectMarkerValue = firstDefined(options.arrayDeepDeltaObjectMarkerValue, UNDEFINED); | ||
this.arrayDeepDeltaPartials = firstDefined(options.arrayDeepDeltaPartials, true); | ||
this.prefixChars = (options.prefixChars || BOXED_PREFIX); | ||
this.suffixChars = (options.suffixChars || BOXED_SUFFIX); | ||
this.extraPrefixChars = (options.extraPrefixChars || ""); | ||
this.extraSuffixChars = (options.extraSuffixChars || "$"); | ||
this.magicPrefixChars = (options.magicPrefixChars || ""); | ||
this.magicSuffixChars = (options.magicSuffixChars || "$"); | ||
this.boxedArrayEnd = this.wrapProp(BOXED_ARRAY_END); | ||
this.magicParamsKey = this.wrapMagicProp("|"); | ||
this.boxedParamsKey = this.wrapProp("|" + this.magicParamsKey + "|"); | ||
// this.boxedParamsKey = this.wrapProp("|" + this.magicParamsKey + "|"); | ||
@@ -144,12 +147,13 @@ if (!util.hasOwnProperty(wrappedProps, this.magicParamsKey)) { | ||
this.reservedProps = wrappedProps[this.magicParamsKey]; | ||
this.reservedPropsList = Object.keys(this.reservedProps); | ||
if (!util.hasOwnProperty(wrappedPropsLists, this.boxedParamsKey)) { | ||
let i = RESERVED_PROPS_LIST.length; | ||
let reservedPropsList = []; | ||
while (i--) { | ||
reservedPropsList[i] = this.wrapProp(this.wrapMagicProp(RESERVED_PROPS_LIST[i])); | ||
} | ||
wrappedPropsLists[this.boxedParamsKey] = reservedPropsList; | ||
} | ||
this.reservedPropsList = wrappedPropsLists[this.boxedParamsKey]; | ||
// if (!util.hasOwnProperty(wrappedPropsLists, this.boxedParamsKey)) { | ||
// let i = RESERVED_PROPS_LIST.length; | ||
// let reservedPropsList = []; | ||
// while (i--) { | ||
// reservedPropsList[i] = this.wrapProp(this.wrapMagicProp(RESERVED_PROPS_LIST[i])); | ||
// } | ||
// wrappedPropsLists[this.boxedParamsKey] = reservedPropsList; | ||
// } | ||
// this.reservedPropsList = wrappedPropsLists[this.boxedParamsKey]; | ||
} | ||
@@ -166,3 +170,3 @@ | ||
BoxedContext.prototype.wrapMagicProp = function (prop) { | ||
return wrap(prop, this.extraPrefixChars, this.extraSuffixChars); | ||
return wrap(prop, this.magicPrefixChars, this.magicSuffixChars); | ||
}; | ||
@@ -318,6 +322,11 @@ | ||
Boxed.prototype.get = function (wrappedProp) { | ||
// accept both unwrapped magic props and wrapped ones | ||
if (this.reservedProps.hasOwnProperty(wrappedProp)) { | ||
return this.reservedProps[wrappedProp].get.call(this, false); | ||
} | ||
let prop = this.context.unwrappedProp(wrappedProp); | ||
if (prop !== wrappedProp) { | ||
if (this.reservedProps.hasOwnProperty(prop)) { | ||
return this.reservedProps[prop].get.call(this); | ||
return this.reservedProps[prop].get.call(this, true); | ||
} | ||
@@ -341,6 +350,10 @@ let boxed = this.getBoxedProp(prop); | ||
Boxed.prototype.set = function (wrappedProp, value, boxed) { | ||
if (wrappedProp !== BOXED_ARRAY_END && this.reservedProps.hasOwnProperty(wrappedProp)) { | ||
return this.reservedProps[wrappedProp].set.call(this, value, false); | ||
} | ||
let prop = this.context.unwrappedProp(wrappedProp); | ||
if (prop !== wrappedProp) { | ||
if (prop !== BOXED_ARRAY_END && this.reservedProps.hasOwnProperty(prop)) { | ||
return this.reservedProps[prop].set.call(this, value); | ||
if (prop !== wrappedProp && prop !== BOXED_ARRAY_END) { | ||
if (this.reservedProps.hasOwnProperty(prop)) { | ||
return this.reservedProps[prop].set.call(this, value, true); | ||
} | ||
@@ -357,7 +370,5 @@ } | ||
const isNumeric = isNumericInteger(prop); | ||
if (isNumeric) { | ||
prop = Number.parseInt(prop); | ||
} | ||
let n = toArrayIndex(prop); | ||
const isNumeric = n !== UNDEFINED; | ||
prop = n || prop; | ||
let updateParent = !this.isCopy; | ||
@@ -393,5 +404,5 @@ | ||
while (i--) { | ||
let key = keys[i]; | ||
if (isNumericInteger(key)) { | ||
let index = Number.parseInt(key) + 1; | ||
let key = toArrayIndex(keys[i]); | ||
if (key !== UNDEFINED) { | ||
let index = key + 1; | ||
if (maxKey < index) maxKey = index; | ||
@@ -431,6 +442,10 @@ } | ||
Boxed.prototype.delete = function (wrappedProp) { | ||
if (this.reservedProps.hasOwnProperty(wrappedProp)) { | ||
return this.reservedProps[wrappedProp].delete.call(this, false); | ||
} | ||
let prop = this.context.unwrappedProp(wrappedProp); | ||
if (prop !== wrappedProp) { | ||
if (this.reservedProps.hasOwnProperty(prop)) { | ||
return this.reservedProps[prop].delete.call(this); | ||
return this.reservedProps[prop].delete.call(this, true); | ||
} | ||
@@ -524,6 +539,27 @@ } | ||
// only set value if undefined | ||
Boxed.prototype.setDefaultValue = function (value) { | ||
Boxed.prototype.setValueOfDefaultMagic = function (value) { | ||
return this.value !== UNDEFINED || value === UNDEFINED || this.setValueOf(value); | ||
}; | ||
// only set value if undefined | ||
Boxed.prototype.setValueOfDefault = function (value) { | ||
if (this.value === UNDEFINED && value !== UNDEFINED) this.setValueOf(value); | ||
return this.value; | ||
}; | ||
// only set value if undefined | ||
Boxed.prototype.setValueOfDefaultBoxed = function (value) { | ||
if (this.value === UNDEFINED && value !== UNDEFINED) this.setValueOf(value); | ||
return this.proxiedThis; | ||
}; | ||
Boxed.prototype.valueOfBoolean = function () { | ||
return !!this.value; | ||
}; | ||
// convert to boolean before setting | ||
Boxed.prototype.setValueOfBoolean = function (value) { | ||
return this.setValueOf(!!value); | ||
}; | ||
// if value is object or array: return only first level children that were modified or UNDEFINED if no mods | ||
@@ -547,8 +583,9 @@ // if otherwise same as valueOfModified return value if modified or UNDEFINED if not modified | ||
if (isArrayDelta) { | ||
let maxIndex = undefined; | ||
let maxIndex = UNDEFINED; | ||
while (i--) { | ||
let prop = keys[i]; | ||
// these are modified, we get our prop value for them | ||
if (isNumericInteger(prop) && isNullOrUndefined(maxIndex) || prop > maxIndex) { | ||
maxIndex = Number.parseInt(prop); | ||
let n = toArrayIndex(prop); | ||
if (n !== UNDEFINED && maxIndex === UNDEFINED || n > maxIndex) { | ||
maxIndex = n; | ||
} else { | ||
@@ -561,3 +598,3 @@ let value = this.value[prop]; | ||
if (maxIndex !== undefined) { | ||
if (maxIndex !== UNDEFINED) { | ||
// copy from 0 to maxIndex | ||
@@ -616,18 +653,20 @@ i = maxIndex + 1; | ||
// arrayDeltaPartials: when true array deltas will contain undefined for all unmodified fields or deleted fields | ||
let isArrayDelta = !this.context.arrayDeepDeltaObjects && !this.context.arrayDeepDeltaPartials && isArray(this.value); | ||
let delta = !isArrayDelta ? {} : this.value.constructor(); // always something or we would not be here | ||
let isArrayDelta = !this.context.arrayDeepDeltaObjects && isArray(this.value); | ||
let isPureArrayDelta = isArrayDelta && !this.context.arrayDeepDeltaPartials; | ||
let delta = !isArrayDelta ? {} : this.value.constructor(); | ||
let keys = Object.keys(this.modifiedProps); | ||
let i = keys.length; | ||
if (isArrayDelta) { | ||
let maxIndex = undefined; | ||
if (isPureArrayDelta) { | ||
let maxIndex = UNDEFINED; | ||
while (i--) { | ||
let prop = keys[i]; | ||
// these are modified, we get our prop value for them | ||
if (isNumericInteger(prop) && isNullOrUndefined(maxIndex) || prop > maxIndex) { | ||
maxIndex = Number.parseInt(prop); | ||
let n = toArrayIndex(prop); | ||
if (n !== UNDEFINED && maxIndex === UNDEFINED || n > maxIndex) { | ||
maxIndex = n; | ||
} else { | ||
let value = this.value[prop]; | ||
// TEST: that undefined values are not added to delta && deepDelta unless this.ignoreUndefinedProperties is false | ||
// TEST: undefined values are not added to delta && deepDelta unless this.ignoreUndefinedProperties is false | ||
if (value !== UNDEFINED || !this.ignoreUndefinedProperties) delta[prop] = value; | ||
@@ -637,3 +676,3 @@ } | ||
if (maxIndex !== undefined) { | ||
if (maxIndex !== UNDEFINED) { | ||
// copy from 0 to maxIndex | ||
@@ -697,67 +736,149 @@ i = maxIndex + 1; | ||
/** | ||
* For all real values of unboxed item. Makes it easier | ||
* to loop over all real values. | ||
* For all real values of unboxed item. Makes it easier to loop over all real values. | ||
* | ||
* @param param if function then gets (boxedValue, prop, unboxedValue) | ||
* if object or array then will crate a copy of these in a new boxed parent and | ||
* return it as result. | ||
* For arrays will only include numeric keys (ie. indices) | ||
* | ||
* @returns {Boxed} | ||
* @param wrapped true if wrapped version of each key function | ||
* @param args {Array} arguments, last one is the callback function | ||
* | ||
* arg: strings & numbers are keys of interest, will be pre-filtered, saves on creating a proxy for undesired properties | ||
* arg: object, then assumed to hold options: | ||
* props: default false, true to have arrays include non-numeric keys | ||
* undefs: default false, true to have keys and array indices with undefined values included | ||
* | ||
* NOT IMPLEMENTED: | ||
* arg: array, contains args other than callback | ||
* | ||
* @returns {Proxy} of self for chaining | ||
* | ||
* TODO: refactor and add options for: exclude: keys, so that both would filter keys | ||
* | ||
*/ | ||
Boxed.prototype.boxedForEach = function (param) { | ||
// TODO: generalize parameter processing. array or object, function, many individual strings or numbers | ||
// const callBack = argumentList[0]; | ||
function forEachKeyImpl(wrapped, args) { | ||
let value = this.value; | ||
if ((isArray(args) || typeof args === typeof arguments) && isObject(value)) { | ||
let argCount = args.length - 1; | ||
const callBack = args[argCount]; | ||
if (isFunction(param)) { | ||
if (isObject(value)) { | ||
let keys = Object.keys(value); | ||
let i = keys.length; | ||
while (i--) { | ||
let prop = keys[i]; | ||
const item = value[prop]; | ||
if (item !== UNDEFINED) { | ||
const boxedItem = this.get(prop); | ||
param.call(UNDEFINED, boxedItem, prop, item); | ||
if (isFunction(callBack)) { | ||
// can work with it | ||
let i; | ||
let filteredKeys; | ||
let options = {}; | ||
const isArrayValue = isArray(value); | ||
let wantIntegers = isArrayValue && !options.props; | ||
let n; | ||
if (argCount) { | ||
i = argCount; | ||
filteredKeys = []; | ||
while (i--) { | ||
let arg = args[i]; | ||
if (isObject(arg)) { | ||
options.props = arg.props; | ||
options.undefs = arg.undefs; | ||
} else if (wantIntegers && (n = toArrayIndex(arg))) { | ||
filteredKeys.push(n); | ||
} else { | ||
filteredKeys.push(arg); | ||
} | ||
} | ||
} | ||
} else if (isArray(value)) { | ||
let i = value.length; | ||
while (i--) { | ||
const item = value[i]; | ||
if (item !== UNDEFINED) { | ||
const boxedItem = this.get(i); | ||
param.call(UNDEFINED, boxedItem, i, item); | ||
if (filteredKeys) { | ||
// both arrays and objects, filtered keys has only integers if so requested | ||
let keys = filteredKeys; | ||
let i = keys.length; | ||
while (i--) { | ||
let prop = keys[i]; | ||
if (value.hasOwnProperty(prop)) { | ||
if (isArrayValue) prop = toNumberIfArrayIndex(prop); | ||
let item = value[prop]; | ||
if (item !== UNDEFINED || options.undefs) { | ||
if (wrapped) { | ||
callBack(prop, this.getProp(prop), item); | ||
} else { | ||
callBack(prop, item); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} else if (isObject(param)) { | ||
const result = {}; | ||
} else { | ||
if (isArrayValue && !options.props) { | ||
let i = value.length; | ||
while (i--) { | ||
const item = value[i]; | ||
if (item !== UNDEFINED || options.undefs) { | ||
if (wrapped) { | ||
callBack(i, this.getProp(i), item); | ||
} else { | ||
callBack(i, item); | ||
} | ||
} | ||
} | ||
} else { | ||
let keys = Object.keys(value); | ||
let i = keys.length; | ||
while (i--) { | ||
let prop = keys[i]; | ||
if (isArrayValue) prop = toNumberIfArrayIndex(prop); | ||
const item = value[prop]; | ||
if (item !== UNDEFINED || options.undefs) { | ||
if (wrapped) { | ||
callBack(prop, this.getProp(prop), item); | ||
} else { | ||
callBack(prop, item); | ||
} | ||
} | ||
} | ||
} | ||
let keys = Object.keys(param); | ||
let i = keys.length; | ||
while (i--) { | ||
let prop = keys[i]; | ||
if (value.hasOwnProperty(param)) { | ||
const item = value[prop]; | ||
if (item !== UNDEFINED) { | ||
result[prop] = item; | ||
} | ||
} | ||
} | ||
let boxed = new Boxed(result, UNDEFINED, UNDEFINED, this.context); | ||
boxedProxy(boxed); | ||
return boxed.proxiedThis; | ||
} else if (!isNullOrUndefined(param)) { | ||
const result = {}; | ||
if (this.value.hasOwnProperty(param)) { | ||
result[param] = this.value[param]; | ||
} | ||
let boxed = new Boxed(result, UNDEFINED, UNDEFINED, this.context); | ||
boxedProxy(boxed); | ||
return boxed.proxiedThis; | ||
} | ||
return this.proxiedThis; | ||
} | ||
/** | ||
* For all real values of unboxed item. Makes it easier | ||
* to loop over all real values. | ||
* | ||
* @param args see forEachKeyImpl | ||
* | ||
* @returns {Boxed} | ||
*/ | ||
Boxed.prototype.forEachKey = function () { | ||
forEachKeyImpl.call(this, false, arguments); | ||
}; | ||
/** | ||
* For all real values of unboxed item. Makes it easier | ||
* to loop over all real values. | ||
* | ||
* @param args see forEachKeyImpl | ||
* | ||
* @returns {Boxed} | ||
*/ | ||
Boxed.prototype.forEachKeyBoxed = function () { | ||
forEachKeyImpl.call(this, true, arguments); | ||
}; | ||
function isBoxed(target) { | ||
return isObject(target) && target.constructor === Boxed; | ||
} | ||
module.exports.isBoxed = isBoxed; | ||
function boxedThis(target) { | ||
return isBoxedProxy(target) ? target[BOXED_GET_THIS] : UNDEFINED; | ||
} | ||
module.exports.boxedThis = boxedThis; | ||
function isBoxedProxy(target) { | ||
return target && isBoxed(target[BOXED_GET_THIS]); | ||
} | ||
module.exports.isBoxedProxy = isBoxedProxy; | ||
const BoxedHandler = { | ||
@@ -788,3 +909,3 @@ // target is the box function with | ||
if (prop === BOXED_GET_THIS) { | ||
return false; | ||
return true; | ||
} | ||
@@ -836,30 +957,35 @@ return target[BOXED_GET_THIS].has(prop, value); | ||
boxed.boxedWith = boxed.boxedWith.bind(boxed); | ||
boxed.detachFromParent = boxed.detachFromParent.bind(boxed); | ||
boxed.detachProp = boxed.detachProp.bind(boxed); | ||
boxed.detachAllProps = boxed.detachAllProps.bind(boxed); | ||
boxed.has = boxed.has.bind(boxed); | ||
boxed.ownKeys = boxed.ownKeys.bind(boxed); | ||
boxed.getOwnPropertyDescriptor = boxed.getOwnPropertyDescriptor.bind(boxed); | ||
boxed.getBoxedProp = boxed.getBoxedProp.bind(boxed); | ||
boxed.get = boxed.get.bind(boxed); | ||
// boxed.detachFromParent = boxed.detachFromParent.bind(boxed); | ||
// boxed.detachProp = boxed.detachProp.bind(boxed); | ||
// boxed.detachAllProps = boxed.detachAllProps.bind(boxed); | ||
// boxed.has = boxed.has.bind(boxed); | ||
// boxed.ownKeys = boxed.ownKeys.bind(boxed); | ||
// boxed.getOwnPropertyDescriptor = boxed.getOwnPropertyDescriptor.bind(boxed); | ||
// boxed.getBoxedProp = boxed.getBoxedProp.bind(boxed); | ||
// boxed.get = boxed.get.bind(boxed); | ||
boxed.getProp = boxed.getProp.bind(boxed); | ||
boxed.copyOfValue = boxed.copyOfValue.bind(boxed); | ||
boxed.set = boxed.set.bind(boxed); | ||
boxed.delete = boxed.delete.bind(boxed); | ||
boxed.valueOf = boxed.valueOf.bind(boxed); | ||
boxed.setValueOf = boxed.setValueOf.bind(boxed); | ||
boxed.deleteValueOf = boxed.deleteValueOf.bind(boxed); | ||
boxed.isModified = boxed.isModified.bind(boxed); | ||
boxed.valueOfModified = boxed.valueOfModified.bind(boxed); | ||
boxed.setValueOfModified = boxed.setValueOfModified.bind(boxed); | ||
boxed.deleteValueOfModified = boxed.deleteValueOfModified.bind(boxed); | ||
boxed.setDefaultValue = boxed.setDefaultValue.bind(boxed); | ||
boxed.unboxedDelta = boxed.unboxedDelta.bind(boxed); | ||
boxed.setUnboxedDelta = boxed.setUnboxedDelta.bind(boxed); | ||
boxed.deleteUnboxedDelta = boxed.deleteUnboxedDelta.bind(boxed); | ||
boxed.unboxedDeepDelta = boxed.unboxedDeepDelta.bind(boxed); | ||
boxed.setUnboxedDeepDelta = boxed.setUnboxedDeepDelta.bind(boxed); | ||
boxed.deleteUnboxedDeepDelta = boxed.deleteUnboxedDeepDelta.bind(boxed); | ||
boxed.boxedForEach = boxed.boxedForEach.bind(boxed); | ||
// boxed.copyOfValue = boxed.copyOfValue.bind(boxed); | ||
// boxed.set = boxed.set.bind(boxed); | ||
// boxed.delete = boxed.delete.bind(boxed); | ||
// boxed.valueOf = boxed.valueOf.bind(boxed); | ||
// boxed.setValueOf = boxed.setValueOf.bind(boxed); | ||
// boxed.deleteValueOf = boxed.deleteValueOf.bind(boxed); | ||
// boxed.isModified = boxed.isModified.bind(boxed); | ||
// boxed.valueOfModified = boxed.valueOfModified.bind(boxed); | ||
// boxed.setValueOfModified = boxed.setValueOfModified.bind(boxed); | ||
// boxed.deleteValueOfModified = boxed.deleteValueOfModified.bind(boxed); | ||
boxed.setValueOfDefault = boxed.setValueOfDefault.bind(boxed); | ||
boxed.setValueOfDefaultBoxed = boxed.setValueOfDefaultBoxed.bind(boxed); | ||
// boxed.setValueOfDefaultMagic = boxed.setValueOfDefaultMagic.bind(boxed); | ||
// boxed.valueOfBoolean = boxed.valueOfBoolean.bind(boxed); | ||
// boxed.setValueOfBoolean = boxed.setValueOfBoolean.bind(boxed); | ||
// boxed.unboxedDelta = boxed.unboxedDelta.bind(boxed); | ||
// boxed.setUnboxedDelta = boxed.setUnboxedDelta.bind(boxed); | ||
// boxed.deleteUnboxedDelta = boxed.deleteUnboxedDelta.bind(boxed); | ||
// boxed.unboxedDeepDelta = boxed.unboxedDeepDelta.bind(boxed); | ||
// boxed.setUnboxedDeepDelta = boxed.setUnboxedDeepDelta.bind(boxed); | ||
// boxed.deleteUnboxedDeepDelta = boxed.deleteUnboxedDeepDelta.bind(boxed); | ||
boxed.forEachKey = boxed.forEachKey.bind(boxed); | ||
boxed.forEachKeyBoxed = boxed.forEachKeyBoxed.bind(boxed); | ||
boxed.boxedWith[BOXED_GET_THIS] = boxed; | ||
@@ -906,3 +1032,5 @@ boxed.proxiedThis = new Proxy(boxed.boxedWith, BoxedHandler); | ||
if (!globalBoxKey) { | ||
globalBoxKey = box + ""; | ||
let obj = {}; | ||
obj[box] = 0; | ||
globalBoxKey = Object.keys(obj)[0]; | ||
} | ||
@@ -909,0 +1037,0 @@ context.globalBox = globalBoxKey; |
@@ -21,3 +21,3 @@ "use strict"; | ||
this.boxed = UNDEFINED; | ||
if (this.saveState) { | ||
if (this.saveState) { | ||
return this.saveState(modified, boxed); | ||
@@ -24,0 +24,0 @@ } else { |
@@ -6,2 +6,10 @@ "use strict"; | ||
const UNDEFINED = void 0; | ||
module.exports.UNDEFINED = UNDEFINED; | ||
module.exports.isNumber = function (arg) { | ||
return typeof arg === 'number'; | ||
}; | ||
module.exports.extend = function (other, add) { | ||
@@ -29,3 +37,3 @@ // Don't do anything if add isn't an object | ||
// noinspection JSUnfilteredForInLoop | ||
if (hasOwnProperty(arg, key) && arg[key] !== undefined) { | ||
if (hasOwnProperty(arg, key) && arg[key] !== UNDEFINED) { | ||
return true; | ||
@@ -67,2 +75,20 @@ } | ||
function toArrayIndex(arg) { | ||
const n = Number.parseFloat(arg); | ||
return Number.isInteger(n) && +n === n && n >= 0 ? n : UNDEFINED; | ||
} | ||
module.exports.toArrayIndex = toArrayIndex; | ||
module.exports.isArrayIndex = function isArrayIndex(arg) { | ||
return toArrayIndex !== UNDEFINED; | ||
}; | ||
function toNumberIfArrayIndex(arg) { | ||
const n = Number.parseFloat(arg); | ||
return Number.isInteger(n) && +n === n && n >= 0 ? n : arg; | ||
} | ||
module.exports.toNumberIfArrayIndex = toNumberIfArrayIndex; | ||
function returnsAlwaysFalse() { | ||
@@ -175,3 +201,3 @@ return false; | ||
// need to copy the non numeric fields, object assign does not copy them | ||
copyFilteredProperties(newValue, src, key => !isNumericInteger(key)); | ||
copyFilteredProperties(newValue, src, key => !isNumericInteger(key) || key < 0); | ||
} | ||
@@ -197,5 +223,10 @@ return newValue; | ||
while (--i) { | ||
let index = arr.indexOf(arguments[i]); | ||
if (index >= 0) { | ||
arr.splice(index, 1); | ||
const arg = arguments[i]; | ||
if (isArray(arg)) { | ||
deleteItems(arr, ...arg) | ||
} else if (isObject(arg)) { | ||
deleteItems(arr, ...Object.keys(arg)) | ||
} else { | ||
let index = arr.indexOf(arg); | ||
if (index >= 0) arr.splice(index, 1); | ||
} | ||
@@ -209,1 +240,2 @@ } | ||
module.exports.deleteItems = deleteItems; | ||
{ | ||
"name": "boxed-immutable", | ||
"version": "0.1.6", | ||
"version": "0.2.0", | ||
"private": false, | ||
@@ -18,3 +18,5 @@ "description": "Immutable proxy wrapper with auto-vivification", | ||
}, | ||
"devDependencies": {}, | ||
"devDependencies": { | ||
"jest-each": "^0.3.1" | ||
}, | ||
"scripts": {}, | ||
@@ -21,0 +23,0 @@ "keywords": [ |
133
README.md
@@ -9,14 +9,2 @@ # boxed-immutable | ||
## Install | ||
Use [npm](https://npmjs.com/) to install. | ||
```sh | ||
npm install boxed-immutable --save | ||
``` | ||
## Usage | ||
[![NPM](https://nodei.co/npm/boxed-immutable.png)](https://www.npmjs.com/package/boxed-immutable) | ||
Create a boxed-immutable object proxy then access and/or modify its nested properties, ignoring | ||
@@ -36,3 +24,3 @@ whether intermediate values are objects/arrays or whether they exist. | ||
Trying to access a field or array index when the property is not valid will throw | ||
`TypeError`. | ||
`TypeError` or `ReferenceError`. | ||
2. Use original property name with `_$` appended to the end of it to get access to a proxy which | ||
@@ -45,2 +33,18 @@ will auto-vivify the property container when the first property is set. | ||
Easily customize the suffix/prefix combination that will not conflict with property names in | ||
your project's state objects. | ||
## Install | ||
Use [npm](https://npmjs.com/) to install. | ||
```sh | ||
npm install boxed-immutable --save | ||
``` | ||
## Usage | ||
[![NPM](https://nodei.co/npm/boxed-immutable.png)](https://www.npmjs.com/package/boxed-immutable) | ||
```javascript | ||
@@ -60,6 +64,6 @@ const _$ = require('boxed-immutable')._$; | ||
// get all the first level children to update state | ||
this.setState(state.delta$_$); | ||
this.setState(state.delta$); | ||
// get only the changed leaf fields and their parents, minimal update image | ||
this.setState(state.deepDelta$_$); | ||
this.setState(state.deepDelta$); | ||
} | ||
@@ -100,4 +104,5 @@ ``` | ||
// all are equivalent | ||
empty[""] = 5; | ||
empty["_$"] = 5; | ||
empty[_$] = 5; // may not work, depends on how functions is converted to string | ||
empty[_$] = 5; // depending on how function is converted to string this may accept any function, not just box. | ||
empty._$ = 5; // simplest and safest alternative | ||
@@ -110,3 +115,3 @@ // result: [5] | ||
empty._$ = 20; | ||
let result = empty.unboxed$_$; | ||
let result = empty.unboxed$; | ||
// result: [5, 10, 20] | ||
@@ -129,8 +134,9 @@ ``` | ||
empty._$ = 20; | ||
let result = empty.unboxed$_$; | ||
let result = empty.unboxed$; | ||
// result: {0: 10, 1: 20, field: 5} | ||
``` | ||
For symmetry, you can also use `_$` on objects. In case of objects `_$` is equal to the greatest | ||
integer key in the object or 0 if no integer keys. Here is why: | ||
For symmetry, you can also use the end of array index `_$` on objects. In case of objects `_$` | ||
is equal to the greatest integer key in the object or 0 if no integer keys. The goal is to make | ||
boxed values allow setting properties like their unboxed JavaScript counterparts. | ||
@@ -145,3 +151,3 @@ ```javascript | ||
obj[2] = 25; | ||
let result = obj.unboxed$_$; | ||
let result = obj.unboxed$; | ||
// result: {0: 5, 1:15, 2:25, prop: "a"} | ||
@@ -151,2 +157,3 @@ | ||
let obj = _$({}); | ||
obj.prop = "a"; | ||
@@ -161,3 +168,3 @@ obj._$ = 5; | ||
You can change a couple of options on how the boxing handles properties whose value is | ||
`undefined` and also modify the prefix/suffix used for accessing boxed properties: | ||
`undefined` and modify the prefix/suffix used for accessing boxed properties or magic properties: | ||
@@ -169,5 +176,5 @@ Use the `createBox(options)` function from the module to create a boxing function with | ||
const createBox = require('boxed-immutable').createBox; | ||
const $__$ = createBox({prefixChars: "$_", suffixChars:"_$"}); | ||
const $__$ = createBox({prefixChars: "$_", suffixChars:"_$", magicPrefixChars: "", magicSuffixChars: "$$"}); | ||
// Now all your properties are wrapped | ||
// Now all your properties are wrapped and magic properties end with two $ | ||
let obj = $__$(); | ||
@@ -180,22 +187,22 @@ | ||
let result = obj.$_unboxed$_$; | ||
let result = obj.unboxed$$; | ||
// result: { field: { subField: 4 }, prop: [ "a", "b", "c"] }; | ||
``` | ||
| Option | Default | Description | | ||
|:-----------------------------------|:------------|:-----------------------------------------------------------------------------------------| | ||
| `deleteEmptyCollections:` | `true` | if deleting a property results in an empty collection, delete that too | | ||
| `ignoreUndefinedProperties:` | `true` | when copying delta and deepDelta ignore properties with `undefined` value | | ||
| `arrayDeltaObjects:` | `false` | return array delta as objects with index as key | | ||
| `arrayDeltaObjectMarker:` | `undefined` | field name to set when returning array delta as objects, `undefined` means don't set | | ||
| `arrayDeltaObjectMarkerValue:` | `undefined` | value for above | | ||
| `arrayDeltaPartials:` | `false` | return array delta as partials, any unset indices will be `undefined` | | ||
| `arrayDeepDeltaObjects:` | `false` | return array deepDelta as objects with index as key | | ||
| `arrayDeepDeltaObjectMarker:` | `undefined` | field name to set when returning array deepDelta as objects, `undefined` means don't set | | ||
| `arrayDeepDeltaObjectMarkerValue:` | `undefined` | value for above | | ||
| `arrayDeepDeltaPartials:` | `false` | return array deepDelta as partials, any unset indices will be `undefined` | | ||
| `prefixChars:`<sup>\[1]</sup> | `""` | prefix for boxed properties. | | ||
| `suffixChars:`<sup>\[1]</sup> | `"_$"` | suffix for boxed properties. | | ||
| `magicPrefixChars:`<sup>\[1]</sup> | `""` | prefix for magic properties, applied after prefixChars | | ||
| `magicSuffixChars:`<sup>\[1]</sup> | `"$"` | suffix for magic properties, applied before suffixChars | | ||
| Option | Default | Description | | ||
|:-----------------------------------|:------------|:---------------------------------------------------------------------------------------------------------------------------------------------| | ||
| `deleteEmptyCollections`: | `true` | if deleting a property results in an empty collection, delete that too | | ||
| `ignoreUndefinedProperties`: | `true` | when copying delta and deepDelta ignore properties with `undefined` value | | ||
| `arrayDeltaObjects`: | `false` | return array delta as objects with index as key | | ||
| `arrayDeltaObjectMarker`: | `undefined` | field name to set when returning array delta as objects, `undefined` means don't set | | ||
| `arrayDeltaObjectMarkerValue`: | `undefined` | value for above | | ||
| `arrayDeltaPartials`: | `false` | return array delta as partials, any unset indices will be `undefined` | | ||
| `arrayDeepDeltaObjects`: | `false` | return array deepDelta as objects with index as key | | ||
| `arrayDeepDeltaObjectMarker`: | `undefined` | field name to set when returning array deepDelta as objects, `undefined` means don't set | | ||
| `arrayDeepDeltaObjectMarkerValue`: | `undefined` | value for above | | ||
| `arrayDeepDeltaPartials`: | `true` | return array deepDelta as partials, any unset indices will be `undefined`, false will return array delta from index 0 to last modified index | | ||
| `prefixChars`: <sup>\[1]</sup> | `""` | prefix for boxed properties. | | ||
| `suffixChars`: <sup>\[1]</sup> | `"_$"` | suffix for boxed properties. | | ||
| `magicPrefixChars`: <sup>\[1]</sup> | `""` | prefix for magic properties, applied after prefixChars | | ||
| `magicSuffixChars`: <sup>\[1]</sup> | `"$"` | suffix for magic properties, applied before suffixChars | | ||
@@ -212,18 +219,27 @@ \* \[1]: Note use of `[_$]` is not affected by prefixes or suffixes since the function is passed | ||
With default settings all magic properties except `_$` get an extra `$` added because of the | ||
default `magicSuffixChars` being `"$"` | ||
Magic properties except `_$` are wrapped in `magicPrefixChars` and `magicSuffixChars`, | ||
properties and empty string `""` which represents array end are wrapped in `prefixChars` and | ||
`suffixChars` | ||
Magic Properties of boxed properties: | ||
Magic properties are accessible with or without the property wrapper (`prefixChars` and | ||
`suffixChars`) for some it affect whether they provide/return regular values or boxed proxies | ||
for these values. | ||
| Property | Get | Set | Delete | Call | | ||
|:---------------|:------------------------------------------------------------------------------------------|:----------------------------------------------------------------------------------------|:--------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------| | ||
| `_$` | proxy of the boxed object, ie. boxed === boxed.\_$, so you can do boxed.\_$() or boxed() | append end of array | error | does a call on first argument, use: `boxed._$(_$ => { });` returns `boxed` | | ||
| `get$_$` | function | error | error | `.get$_$("prop")` is same as `["prop" + "_$"]`, convenience function when you have a property name in a variabel and need a boxed version of it | | ||
| `forEach$_$` | function | error | error | functions executes callback for each own property, passes `.forEach$_$((boxedValue, prop, unboxedValue) =>{});` | | ||
| `unboxed$_$` | unboxed value | set value of boxed property and mark as modified | delete property in parent | error | | ||
| `modified$_$` | value if modified else undefined | same as above | same as above | error | | ||
| `default$_$` | function | set value if it is undefined, otherwise do nothing | error | error | | ||
| `delta$_$` | modified properties of first level, full props thereafter: shallow delta | do shallow delta update of properties, all properties after first level will be changed | error | error | | ||
| `deepDelta$_$` | modified properties only of all levels: deep delta | do deep delta update with value, only modified properties of all levels are changed. | error | error | | ||
Where it makes a difference, both wrapped and unwrapped magic properties are given. | ||
Magic Properties of boxed object shown with defaults for wrapping prefix/suffix options: | ||
| Property | Get | Set | Delete | Call | | ||
|:----------------|:-------------------------------------------------------------------------|:----------------------------------------------------------------------------------------|:--------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------| | ||
| `_$` | proxy for the boxed object | append end of array | error | does a call on first argument, use: `boxed._$(_$ => { });` returns `boxed` | | ||
| `get$` | function | error | error | `.get$("prop")` is same as `["prop" + "_$"]`, convenience function when you have a property name in a variable and need a boxed version of it | | ||
| `forEachKey$` | function | error | error | functions executes callback for each property key `.forEach$((prop, unboxedValue) =>{});` skip `undefined` values, addtionally for arrays prop is integers >=0 | | ||
| `forEachKey$_$` | function | error | error | functions executes callback for each property key `.forEach$((prop, boxedValue, unboxedValue) =>{});` skip `undefined` values, addtionally for arrays prop is integers >=0 | | ||
| `unboxed$` | unboxed value | set value of boxed property and mark as modified | delete property in parent | error | | ||
| `modified$` | value if modified else undefined | same as above | same as above | error | | ||
| `default$` | function | set value if it is undefined, otherwise do nothing | error | error | | ||
| `boolean$` | value converted to true or false | convert passed value to true or false before setting | error | error | | ||
| `delta$` | modified properties of first level, full props thereafter: shallow delta | do shallow delta update of properties, all properties after first level will be changed | error | error | | ||
| `deepDelta$` | modified properties only of all levels: deep delta | do deep delta update with value, only modified properties of all levels are changed. | error | error | | ||
Use of `._$()`, sometimes you need to modify deep properties based on programming logic. Instead | ||
@@ -279,2 +295,5 @@ of creating an object then adding it to your modified state, you can use this option and benefit | ||
Applying partial changes to component's state is as easy as setting a value in boxedOnDemand | ||
instance and invoking `.save()` | ||
```javascript | ||
@@ -332,6 +351,6 @@ const boxOnDemand = require('boxed-immutable').boxOnDemand; | ||
| Property | Get | Set | Delete | Call | | ||
|:---------|:---------|:------|:-------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------| | ||
| Property | Get | Set | Delete | Call | | ||
|:---------|:---------|:------|:-------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------| | ||
| `save` | function | error | error | calls the saveState callback passed to boxOnDemand function, returns value returned from callback, callback only called if there were changes made to boxed object | | ||
| `cancel` | function | error | error | cancels any changes and destroys the boxed object, it is recreated on next access with a fresh copy of the immutable state, returns proxy this for chaining calls | | ||
| `cancel` | function | error | error | cancels any changes and destroys the boxed object, it is recreated on next access with a fresh copy of the immutable state, returns proxy this for chaining calls | | ||
@@ -338,0 +357,0 @@ #### boxOnDemand(getState, saveState, options) |
"use strict"; | ||
const each = require('jest-each'); | ||
const boxedImmutable = require("boxed-immutable"); | ||
const testUtil = require('./testUtil'); | ||
const _$ = boxedImmutable._$; | ||
const createBox = boxedImmutable.createBox; | ||
const Boxed = boxedImmutable.boxed.Boxed; | ||
const BOXED_GET_THIS = boxedImmutable.boxed.BOXED_GET_THIS; | ||
const isProxy = boxedImmutable.boxed.isBoxedProxy; | ||
const generateTestParams = testUtil.generateTestParams; | ||
const paramStringException = testUtil.paramStringException; | ||
const createBoxed = testUtil.createBoxed; | ||
const createOnDemandBoxed = testUtil.createOnDemandBoxed; | ||
function createBoxed(val) { | ||
const boxedProxy = _$(val); | ||
return { | ||
origVal: val, | ||
boxedProxy: boxedProxy, | ||
boxedVal: boxedProxy[BOXED_GET_THIS], | ||
}; | ||
} | ||
describe('Create Array first level', () => { | ||
@@ -226,3 +222,3 @@ let origVal; | ||
test('deepDelta$_$ == deepDelta', () => { | ||
expect(boxedProxy.deepDelta$_$).toEqual(deepDeltaValue); | ||
expect(boxedProxy.deepDelta$_$).toEqual([undefined, undefined, 12, undefined, undefined, 15, undefined, 17]); | ||
}); | ||
@@ -240,3 +236,3 @@ }); | ||
beforeAll(() => { | ||
let vals = createBoxed({field: ""}); | ||
let vals = createBoxed({ field: "" }); | ||
origVal = vals.origVal; | ||
@@ -250,3 +246,3 @@ boxedVal = vals.boxedVal; | ||
expectedValue = {field: 3}; | ||
expectedValue = { field: 3 }; | ||
deepDeltaValue = deltaValue = expectedValue; | ||
@@ -282,3 +278,3 @@ }); | ||
beforeAll(() => { | ||
let vals = createBoxed({field1: ""}); | ||
let vals = createBoxed({ field1: "" }); | ||
origVal = vals.origVal; | ||
@@ -295,4 +291,4 @@ boxedVal = vals.boxedVal; | ||
expectedValue = {field1: "", field2: 2, field3:1, }; | ||
deepDeltaValue = deltaValue = {field2: 2, field3:1, }; | ||
expectedValue = { field1: "", field2: 2, field3: 1, }; | ||
deepDeltaValue = deltaValue = { field2: 2, field3: 1, }; | ||
}); | ||
@@ -327,3 +323,3 @@ | ||
beforeAll(() => { | ||
let vals = createBoxed({field1: ""}); | ||
let vals = createBoxed({ field1: "" }); | ||
origVal = vals.origVal; | ||
@@ -340,8 +336,8 @@ boxedVal = vals.boxedVal; | ||
expectedValue = {field1: "", field2: 2, field3:1, }; | ||
deepDeltaValue = deltaValue = {field2: 2, field3:1, }; | ||
expectedValue = { field1: "", field2: 2, field3: 1, }; | ||
deepDeltaValue = deltaValue = { field2: 2, field3: 1, }; | ||
}); | ||
test('[prop].prop fails', () => { | ||
expect(()=>{let t= boxedProxy[field10].flag}).toThrow(ReferenceError); | ||
expect(() => {let t = boxedProxy[field10].flag}).toThrow(ReferenceError); | ||
}); | ||
@@ -348,0 +344,0 @@ |
"use strict"; | ||
const each = require('jest-each'); | ||
const boxedImmutable = require("boxed-immutable"); | ||
const testUtil = require('./testUtil'); | ||
const boxedImmutable = require("boxed-immutable"); | ||
const _$ = boxedImmutable._$; | ||
const createBox = boxedImmutable.createBox; | ||
const Boxed = boxedImmutable.boxed.Boxed; | ||
const BOXED_GET_THIS = boxedImmutable.boxed.BOXED_GET_THIS; | ||
const isProxy = boxedImmutable.boxed.isBoxedProxy; | ||
const generateTestParams = testUtil.generateTestParams; | ||
const paramStringException = testUtil.paramStringException; | ||
const createBoxed = testUtil.createBoxed; | ||
const createOnDemandBoxed = testUtil.createOnDemandBoxed; | ||
function createBoxed(val) { | ||
const boxedProxy = _$(val); | ||
return { | ||
origVal: val, | ||
boxedProxy: boxedProxy, | ||
boxedVal: boxedProxy[BOXED_GET_THIS], | ||
}; | ||
} | ||
describe('Boxed undefined Unmodified', () => { | ||
@@ -767,3 +762,3 @@ let origVal; | ||
test('deepDelta$_$ == value', () => { | ||
expect(boxedProxy.deepDelta$_$).toEqual([10, 5]); | ||
expect(boxedProxy.deepDelta$_$).toEqual([undefined, 5]); | ||
}); | ||
@@ -877,3 +872,3 @@ }); | ||
test('deepDelta$_$ == delta$_$', () => { | ||
expect(boxedProxy.deepDelta$_$).toEqual(boxedVal.unboxedDelta()); | ||
expect(boxedProxy.deepDelta$_$).toEqual([5, undefined, 15]); | ||
}); | ||
@@ -1071,3 +1066,3 @@ }); | ||
newValue2: { field: 25, }, | ||
}, | ||
}, | ||
// oldValue2: "aValue", | ||
@@ -1229,3 +1224,3 @@ oldValue3: { | ||
newValue2: { field: 25, }, | ||
}, | ||
}, | ||
// oldValue2: "aValue", | ||
@@ -1242,3 +1237,2 @@ oldValue3: { | ||
origDeepDeltaValue = { | ||
@@ -1250,3 +1244,3 @@ oldValue: { newValue1: [5] }, | ||
newValue2: { field: 25, }, | ||
}, | ||
}, | ||
// oldValue2: "aValue", | ||
@@ -1263,3 +1257,2 @@ oldValue3: { | ||
origVal = vals.origVal; | ||
@@ -1386,3 +1379,3 @@ boxedVal = vals.boxedVal; | ||
newValue2: { field: 25, }, | ||
}, | ||
}, | ||
// oldValue2: "aValue", | ||
@@ -1399,3 +1392,2 @@ oldValue3: { | ||
origDeepDeltaValue = { | ||
@@ -1407,3 +1399,3 @@ oldValue: { newValue1: [5] }, | ||
newValue2: { field: 25, }, | ||
}, | ||
}, | ||
// oldValue2: "aValue", | ||
@@ -1420,3 +1412,2 @@ oldValue3: { | ||
origVal = vals.origVal; | ||
@@ -1423,0 +1414,0 @@ boxedVal = vals.boxedVal; |
"use strict"; | ||
const each = require('jest-each'); | ||
const boxedImmutable = require("boxed-immutable"); | ||
const testUtil = require('./testUtil'); | ||
const _$ = boxedImmutable._$; | ||
const createBox = boxedImmutable.createBox; | ||
const BOXED_GET_THIS = boxedImmutable.boxed.BOXED_GET_THIS; | ||
const boxOnDemand = boxedImmutable.boxOnDemand; | ||
const BoxedOnDemand = boxedImmutable.boxed.BoxedOnDemand; | ||
const Boxed = boxedImmutable.boxed.Boxed; | ||
const isProxy = boxedImmutable.boxed.isBoxedProxy; | ||
const generateTestParams = testUtil.generateTestParams; | ||
const paramStringException = testUtil.paramStringException; | ||
const createBoxed = testUtil.createBoxed; | ||
const createOnDemandBoxed = testUtil.createOnDemandBoxed; | ||
function createBoxed(get, set) { | ||
const boxedProxy = boxOnDemand(get, set); | ||
return { | ||
boxedProxy: boxedProxy, | ||
}; | ||
} | ||
describe('boxed on demand empty no changes', () => { | ||
@@ -30,6 +26,6 @@ let onDemand; | ||
let vals = createBoxed(()=>{ | ||
let vals = createOnDemandBoxed(() => { | ||
getCalled++; | ||
return onDemand || origVal; | ||
}, (modified, boxed)=>{ | ||
return onDemand || origVal; | ||
}, (modified, boxed) => { | ||
saveCalled++; | ||
@@ -49,11 +45,11 @@ onDemand = { | ||
let tmp; | ||
expect([getCalled, saveCalled]).toEqual([1,0]); | ||
expect([getCalled, saveCalled]).toEqual([1, 0]); | ||
tmp = boxedProxy.value; | ||
expect([getCalled, saveCalled]).toEqual([1,0]); | ||
expect([getCalled, saveCalled]).toEqual([1, 0]); | ||
expect(boxedProxy._$).toBe(boxed); | ||
expect([getCalled, saveCalled]).toEqual([1,0]); | ||
expect([getCalled, saveCalled]).toEqual([1, 0]); | ||
expect(boxedProxy._$).toBe(boxed); | ||
expect([getCalled, saveCalled]).toEqual([1,0]); | ||
expect([getCalled, saveCalled]).toEqual([1, 0]); | ||
expect(onDemand).toEqual(undefined); | ||
@@ -63,13 +59,13 @@ expect(origVal).toEqual(undefined); | ||
boxedProxy.save(); | ||
expect([getCalled, saveCalled]).toEqual([1,0]); | ||
expect([getCalled, saveCalled]).toEqual([1, 0]); | ||
let boxed2 = boxedProxy._$; | ||
expect([getCalled, saveCalled]).toEqual([1,0]); | ||
expect([getCalled, saveCalled]).toEqual([1, 0]); | ||
expect(boxed2).toBe(boxed); | ||
expect(boxedProxy._$).toBe(boxed2); | ||
expect([getCalled, saveCalled]).toEqual([1,0]); | ||
expect([getCalled, saveCalled]).toEqual([1, 0]); | ||
expect(boxedProxy._$).toBe(boxed2); | ||
expect([getCalled, saveCalled]).toEqual([1,0]); | ||
expect([getCalled, saveCalled]).toEqual([1, 0]); | ||
boxedProxy.save(); | ||
expect([getCalled, saveCalled]).toEqual([1,0]); | ||
expect([getCalled, saveCalled]).toEqual([1, 0]); | ||
}); | ||
@@ -90,6 +86,6 @@ }); | ||
let vals = createBoxed(()=>{ | ||
let vals = createOnDemandBoxed(() => { | ||
getCalled++; | ||
return onDemand || origVal; | ||
}, (modified, boxed)=>{ | ||
return onDemand || origVal; | ||
}, (modified, boxed) => { | ||
saveCalled++; | ||
@@ -108,7 +104,7 @@ onDemand = { | ||
let boxed = boxedProxy._$; | ||
expect([getCalled, saveCalled]).toEqual([1,0]); | ||
expect([getCalled, saveCalled]).toEqual([1, 0]); | ||
expect(boxedProxy._$).toBe(boxed); | ||
expect([getCalled, saveCalled]).toEqual([1,0]); | ||
expect([getCalled, saveCalled]).toEqual([1, 0]); | ||
expect(boxedProxy._$).toBe(boxed); | ||
expect([getCalled, saveCalled]).toEqual([1,0]); | ||
expect([getCalled, saveCalled]).toEqual([1, 0]); | ||
expect(onDemand).toEqual(undefined); | ||
@@ -120,18 +116,18 @@ expect(origVal).toEqual(undefined); | ||
expect(origVal).toEqual(undefined); | ||
expect([getCalled, saveCalled]).toEqual([1,0]); | ||
expect([getCalled, saveCalled]).toEqual([1, 0]); | ||
boxedProxy.save(); | ||
expect([getCalled, saveCalled]).toEqual([1,1]); | ||
expect(onDemand).toEqual({state: {simple: 0}, delta: {simple: 0}, deepDelta: {simple: 0}, }); | ||
expect([getCalled, saveCalled]).toEqual([1, 1]); | ||
expect(onDemand).toEqual({ state: { simple: 0 }, delta: { simple: 0 }, deepDelta: { simple: 0 }, }); | ||
expect(origVal).toEqual(undefined); | ||
let boxed2 = boxedProxy._$; | ||
expect([getCalled, saveCalled]).toEqual([2,1]); | ||
expect([getCalled, saveCalled]).toEqual([2, 1]); | ||
expect(boxed2).not.toBe(boxed); | ||
expect(boxedProxy._$).toBe(boxed2); | ||
expect([getCalled, saveCalled]).toEqual([2,1]); | ||
expect([getCalled, saveCalled]).toEqual([2, 1]); | ||
expect(boxedProxy._$).toBe(boxed2); | ||
expect([getCalled, saveCalled]).toEqual([2,1]); | ||
expect([getCalled, saveCalled]).toEqual([2, 1]); | ||
boxedProxy.save(); | ||
expect([getCalled, saveCalled]).toEqual([2,1]); | ||
expect(onDemand).toEqual({state: {simple: 0}, delta: {simple: 0}, deepDelta: {simple: 0}, }); | ||
expect([getCalled, saveCalled]).toEqual([2, 1]); | ||
expect(onDemand).toEqual({ state: { simple: 0 }, delta: { simple: 0 }, deepDelta: { simple: 0 }, }); | ||
expect(origVal).toEqual(undefined); | ||
@@ -153,6 +149,6 @@ }); | ||
let vals = createBoxed(()=>{ | ||
let vals = createOnDemandBoxed(() => { | ||
getCalled++; | ||
return onDemand || origVal; | ||
}, (modified, boxed)=>{ | ||
return onDemand || origVal; | ||
}, (modified, boxed) => { | ||
saveCalled++; | ||
@@ -171,5 +167,5 @@ onDemand = { | ||
expect(boxedProxy.unboxed$_$).toBe(undefined); | ||
expect([getCalled, saveCalled]).toEqual([1,0]); | ||
expect([getCalled, saveCalled]).toEqual([1, 0]); | ||
expect(boxedProxy.field).toEqual(undefined); | ||
expect([getCalled, saveCalled]).toEqual([1,0]); | ||
expect([getCalled, saveCalled]).toEqual([1, 0]); | ||
@@ -182,6 +178,6 @@ onDemand = { field: 0 }; | ||
expect(boxedProxy.unboxed$_$).toEqual({ field: 0 }); | ||
expect([getCalled, saveCalled]).toEqual([2,0]); | ||
expect([getCalled, saveCalled]).toEqual([2, 0]); | ||
expect(boxedProxy.field).toEqual(0); | ||
expect(boxedProxy.unboxed$_$).toEqual({ field: 0 }); | ||
expect([getCalled, saveCalled]).toEqual([2,0]); | ||
expect([getCalled, saveCalled]).toEqual([2, 0]); | ||
}); | ||
@@ -198,11 +194,11 @@ }); | ||
beforeAll(() => { | ||
origVal = { state: { } }; | ||
onDemand = { }; | ||
origVal = { state: {} }; | ||
onDemand = {}; | ||
saveCalled = 0; | ||
getCalled = 0; | ||
let vals = createBoxed(()=>{ | ||
let vals = createOnDemandBoxed(() => { | ||
getCalled++; | ||
return onDemand.state; | ||
}, (modified, boxed)=>{ | ||
return onDemand.state; | ||
}, (modified, boxed) => { | ||
saveCalled++; | ||
@@ -219,21 +215,21 @@ origVal.state = modified; | ||
let boxed = boxedProxy._$; | ||
expect([getCalled, saveCalled]).toEqual([1,0]); | ||
expect([getCalled, saveCalled]).toEqual([1, 0]); | ||
expect(boxedProxy._$).toBe(boxed); | ||
expect([getCalled, saveCalled]).toEqual([1,0]); | ||
expect([getCalled, saveCalled]).toEqual([1, 0]); | ||
expect(boxedProxy._$).toBe(boxed); | ||
expect([getCalled, saveCalled]).toEqual([1,0]); | ||
expect(origVal).toEqual({state: {}, }); | ||
expect([getCalled, saveCalled]).toEqual([1, 0]); | ||
expect(origVal).toEqual({ state: {}, }); | ||
boxedProxy.save(); | ||
expect([getCalled, saveCalled]).toEqual([1,0]); | ||
expect([getCalled, saveCalled]).toEqual([1, 0]); | ||
let boxed2 = boxedProxy._$; | ||
expect([getCalled, saveCalled]).toEqual([1,0]); | ||
expect([getCalled, saveCalled]).toEqual([1, 0]); | ||
expect(boxed2).toBe(boxed); | ||
expect(boxedProxy._$).toBe(boxed2); | ||
expect([getCalled, saveCalled]).toEqual([1,0]); | ||
expect([getCalled, saveCalled]).toEqual([1, 0]); | ||
expect(boxedProxy._$).toBe(boxed2); | ||
expect([getCalled, saveCalled]).toEqual([1,0]); | ||
expect([getCalled, saveCalled]).toEqual([1, 0]); | ||
boxedProxy.save(); | ||
expect([getCalled, saveCalled]).toEqual([1,0]); | ||
expect([getCalled, saveCalled]).toEqual([1, 0]); | ||
}); | ||
@@ -250,11 +246,11 @@ }); | ||
beforeAll(() => { | ||
origVal = { state: { } }; | ||
onDemand = { }; | ||
origVal = { state: {} }; | ||
onDemand = {}; | ||
saveCalled = 0; | ||
getCalled = 0; | ||
let vals = createBoxed(()=>{ | ||
let vals = createOnDemandBoxed(() => { | ||
getCalled++; | ||
return onDemand.state; | ||
}, (modified, boxed)=>{ | ||
return onDemand.state; | ||
}, (modified, boxed) => { | ||
saveCalled++; | ||
@@ -271,25 +267,25 @@ origVal.state = modified; | ||
let boxed = boxedProxy._$; | ||
expect([getCalled, saveCalled]).toEqual([1,0]); | ||
expect([getCalled, saveCalled]).toEqual([1, 0]); | ||
expect(boxedProxy._$).toBe(boxed); | ||
expect([getCalled, saveCalled]).toEqual([1,0]); | ||
expect([getCalled, saveCalled]).toEqual([1, 0]); | ||
expect(boxedProxy._$).toBe(boxed); | ||
expect([getCalled, saveCalled]).toEqual([1,0]); | ||
expect(origVal).toEqual({state: {}, }); | ||
expect([getCalled, saveCalled]).toEqual([1, 0]); | ||
expect(origVal).toEqual({ state: {}, }); | ||
boxedProxy._$.simple = 0; | ||
expect(origVal).toEqual({state: {}, }); | ||
expect([getCalled, saveCalled]).toEqual([1,0]); | ||
expect(origVal).toEqual({ state: {}, }); | ||
expect([getCalled, saveCalled]).toEqual([1, 0]); | ||
boxedProxy.save(); | ||
expect([getCalled, saveCalled]).toEqual([1,1]); | ||
expect(origVal).toEqual({state: {simple: 0}, delta: {simple: 0}, deepDelta: {simple: 0}, }); | ||
expect([getCalled, saveCalled]).toEqual([1, 1]); | ||
expect(origVal).toEqual({ state: { simple: 0 }, delta: { simple: 0 }, deepDelta: { simple: 0 }, }); | ||
let boxed2 = boxedProxy._$; | ||
expect([getCalled, saveCalled]).toEqual([2,1]); | ||
expect([getCalled, saveCalled]).toEqual([2, 1]); | ||
expect(boxed2).not.toBe(boxed); | ||
expect(boxedProxy._$).toBe(boxed2); | ||
expect([getCalled, saveCalled]).toEqual([2,1]); | ||
expect([getCalled, saveCalled]).toEqual([2, 1]); | ||
expect(boxedProxy._$).toBe(boxed2); | ||
expect([getCalled, saveCalled]).toEqual([2,1]); | ||
expect([getCalled, saveCalled]).toEqual([2, 1]); | ||
boxedProxy.save(); | ||
expect([getCalled, saveCalled]).toEqual([2,1]); | ||
expect([getCalled, saveCalled]).toEqual([2, 1]); | ||
}); | ||
@@ -304,8 +300,8 @@ }); | ||
beforeEach(() => { | ||
origVal = { state: { } }; | ||
onDemand = { }; | ||
origVal = { state: {} }; | ||
onDemand = {}; | ||
let vals = createBoxed(()=>{ | ||
return onDemand.state; | ||
}, (modified, boxed)=>{ | ||
let vals = createOnDemandBoxed(() => { | ||
return onDemand.state; | ||
}, (modified, boxed) => { | ||
origVal.state = modified; | ||
@@ -321,6 +317,6 @@ origVal.delta = boxed.delta$_$; | ||
boxedProxy._$.simple = 0; | ||
expect(origVal).toEqual({state: {}}); | ||
expect(origVal).toEqual({ state: {} }); | ||
boxedProxy.save(); | ||
expect(origVal).toEqual({state: {simple: 0}, delta: {simple: 0}, deepDelta: {simple: 0}, }); | ||
expect(origVal).toEqual({ state: { simple: 0 }, delta: { simple: 0 }, deepDelta: { simple: 0 }, }); | ||
}); | ||
@@ -332,6 +328,6 @@ | ||
boxedProxy._$.simple = 2; | ||
expect(origVal).toEqual({state: {}}); | ||
expect(origVal).toEqual({ state: {} }); | ||
expect(boxedProxy._$).toBe(boxed); | ||
boxedProxy._$.simple = 1; | ||
expect(origVal).toEqual({state: {}}); | ||
expect(origVal).toEqual({ state: {} }); | ||
expect(boxedProxy._$).toBe(boxed); | ||
@@ -343,3 +339,3 @@ boxedProxy._$.simple = 0; | ||
expect(origVal).toEqual({state: {simple: 0}, delta: {simple: 0}, deepDelta: {simple: 0}, }); | ||
expect(origVal).toEqual({ state: { simple: 0 }, delta: { simple: 0 }, deepDelta: { simple: 0 }, }); | ||
}); | ||
@@ -350,9 +346,9 @@ | ||
boxedProxy.save(); | ||
expect(origVal).toEqual({state: {simple: 2}, delta: {simple: 2}, deepDelta: {simple: 2}, }); | ||
expect(origVal).toEqual({ state: { simple: 2 }, delta: { simple: 2 }, deepDelta: { simple: 2 }, }); | ||
boxedProxy._$.simple = 1; | ||
boxedProxy.save(); | ||
expect(origVal).toEqual({state: {simple: 1}, delta: {simple: 1}, deepDelta: {simple: 1}, }); | ||
expect(origVal).toEqual({ state: { simple: 1 }, delta: { simple: 1 }, deepDelta: { simple: 1 }, }); | ||
boxedProxy._$.simple = 0; | ||
boxedProxy.save(); | ||
expect(origVal).toEqual({state: {simple: 0}, delta: {simple: 0}, deepDelta: {simple: 0}, }); | ||
expect(origVal).toEqual({ state: { simple: 0 }, delta: { simple: 0 }, deepDelta: { simple: 0 }, }); | ||
}); | ||
@@ -362,14 +358,14 @@ | ||
boxedProxy._$.simple = 2; | ||
expect(origVal).toEqual({state: {}}); | ||
expect(origVal).toEqual({ state: {} }); | ||
boxedProxy.cancel(); | ||
expect(origVal).toEqual({state: {}}); | ||
expect(origVal).toEqual({ state: {} }); | ||
boxedProxy._$.simple = 1; | ||
expect(origVal).toEqual({state: {}}); | ||
expect(origVal).toEqual({ state: {} }); | ||
boxedProxy.cancel(); | ||
expect(origVal).toEqual({state: {}}); | ||
expect(origVal).toEqual({ state: {} }); | ||
boxedProxy._$.simple = 0; | ||
expect(origVal).toEqual({state: {}}); | ||
expect(origVal).toEqual({ state: {} }); | ||
boxedProxy.save(); | ||
expect(origVal).toEqual({state: {simple: 0}, delta: {simple: 0}, deepDelta: {simple: 0}, }); | ||
expect(origVal).toEqual({ state: { simple: 0 }, delta: { simple: 0 }, deepDelta: { simple: 0 }, }); | ||
}); | ||
@@ -388,3 +384,3 @@ | ||
retVal.save(); | ||
expect(origVal).toEqual({"deepDelta": {"simple": 0}, "delta": {"simple": 0}, "state": {"simple": 0}}); | ||
expect(origVal).toEqual({ "deepDelta": { "simple": 0 }, "delta": { "simple": 0 }, "state": { "simple": 0 } }); | ||
}); | ||
@@ -397,5 +393,5 @@ | ||
retVal.save(); | ||
expect(origVal).toEqual({"deepDelta": {"simple": null}, "delta": {"simple": null}, "state": {"simple": null}}); | ||
expect(origVal).toEqual({ "deepDelta": { "simple": null }, "delta": { "simple": null }, "state": { "simple": null } }); | ||
}); | ||
}); | ||
@@ -6,2 +6,3 @@ # Version History | ||
- [0.2.0](#020) | ||
- [0.1.6](#016) | ||
@@ -16,2 +17,28 @@ - [0.1.5](#015) | ||
## 0.2.0 | ||
* Fix: forEach$ did not box item passed to callback. | ||
* Delete: `.forEach$` magic property. Was not debugged and not thought through. `.forEachKey$()` | ||
and `.forEachKey$_$()` instead. | ||
* Change: magic wrapped properties can be accessed with or without the parameter prefix/suffix | ||
wrapper. Having both was not relevant since the magic property would hide its unwrapped | ||
counterpart and only made mode typing necessary. | ||
Now `_$().unboxed$` and `_$().unboxed$_$` are the same. However, some magic properties have | ||
meaning attached to a parameter wrapped version. For example: `var_$.forEachKey$` provides | ||
`(key, _$var[key]` to callback and `_$().forEachKey$_$()` provides `(key, _$var[key + "_$"])`, | ||
the latter being the boxed property. Which makes sense based on the `_$` suffix convention of | ||
this library. | ||
Another is `default$(val)` sets to `val` if was undefined, returns unboxed value. | ||
`default$_$(val)` sets to `val` if was undefined, returns boxed proxy for chaining. Quick way | ||
to set defaults then proceed to modify them. | ||
* Add: `boolean$` magic, returns boolean of value, when you absolutely need a boolean and | ||
`undefined`, `null` and other stuff won't do. `_$().boolean$ = value` will convert `value` to | ||
boolean before setting. `boolean$` and `boolean$_$` are equivalent. | ||
* Change: `arrayDeepDeltaPartials` default to `true` | ||
## 0.1.6 | ||
@@ -28,20 +55,17 @@ | ||
* Fix: deep delta would not include properties for which proxy was | ||
created after the property was already modified in the parent. | ||
* Add: `default$_$` magic property which only changes value if it is | ||
`undefined`, otherwise a noop. Use: `boxed.field_$.default$_$ = | ||
value;` or `boxed.field_$.default$_$(value)` | ||
* Add: `boxOnDemand` proxy wrapper to allow creating a boxed state with | ||
`save()` and `cancel()` methods. It provides a new copy of the state | ||
if it has changed from last access. | ||
* Fix: deep delta would not include properties for which proxy was created after the property | ||
was already modified in the parent. | ||
* Add: `default$_$` magic property which only changes value if it is `undefined`, otherwise a | ||
noop. Use: `boxed.field_$.default$_$ = value;` or `boxed.field_$.default$_$(value)` | ||
* Add: `boxOnDemand` proxy wrapper to allow creating a boxed state with `save()` and `cancel()` | ||
methods. It provides a new copy of the state if it has changed from last access. | ||
## 0.1.3 | ||
* Fix: remove endsWith/startsWith on strings. Need to get babel and | ||
polyfill presets figured out. for now works in a react app. | ||
* Fix: remove endsWith/startsWith on strings. Need to get babel and polyfill presets figured | ||
out. for now works in a react app. | ||
## 0.1.2 | ||
* Fix: clean out node_modules to detect extraneous requires which are | ||
not used | ||
* Fix: clean out node_modules to detect extraneous requires which are not used | ||
@@ -48,0 +72,0 @@ ## 0.1.1 |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
152459
17
3371
369
1