diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..15ad7a6 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2017 Sam Gluck + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index 8c05cb4..3c3e9db 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@

mewt

-

:seedling: Immutability in under a kilobyte

+

Immutability in under one kilobyte

Made with ❤ at @outlandish

@@ -14,7 +14,11 @@
-:cookie: Under 1kb unminified. Zero dependencies. +:seedling: Under 1kb (unminified), tiny API, zero dependencies. + +:+1: Makes all native array methods immutable operations. + +:v: Two simple methods `$set` and `$unset` for objects and arrays. :point_right: Built for Node ES2015 environments. Use a bundler, transpiler, and Proxy polyfill as required. @@ -42,45 +46,72 @@ var immutable = require('mewt') ## Usage -Create an immutable instance from a JavaScript array or object: +Create an immutable instance from a JavaScript array or object. + +Both objects and arrays have the `$set` and `$unset` methods. ```js -let immutableArray = immutable([]) -let immutableObect = immutable({}) +const immutableArray = immutable([]) +const immutableObect = immutable({}) + +immutableArray[0] = 'Van Morrison' //=> Error "array is immutable" +immutableObect.name = 'Van Morrison' //=> Error "object is immutable" ``` ### Array +Use `$set` and `$unset` to create new array with applied change. + +Use all array instance methods as usual, however those that would normally return a single +non-array value (pop, push, shift, unshift) will return an array containing the value and a new array +(see part 2 in example below). + ```js const arr = immutable([]) -// all array instance methods are available +// 1. all array instance methods are available const arr1 = arr.concat('bubble') - console.log(arr) //=> ['bubble'] + console.log(arr1) //=> ['bubble'] console.log(arr1 === arr) //=> false -// methods with non-array return value also return new -// array via destructuring (push, pop, shift, unshift) +// 2. methods with non-array return value (push, pop, shift, unshift) +// also return new array, accessible via destructuring const [val, arr2] = arr1.pop() console.log(val) //=> 'bubble' console.log(arr2) //=> [] console.log(arr2 === arr1) //=> false + +// 3. use $set and $unset to get new array with changes +const arr3 = arr2.$set(0, 'Iggy Pop') + + console.log(arr3) //=> ['Iggy Pop'] + console.log(arr3 === arr2) //=> false ``` ### Object +Use `$set` and `$unset` to create new object with applied change. + ```js const obj = immutable({}) -// properties are added/updated using `$set` -const obj1 = obj.set('album', 'Hunky Dory') +// 1. properties are added/updated using `$set` +const obj1 = obj.$set('album', 'Hunky Dory') console.log(obj1) //=> {album: 'Hunky Dory'} + console.log(obj1 === obj) //=> false -// properties are deleted using `$unset` +// 2. properties are deleted using `$unset` const obj2 = obj1.$unset('album') console.log(obj2) //=> {} + console.log(obj2 === obj1) //=> false ``` + +## Contributing + +All pull requests and issues welcome! + +If you're not sure how, check out the [great video tutorials on egghead.io](http://bit.ly/2aVzthz)! diff --git a/index.js b/index.js index e24db6e..5c10a70 100644 --- a/index.js +++ b/index.js @@ -1,22 +1,29 @@ -/** Create immutable instance of array or object. - * @returns {Array|Object} */ +/** @returns {Array|Object} */ module.exports = function mewt (target) { - let current = target - , isArr = Array.isArray(target) + let isA = Array.isArray(target) , multiRet = 'push pop shift unshift' - , clone = v => isArr ? [].concat(v) : Object.assign({}, v) + , clone = (v, soft) => (v = isA ? [].concat(v) : Object.assign({}, v), soft ? v : mewt(v)) - let override = fn => (...args) => { - let res = current[fn](...args) - current = clone(current) - return multiRet.includes(fn) ? [res, current] : current + let override = prop => (...args) => { + let res = target[prop](...args) + return multiRet.includes(prop) ? [res, clone(target)] : clone(res) } - if (!isArr && typeof target !== 'object') + let newObj, api = { + $set: (prop, val) => (newObj = clone(target, true), newObj[prop] = val, newObj), + $unset: prop => (newObj = clone(target, true), delete newObj[prop], newObj) + } + + if (!isA && typeof target !== 'object') throw new Error('mewt accepts array or object') return new Proxy(target, { - set: (_, prop, val) => (current = clone(current), current[prop] = val, true), - get: (_, prop) => current[prop] && (current.hasOwnProperty(prop) ? current[prop] : override(prop)) + set: () => { + throw new Error(`${isA ? 'array' : 'object'} is immutable`) + }, + get: (_, prop) => { + if (api[prop]) return api[prop] + return target[prop] && (target.hasOwnProperty(prop) ? target[prop] : override(prop)) + } }) } diff --git a/package.json b/package.json index b4b9b2a..0754ba0 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "mewt", "version": "1.0.0", - "description": "Immutability in under a kilobyte", + "description": "Immutability in under one kilobyte", "main": "index.js", "scripts": { "test": "node test" diff --git a/test.js b/test.js index 0facd06..0b6bb3c 100644 --- a/test.js +++ b/test.js @@ -20,6 +20,36 @@ test('array', (t) => { const n = mewt(a) t.notEqual(a, n) } + { + // throws on mutation + const a = mewt([]) + t.throws(() => a[0] = 'Lodger', /immutable/) + } + { + // has $set & $unset + const a = mewt([]) + t.equal(typeof a.$set, 'function') + t.equal(typeof a.$unset, 'function') + } + { + // get own property + const a = mewt(['']) + t.equal(a[0], '') + } + { + // $set + const a = mewt([]) + const n = a.$set(0, '') + t.deepEqual(n, ['']) + t.notEqual(a, n) + } + { + // $unset + const a = mewt(['']) + const n = a.$unset(0) + t.deepEqual(n, []) + t.notEqual(a, n) + } { // copyWithin const a = mewt([1, 2]) @@ -76,7 +106,7 @@ test('array', (t) => { // sort const a = mewt(['a', 'b', 'c']) const n = a.splice(0, 1) - t.deepEqual(n, ['b', 'c']) + t.deepEqual(n, ['a']) t.notEqual(a, n) } { @@ -98,9 +128,36 @@ test('object', (t) => { t.notEqual(o, n) } { - // - const o = {} - const n = mewt(o) + // throws on mutation + const o = mewt({}) + t.throws(() => o.track = 'The Promise', /immutable/) + } + { + // has $set & $unset + const o = mewt({}) + t.equal(typeof o.$set, 'function') + t.equal(typeof o.$unset, 'function') + } + { + // get own property + const o = mewt({album: 'Aladdin Sane'}) + t.equal(o.album, 'Aladdin Sane') + } + { + // $set + const o = mewt({}) + const n = o.$set('album', 'Hours') + console.log(o.album) + t.equal(o.album, undefined) + t.equal(n.album, 'Hours') + t.notEqual(o, n) + } + { + // $unset + const o = mewt({album: 'Heroes'}) + const n = o.$unset('album') + t.equal(o.album, 'Heroes') + t.equal(n.album, undefined) t.notEqual(o, n) } t.end()