boxed-immutable
Advanced tools
Comparing version 0.1.3 to 0.1.4
'use strict'; | ||
const boxed = require('./lib/boxed-immutable'); | ||
const boxedOnDemand = require('./lib/boxed-on-demand'); | ||
exports._$ = boxed.box; | ||
exports.createBox = boxed.createBox; | ||
exports.boxedOnDemand = boxedOnDemand.boxedOnDemand; | ||
// mostly for texting | ||
// mostly for testing | ||
exports.Boxed = boxed.Boxed; | ||
exports.BoxedOnDemand = boxedOnDemand.BoxedOnDemand; | ||
exports.BOXED_GET_THIS = boxed.BOXED_GET_THIS; |
@@ -49,2 +49,8 @@ /** internal | ||
}, | ||
default: { | ||
get: function () {return this.setDefaultValue;}, | ||
set: function (value) {return this.setDefaultValue(value);}, | ||
delete: function () {return false;}, | ||
ownPropertyDescriptor: function () {return { value: this.value, writable: true, /*enumerable: false, configurable: false, get:UNDEFINED, set:UNDEFINED,*/ };}, | ||
}, | ||
"": { // $boxed with after stripping off prefix/suffix _$ | ||
@@ -292,2 +298,5 @@ get: function () {return this.proxiedThis;}, | ||
let boxed = new Boxed(value, this, prop), BoxedHandler; | ||
if (this.modifiedProps.hasOwnProperty(prop)) { | ||
boxed.isCopy = true; | ||
} | ||
this.boxedProps[prop] = boxed; | ||
@@ -455,13 +464,13 @@ boxedProxy(boxed); | ||
this.value = value; | ||
this.isCopy = true; | ||
this.isFullCopy = true; | ||
// detach all boxed props and clear modified props, we don't need them. | ||
this.detachAllProps(); | ||
this.modifiedProps = {}; | ||
if (this.parent && this.prop) { | ||
// set in parent so we are invalidated | ||
this.parent.set(this.prop, value); | ||
} else { | ||
this.value = value; | ||
this.isCopy = true; | ||
this.isFullCopy = true; | ||
// detach all boxed props and clear modified props, we don't need them. | ||
this.detachAllProps(); | ||
this.modifiedProps = {}; | ||
this.parent.set(this.prop, value, this); | ||
} | ||
@@ -496,2 +505,7 @@ } | ||
// only set value if undefined | ||
Boxed.prototype.setDefaultValue = function (value) { | ||
return this.value !== UNDEFINED || value === UNDEFINED || this.setValueOf(value); | ||
}; | ||
// if value is object or array: return only first level children that were modified or UNDEFINED if no mods | ||
@@ -786,2 +800,3 @@ // if otherwise same as valueOfModified return value if modified or UNDEFINED if not modified | ||
function boxedProxy(boxed) { | ||
boxed.setDefaultValue = boxed.setDefaultValue.bind(boxed); | ||
boxed.boxedWith = boxed.boxedWith.bind(boxed); | ||
@@ -832,2 +847,3 @@ boxed.boxedWith[BOXED_GET_THIS] = boxed; | ||
context.globalBox = globalBoxKey; | ||
box.boxedContext = context; | ||
return box; | ||
@@ -834,0 +850,0 @@ } |
{ | ||
"name": "boxed-immutable", | ||
"version": "0.1.3", | ||
"version": "0.1.4", | ||
"private": false, | ||
@@ -5,0 +5,0 @@ "description": "Immutable proxy wrapper with auto-vivification", |
124
README.md
@@ -97,4 +97,4 @@ # boxed-immutable | ||
empty["_$"] = 5; | ||
empty[_$] = 5; | ||
empty._$ = 5; // if you know its empty can also do empty[0] however if you are wrong then the latter will overwrite the value at 0 | ||
empty[_$] = 5; // may not work, depends on how functions is converted to string | ||
empty._$ = 5; // simplest and safest alternative | ||
// result: [5] | ||
@@ -143,6 +143,7 @@ | ||
// the same can be achieved with, without having to increment the index | ||
let obj = _$({}); | ||
obj.prop = "a"; | ||
obj[_$] = 5; | ||
obj[_$] = 15; | ||
obj[_$] = 25; | ||
obj._$ = 5; | ||
obj._$ = 15; | ||
obj._$ = 25; | ||
``` | ||
@@ -166,5 +167,5 @@ | ||
obj.$_field_$.subField = 4; | ||
obj.$_prop_$[$__$] = "a"; | ||
obj.$_prop_$[$__$] = "b"; | ||
obj.$_prop_$[$__$] = "c"; | ||
obj.$_prop_$.$__$ = "a"; | ||
obj.$_prop_$.$__$ = "b"; | ||
obj.$_prop_$.$__$ = "c"; | ||
@@ -198,2 +199,4 @@ let result = obj.$_unboxed$_$; | ||
### boxed-immutable box | ||
Change `_$` to your combination of prefix/suffix if modifying defaults. | ||
@@ -212,2 +215,3 @@ | ||
| `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 | | ||
@@ -235,5 +239,5 @@ | `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 | | ||
:warning: When a value is set on the parent collection it orphans the boxed state for | ||
all the properties of the parent for which you kept reference. These detached properties will | ||
still work but only on their own copy of the data since they are now detached from the root. | ||
:warning: When a value is set on the parent collection it orphans the boxed state for all the | ||
properties of the parent for which you kept reference. These detached properties will still work | ||
but only on their own copy of the data since they are now detached from the root. | ||
@@ -246,5 +250,5 @@ For example this will happen when you do something like: | ||
let nested = boxed.level1_$.level2_$.level3_$; | ||
nested[_$] = 0; | ||
nested[_$] = 1; | ||
nested[_$] = 2; | ||
nested._$ = 0; | ||
nested._$ = 1; | ||
nested._$ = 2; | ||
// boxed is now: { level1: { level2: { level3: [0,1,2]}}}; | ||
@@ -254,5 +258,5 @@ | ||
nested[_$] = 3; | ||
nested[_$] = 4; | ||
nested[_$] = 5; | ||
nested._$ = 3; | ||
nested._$ = 4; | ||
nested._$ = 5; | ||
// nested is [0,1,2,3,4,5] | ||
@@ -262,2 +266,88 @@ // boxed is still: { level1: { level2: { level3: [0,1,2]}}}; | ||
### boxed-immutable boxedOnDemand | ||
Provides a boxed proxy to immutable state, with `.save()` and `.cancel()` methods for saving or | ||
canceling state changes. With minimal code this allows transparent access to current state | ||
without the callers worrying about stale data or how to apply the changes back to the state | ||
holder. | ||
```javascript | ||
const boxedOnDemand = require('boxed-immutable').boxedOnDemand; | ||
// your current function for returning immutable state object | ||
function getSimpleState() { | ||
} | ||
let stateHolder; | ||
// your current function for setting a new state | ||
function saveState(newState) { | ||
// 1. update state | ||
// 2. regardless of how this function is called, cancel the boxed on demand so next access will be forced to get a fresh state | ||
stateHolder.cancel(); | ||
} | ||
// wrap in proxy so boxed state can be provided automatically | ||
// use in new getState to get boxed on demand | ||
stateHolder = boxedOnDemand(undefined, () => { | ||
return getSimpleState(); | ||
}, (modified, boxed) => { | ||
// call the save state function to apply changes in full | ||
saveState(modified); | ||
// or if your saveState can handle delta | ||
//saveState(boxed.delta$_$); | ||
// or anything else without having to change the users of your state API | ||
}); | ||
// use new function to provide access to now boxed immutable state | ||
function getState() { | ||
return stateHolder; | ||
} | ||
// somewhere in the code. | ||
let state = getState(); | ||
// can use state as before or as boxed state, except now there is no need to get a new state | ||
// every time. The same state will reflect latest changes. Make a copy if you need immutable state | ||
// NOTE: now it is not immutable, make a copy of its .unboxed$_$ property if you need an immutable copy | ||
// make changes | ||
// saving is handled by the provider instead of having the caller know how to update state | ||
state.save(); // save changes | ||
state.cancel(); // discard changes | ||
// next access to any properties of the boxed state will get a fresh copy to eliminate invalid state content after update/cancel | ||
``` | ||
| Property | Get | Set | Delete | Call | | ||
|:---------|:---------|:------|:-------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------| | ||
| `save` | function | error | error | calls the saveState callback passed to boxedOnDemand 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 | | ||
#### boxedOnDemand(getState, saveState, options) | ||
```javascript | ||
const boxedOnDemand = require('boxed-immutable').boxedOnDemand; | ||
const onDemandState = boxedOnDemand(); | ||
``` | ||
Used to construct a new boxed on demand proxy. | ||
| argument | default | Description | | ||
|:------------|:--------|:-----------------------------------------------------------------------------------------------------------------------------------------| | ||
| `getState` | none | callback to call to obtain the current state | | ||
| `saveState` | none | callback to call on save operation, returned result passed back to caller of `save()`. | | ||
| `options` | box | options to use. Can be a box as provided by boxedImmutable.box or boxedImmutable.createBox(), then all other options are set to defaults | | ||
| Option | Default | Description | | ||
|:-------------------|:-----------|:-------------------------------------------------------------------------------------------------------------------------------------------------| | ||
| `box`: | global box | Which box creation to use for each new boxed state object | | ||
| `saveBoxedProp`: | 'save' | name of the `save` property to use, allows changing of the function to 'commit' or something that will not conflict with your state's properties | | ||
| `cancelBoxedProp`: | 'cancel' | name of the `cancel` property to use, allows changing of the function to something that will not conflict with your state's properties | | ||
| `wrapProps`: | false | if true will wrap the saveBoxedProp and cancelBoxedProp the same as magical properties of the boxed object created from the box | | ||
## License | ||
@@ -264,0 +354,0 @@ |
@@ -230,1 +230,84 @@ "use strict"; | ||
describe('Multiple modifications of same field', () => { | ||
let origVal; | ||
let boxedVal; | ||
let boxedProxy; | ||
let expectedValue; | ||
let deltaValue; | ||
let deepDeltaValue; | ||
beforeAll(() => { | ||
let vals = createBoxed({field: ""}); | ||
origVal = vals.origVal; | ||
boxedVal = vals.boxedVal; | ||
boxedProxy = vals.boxedProxy; | ||
boxedProxy.field_$ = 1; | ||
boxedProxy.field_$ = 2; | ||
boxedProxy.field_$ = 3; | ||
expectedValue = {field: 3}; | ||
deepDeltaValue = deltaValue = expectedValue; | ||
}); | ||
test('value == modified', () => { | ||
const value = boxedVal.value; | ||
expect(value).toEqual(expectedValue); | ||
}); | ||
test('modified$_$ is value', () => { | ||
expect(boxedProxy.modified$_$).toEqual(boxedVal.value); | ||
}); | ||
test('delta$_$ == delta', () => { | ||
expect(boxedProxy.delta$_$).toEqual(deltaValue); | ||
}); | ||
test('deepDelta$_$ == deepDelta', () => { | ||
expect(boxedProxy.deepDelta$_$).toEqual(deepDeltaValue); | ||
}); | ||
}); | ||
describe('Default Value setting', () => { | ||
let origVal; | ||
let boxedVal; | ||
let boxedProxy; | ||
let expectedValue; | ||
let deltaValue; | ||
let deepDeltaValue; | ||
beforeAll(() => { | ||
let vals = createBoxed({field1: ""}); | ||
origVal = vals.origVal; | ||
boxedVal = vals.boxedVal; | ||
boxedProxy = vals.boxedProxy; | ||
boxedProxy.field1_$.default$_$ = 1; | ||
boxedProxy.field2_$.default$_$ = 2; | ||
boxedProxy.field2_$.default$_$ = 3; | ||
boxedProxy.field3_$.default$_$(1); | ||
boxedProxy.field3_$.default$_$(2); | ||
boxedProxy.field3_$.default$_$(3); | ||
expectedValue = {field1: "", field2: 2, field3:1, }; | ||
deepDeltaValue = deltaValue = {field2: 2, field3:1, }; | ||
}); | ||
test('value == modified', () => { | ||
const value = boxedVal.value; | ||
expect(value).toEqual(expectedValue); | ||
}); | ||
test('modified$_$ is value', () => { | ||
expect(boxedProxy.modified$_$).toEqual(boxedVal.value); | ||
}); | ||
test('delta$_$ == delta', () => { | ||
expect(boxedProxy.delta$_$).toEqual(deltaValue); | ||
}); | ||
test('deepDelta$_$ == deepDelta', () => { | ||
expect(boxedProxy.deepDelta$_$).toEqual(deepDeltaValue); | ||
}); | ||
}); | ||
@@ -6,2 +6,3 @@ # Version History | ||
- [0.1.4](#014) | ||
- [0.1.3](#013) | ||
@@ -13,2 +14,11 @@ - [0.1.2](#012) | ||
## 0.1.4 | ||
* 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: `boxedOnDemand` 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 | ||
@@ -15,0 +25,0 @@ |
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
120460
12
2793
349