Skip to content
This repository has been archived by the owner on Sep 25, 2020. It is now read-only.

add deepFreeze to make config truly immutable, limit clone alloc #37

Merged
merged 1 commit into from
Apr 12, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ type Config : {
set: ((keypath: Keypath, value: Any) => void) &
(value: Any) => void,
freeze: () => void,
deepFreeze: () => void,
clone: () => Config
getRemote: (keypath?: Keypath) => Any,
setRemote: ((keypath: Keypath, value: Any) => void) &
Expand Down Expand Up @@ -284,6 +285,17 @@ Once you are ready to stop mutating `config` you can call
Note that you can always call `config.setRemote()` as that is
not effected by `.freeze()`

#### `config.deepFreeze()`

A stricter from of freeze which actually recursively calls
Object.freeze() on the config object rendering it immutable.

In strict mode this will throw an error if calling code attempts
to mutate the returned config object. A side benefit of this is
that it enables config.get() to return the actual object instead
of a deep-copy, greatly reducing allocation pressure if your
application is fetching large objects out of the config repeatedly.

#### `config.clone()`

To get a deep clone of the config object, use `config.clone()`.
Expand Down
28 changes: 27 additions & 1 deletion config-wrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@ module.exports = ConfigWrapper;

function ConfigWrapper(configObject, loose) {
var frozen = false;
var deepFrozen = false;
// default `loose` to true.
loose = typeof loose === 'boolean' ? loose : true;

return {
get: configuredGet,
set: setKey,
freeze: freeze
freeze: freeze,
deepFreeze: deepFreeze
};

function getKey(keyPath) {
Expand Down Expand Up @@ -73,6 +75,12 @@ function ConfigWrapper(configObject, loose) {
frozen = true;
}

function deepFreeze() {
frozen = true;
deepFrozen = true;
deepFreezeObject(configObject);
}

function multiSet(obj) {
if (obj === null || typeof obj !== 'object') {
throw errors.InvalidMultiSetArgument({
Expand All @@ -89,6 +97,9 @@ function ConfigWrapper(configObject, loose) {
}

function safe(value) {
if (deepFrozen === true) {
return value;
}
safeCloneInto.value = null;
safeCloneFrom.value = value;
return deepExtend(safeCloneInto, safeCloneFrom).value;
Expand All @@ -99,3 +110,18 @@ function isValidKeyPath(keyPath) {
return typeof keyPath === 'string' ||
Array.isArray(keyPath);
}

function deepFreezeObject(o) {
Object.freeze(o);

Object.getOwnPropertyNames(o).forEach(function eachProp(prop) {
if (Object.hasOwnProperty.call(o, prop) &&
o[prop] !== null &&
(typeof o[prop] === 'object' || typeof o[prop] === 'function') &&
!Object.isFrozen(o[prop])) {

deepFreezeObject(o[prop]);
}
});
return o;
}
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ function fetchConfigSync(dirname, opts) {
config.get = localConfigWrapper.get;
config.set = localConfigWrapper.set;
config.freeze = localConfigWrapper.freeze;
config.deepFreeze = localConfigWrapper.deepFreeze;
config.clone = function(){
return ConfigWrapper(clone(configState));
};
Expand Down
31 changes: 31 additions & 0 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -722,6 +722,37 @@ test('config.freeze()', function t(assert) {
assert.end();
});

test('config.deepFreeze()', function t(assert) {
var config = fetchConfig(__dirname);

var deep = {bar: {baz: true}};

config.set('foo', deep);

assert.deepEqual(config.get('foo'), deep);

config.deepFreeze();

assert.throws(function () {
config.set('foo', 'notBaz');
}, /Config is frozen/);

assert.throws(function () {
config.set('bar', 'baz');
}, /Config is frozen/);

var immutable = config.get('foo');
immutable.bar.baz = false;
assert.equal(immutable.bar.baz, true);

assert.throws(function() {
'use strict';
immutable.bar.baz = false;
}, /Cannot assign to read only property/);

assert.end();
});

test('config.clone()', function t(assert) {
var config = fetchConfig(__dirname);

Expand Down