Research
Security News
Malicious npm Packages Inject SSH Backdoors via Typosquatted Libraries
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
boxed-immutable
Advanced tools
Immutable proxy wrapper with auto-vivification of intermediate objects/arrays with syntactic sugar to keep access/modification to deeply nested properties looking almost the same as plain object property access.
Create a boxed-immutable object proxy then access and/or modify its nested properties, ignoring whether intermediate values are objects/arrays or whether they exist.
Original object/array are shallow copied on first modification (all the way back to the root collection), thereafter all mods are done on the copy. This occurs at every level so minimal copying is done on all modifications and any unmodified values are re-used.
Subsequent modifications will be done on a copy. Once a copy is made it is re-used for the life of the boxed object since it is detached from the original.
Access to object's original properties occurs in two ways:
TypeError
or ReferenceError
._$
appended to the end of it to get access to a proxy which
will auto-vivify the property container when the first property is set.Use option 1 to access leaf values in the object since JavaScript handles this nicely.
Use option 2 to access intermediate properties.
Easily customize the suffix/prefix combination that will not conflict with property names in your project's state objects.
Use npm to install.
npm install boxed-immutable --save
const _$ = require('boxed-immutable')._$;
function updateState(confirmationName, confirmationValue) {
let state = _$(this.getState());
state.fieldStatus = 5; // direct access to first level field, state will be copied if field's value !== 5
// can hold intermediate values for easy access
const confirmationData = state.properties_$.confirmations_$[confirmationName + "_$"]; // add _$ to get boxed proxy
confirmationData.count = 1 + (confirmationData.count || 0);
confirmationData.value = confirmationValue;
// get all the first level children to update state
this.setState(state.delta$);
// get only the changed leaf fields and their parents, minimal update image
this.setState(state.deepDelta$);
}
Use it to safely access deeply nested values without throwing TypeError
:
let boxed = _$(someValue);
// anything accessed via proxied properties (ending in _$) could be undefined, null, or anything that would throw a TypeError, the result is undefined
let caption = boxed.appState_$.dashboard_$[dashboardName + "_$"].captionText || 'default caption';
// would work just as well
let boxed = _$(undefined);
// everything could be completely empty, the end result is undefined
let caption = boxed.appState_$.dashboard_$[dashboardName + "_$"].captionText || 'default caption';
When setting properties on non-existent or non-array or object properties the proxy will auto-create the array or object, ie. auto-vivify it.
Auto-vivication takes the property name into account: integers whether numbers or numbers in
strings will create an Array
, everything else will create an Object
.
You can use the special end of array
value of "_$"
or just the global box _$
or ._$
property on any boxed value when setting an array property to have the value added to the end of
the array. Effectively, _$
is always equal to the length of the array.
let empty = _$();
// all are equivalent
empty[""] = 5;
empty["_$"] = 5;
empty[_$] = 5; // depending on how function is converted to string this may accept any function, not just box.
empty._$ = 5; // simplest and safest alternative
// result: [5]
empty._$ = 10;
// result: [5, 10]
empty._$ = 20;
let result = empty.unboxed$;
// result: [5, 10, 20]
The following will create an object:
let empty = _$();
// all are equivalent
empty["field"] = 5;
empty.field = 5;
// result: {field: 5}
empty._$ = 10;
// result: {0: 10, field: 5}
empty._$ = 20;
let result = empty.unboxed$;
// result: {0: 10, 1: 20, field: 5}
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.
let obj = {};
obj.prop = "a";
obj[0] = 5;
obj[1] = 15;
obj[2] = 25;
let result = obj.unboxed$;
// result: {0: 5, 1:15, 2:25, prop: "a"}
// the same can be achieved with, without having to increment the index
let obj = _$({});
obj.prop = "a";
obj._$ = 5;
obj._$ = 15;
obj._$ = 25;
You can change a couple of options on how the boxing handles properties whose value is
undefined
and modify the prefix/suffix used for accessing boxed properties or magic properties:
Use the createBox(options)
function from the module to create a boxing function with
non-default options:
const createBox = require('boxed-immutable').createBox;
const $__$ = createBox({prefixChars: "$_", suffixChars:"_$", magicPrefixChars: "", magicSuffixChars: "$$"});
// Now all your properties are wrapped and magic properties end with two $
let obj = $__$();
obj.$_field_$.subField = 4;
obj.$_prop_$.$__$ = "a";
obj.$_prop_$.$__$ = "b";
obj.$_prop_$.$__$ = "c";
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 : | 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 : [1] | "" | prefix for boxed properties. |
suffixChars : [1] | "_$" | suffix for boxed properties. |
magicPrefixChars : [1] | "" | prefix for magic properties, applied after prefixChars |
magicSuffixChars : [1] | "$" | suffix for magic properties, applied before suffixChars |
* [1]: Note use of [_$]
is not affected by prefixes or suffixes since the function is passed
as property, use of ["_$"]
and ._$
has to have the prefix/suffix used in the options for the
box context
Change _$
to your combination of prefix/suffix if modifying defaults.
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 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.
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
of creating an object then adding it to your modified state, you can use this option and benefit
from not worrying about immutability:
let boxed = _$(someState);
boxed.appState_$.dashboards_$.userData_$(_$ => {
// here _$ is boxed.dashboards_$.userData_$, get/set/call it or properties
if (condition) {
// fill
_$.undoList_$.modified_$(_$ => {
// here _$ is boxed.dashboards_$.userData_$.undoList_$.modified_$, get/set/call it or properties
});
}
});
: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.
For example this will happen when you do something like:
let boxed = _$();
let nested = boxed.level1_$.level2_$.level3_$;
nested._$ = 0;
nested._$ = 1;
nested._$ = 2;
// boxed is now: { level1: { level2: { level3: [0,1,2]}}};
boxed.level1_$.level2_$.level3 = [0,1,2]; // this will detach all boxed properties from level3 and below, like nested
nested._$ = 3;
nested._$ = 4;
nested._$ = 5;
// nested is [0,1,2,3,4,5]
// boxed is still: { level1: { level2: { level3: [0,1,2]}}};
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.
Applying partial changes to component's state is as easy as setting a value in boxedOnDemand
instance and invoking .save()
const boxOnDemand = require('boxed-immutable').boxOnDemand;
// 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 = boxOnDemand(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 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 |
const boxOnDemand = require('boxed-immutable').boxOnDemand;
const onDemandState = boxOnDemand();
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 |
MIT, see LICENSE.md for details.
FAQs
Immutable proxy wrapper with exception free access to nested properties and auto-instantiation of intermediate containers when nested properties are defined
We found that boxed-immutable demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
Security News
MITRE's 2024 CWE Top 25 highlights critical software vulnerabilities like XSS, SQL Injection, and CSRF, reflecting shifts due to a refined ranking methodology.
Security News
In this segment of the Risky Business podcast, Feross Aboukhadijeh and Patrick Gray discuss the challenges of tracking malware discovered in open source softare.