electrum-store
Advanced tools
Comparing version 1.12.0 to 2.0.0
@@ -14,2 +14,5 @@ 'use strict'; | ||
Object.freeze(emptyValues); | ||
Object.freeze(secretKey); | ||
function isPositiveInteger(value) { | ||
@@ -64,5 +67,10 @@ if (typeof value === 'number') { | ||
} | ||
if (values.length === 0) { | ||
if (isEmpty(values)) { | ||
values = emptyValues; // optimize for values set to {} | ||
} | ||
} else { | ||
Object.freeze(values); | ||
for (let key of Object.getOwnPropertyNames(values)) { | ||
State.freezeTop(values[key]); | ||
} | ||
} | ||
@@ -391,2 +399,25 @@ this._id = id; | ||
} | ||
}, { | ||
key: 'freeze', | ||
value: function freeze(obj) { | ||
if (Array.isArray(obj)) { | ||
obj.forEach(x => State.freeze(x)); | ||
} | ||
if (obj && typeof obj === 'object') { | ||
Object.freeze(obj); | ||
for (let key of Object.getOwnPropertyNames(obj)) { | ||
State.freeze(obj[key]); | ||
} | ||
} | ||
} | ||
}, { | ||
key: 'freezeTop', | ||
value: function freezeTop(obj) { | ||
if (Array.isArray(obj)) { | ||
obj.forEach(x => State.freezeTop(x)); | ||
} | ||
if (obj && typeof obj === 'object') { | ||
Object.freeze(obj); | ||
} | ||
} | ||
}]); | ||
@@ -393,0 +424,0 @@ |
{ | ||
"name": "electrum-store", | ||
"version": "1.12.0", | ||
"version": "2.0.0", | ||
"description": "Electrum store provides a store implementation tailored for Electrum.", | ||
@@ -5,0 +5,0 @@ "main": "lib/index.js", |
@@ -119,3 +119,3 @@ # Electrum Store | ||
expect (state.generation).to.equal (2); | ||
expect (state.shouldUpdate (1)).to.be.false (); // provided gen=1 older than state.generation | ||
expect (state.shouldUpdate (1)).to.be.false (); // provided gen=1 older than state.generation | ||
expect (state.shouldUpdate (2)).to.be.true (); | ||
@@ -140,3 +140,3 @@ expect (state.shouldUpdate (3)).to.be.true (); | ||
expect (store.select ('a.b').get ('name')).to.equal ('foo'); | ||
expect (store.select ('a.b.c').get ('value')).to.equal ('bar'); | ||
expect (store.select ('a.b.c').get ('value')).to.equal ('bar'); | ||
``` | ||
@@ -301,2 +301,31 @@ | ||
## Setting values freezes them | ||
When setting values on a state object, we do not want them to be | ||
mutated arbitrarily by an external source (at least at the top | ||
level). | ||
In order to prevent mutation of values stored in a state object, | ||
every value is automatically frozen (`Object.freeze()`): | ||
```javascript | ||
const state = State.create ('a').set ('x', {foo: 'bar'}); | ||
expect (Object.isFrozen (state.get ('x'))).to.be.true (); | ||
expect (() => state.get ('x').foo = 'baz').to.throw (); | ||
``` | ||
If the value is an array, the values of the array will also be | ||
frozen (recursively for sub-arrays). | ||
## Explicit freeze API | ||
The `State` class exposes two methods to explicitly freeze | ||
objects: | ||
* `State.freeze(obj)` → freezes recursively the full object | ||
tree. | ||
* `State.freezeTop(obj)` → freezes the object; if it is an | ||
array, then every item of the array will be frozen by calling | ||
`freezeTop()` recursively. | ||
## Mutate state in a store | ||
@@ -303,0 +332,0 @@ |
@@ -33,2 +33,10 @@ 'use strict'; | ||
}); | ||
it ('freezes the initial values', () => { | ||
const state = State.create ('a', {o: {foo: 'bar'}}); | ||
const ref = state.get ('o'); | ||
expect (ref).to.deep.equal ({foo: 'bar'}); | ||
expect (() => ref.foo = 'baz').to.throw (); | ||
expect (ref).to.deep.equal ({foo: 'bar'}); | ||
}); | ||
}); | ||
@@ -166,2 +174,49 @@ | ||
}); | ||
describe ('State.freeze()', () => { | ||
it ('accepts plain values', () => { | ||
expect (() => State.freeze (undefined)).to.not.throw (); | ||
expect (() => State.freeze (1)).to.not.throw (); | ||
expect (() => State.freeze ('hello')).to.not.throw (); | ||
expect (() => State.freeze (function (x) { | ||
return x; | ||
})).to.not.throw (); | ||
}); | ||
it ('freezes deeply an object', () => { | ||
const obj = {x: 1, y: {a: ['A', [{q: 'Q'}]]}}; | ||
State.freeze (obj); | ||
expect (Object.isFrozen (obj)).to.be.true (); | ||
expect (Object.isFrozen (obj.x)).to.be.true (); | ||
expect (Object.isFrozen (obj.y)).to.be.true (); | ||
expect (Object.isFrozen (obj.y.a)).to.be.true (); | ||
expect (Object.isFrozen (obj.y.a[0])).to.be.true (); | ||
expect (Object.isFrozen (obj.y.a[1])).to.be.true (); | ||
expect (Object.isFrozen (obj.y.a[1][0])).to.be.true (); | ||
expect (Object.isFrozen (obj.y.a[1][0].q)).to.be.true (); | ||
// Just to make sure... | ||
expect (() => obj.y.a[1][0].r = 'R').to.throw (); | ||
}); | ||
}); | ||
describe ('State.freezeTop()', () => { | ||
it ('freezes top level only', () => { | ||
const obj = {x: 1, y: {a: 'A'}}; | ||
State.freezeTop (obj); | ||
expect (Object.isFrozen (obj)).to.be.true (); | ||
expect (Object.isFrozen (obj.x)).to.be.true (); | ||
expect (Object.isFrozen (obj.y)).to.be.false (); | ||
expect (() => obj.y.a = 'B').to.not.throw (); | ||
}); | ||
it ('freezes top level only, but walks arrays', () => { | ||
const obj = [{x: 1, y: {a: 'A'}}]; | ||
State.freezeTop (obj); | ||
expect (Object.isFrozen (obj)).to.be.true (); | ||
expect (Object.isFrozen (obj[0])).to.be.true (); | ||
expect (Object.isFrozen (obj[0].x)).to.be.true (); | ||
expect (Object.isFrozen (obj[0].y)).to.be.false (); | ||
expect (() => obj[0].y.a = 'B').to.not.throw (); | ||
}); | ||
}); | ||
}); |
@@ -40,3 +40,3 @@ 'use strict'; | ||
describe ('set()', () => { | ||
it ('produces new instance of state', () => { | ||
it ('produces new instance of state (with key)', () => { | ||
const state1 = State.create ('a', {a: 1}); | ||
@@ -49,3 +49,3 @@ const state2 = state1.set ('b', 2); | ||
it ('produces new instance of state', () => { | ||
it ('produces new instance of state (without key)', () => { | ||
const state1 = State.create ('a', {'': 'x'}); | ||
@@ -56,2 +56,44 @@ const state2 = state1.set ('y'); | ||
}); | ||
it ('freezes an object value', () => { | ||
const state = State.create ('a').set ('x', {a: 1}); | ||
const ref = state.get ('x'); | ||
expect (ref).to.deep.equal ({a: 1}); | ||
expect (() => ref.a = 2).to.throw (); | ||
expect (ref).to.deep.equal ({a: 1}); | ||
}); | ||
it ('freezes an array value', () => { | ||
const state = State.create ('a').set ('x', [1, 2]); | ||
const ref = state.get ('x'); | ||
expect (ref).to.deep.equal ([1, 2]); | ||
expect (() => ref.a.push (3)).to.throw (); | ||
expect (ref).to.deep.equal ([1, 2]); | ||
}); | ||
it ('freezes the top level of an object', () => { | ||
const state = State.create ('a').set ('x', [{a: 1}, {b: {x: 2}}]); | ||
const ref = state.get ('x'); | ||
expect (Object.isFrozen (ref[0])).to.be.true (); | ||
expect (Object.isFrozen (ref[1])).to.be.true (); | ||
expect (Object.isFrozen (ref[1].b)).to.be.false (); | ||
expect (ref).to.deep.equal ([{a: 1}, {b: {x: 2}}]); | ||
expect (() => ref[0].a = 0).to.throw (); | ||
expect (() => ref[1].b = 0).to.throw (); | ||
expect (() => ref[1].b.x = 0).to.not.throw (); | ||
expect (ref).to.deep.equal ([{a: 1}, {b: {x: 0}}]); | ||
}); | ||
it ('freezes deeply arrays up to the top level objects', () => { | ||
const state = State.create ('a').set ('x', [{a: 1}, [{b: {x: 2}}]]); | ||
const ref = state.get ('x'); | ||
expect (Object.isFrozen (ref[0])).to.be.true (); | ||
expect (Object.isFrozen (ref[1])).to.be.true (); | ||
expect (Object.isFrozen (ref[1][0])).to.be.true (); | ||
expect (Object.isFrozen (ref[1][0].b)).to.be.false (); | ||
expect (ref).to.deep.equal ([{a: 1}, [{b: {x: 2}}]]); | ||
expect (() => ref[0].a = 0).to.throw (); | ||
expect (() => ref[1][0].b.x = 0).to.not.throw (); | ||
expect (ref).to.deep.equal ([{a: 1}, [{b: {x: 0}}]]); | ||
}); | ||
}); | ||
@@ -58,0 +100,0 @@ |
@@ -9,2 +9,5 @@ 'use strict'; | ||
Object.freeze (emptyValues); | ||
Object.freeze (secretKey); | ||
function isPositiveInteger (value) { | ||
@@ -55,4 +58,9 @@ if (typeof value === 'number') { | ||
} | ||
if (values.length === 0) { | ||
if (isEmpty (values)) { | ||
values = emptyValues; // optimize for values set to {} | ||
} else { | ||
Object.freeze (values); | ||
for (let key of Object.getOwnPropertyNames (values)) { | ||
State.freezeTop (values[key]); | ||
} | ||
} | ||
@@ -340,2 +348,23 @@ | ||
} | ||
static freeze (obj) { | ||
if (Array.isArray (obj)) { | ||
obj.forEach (x => State.freeze (x)); | ||
} | ||
if (obj && typeof obj === 'object') { | ||
Object.freeze (obj); | ||
for (let key of Object.getOwnPropertyNames (obj)) { | ||
State.freeze (obj[key]); | ||
} | ||
} | ||
} | ||
static freezeTop (obj) { | ||
if (Array.isArray (obj)) { | ||
obj.forEach (x => State.freezeTop (x)); | ||
} | ||
if (obj && typeof obj === 'object') { | ||
Object.freeze (obj); | ||
} | ||
} | ||
} | ||
@@ -342,0 +371,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
95062
2179
349