diff --git a/md-variables.json b/md-variables.json index 568053e97..098c93f4c 100644 --- a/md-variables.json +++ b/md-variables.json @@ -532,6 +532,17 @@ "unique(['a', 'c', 'b', 'c', 'a'], false, true); // ['a', 'b', 'c']" ] }, + "just-upsert": { + "packageName": "just-upsert", + "dir": "array-upsert", + "description": "Upsert (update or insert) a value into an array at a target index", + "examples": [ + "import upsert from 'just-upsert';", + "", + "upsert([1,2,3,4],-1,2) // [1,2,-1,4]", + "upsert(['a','b','c'],'d',6) // ['a','b','c','d']" + ] + }, "just-variance": { "packageName": "just-variance", "dir": "array-variance", diff --git a/packages/array-upsert/LICENSE b/packages/array-upsert/LICENSE new file mode 100644 index 000000000..5d2c6e577 --- /dev/null +++ b/packages/array-upsert/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 angus croll + +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/packages/array-upsert/README.md b/packages/array-upsert/README.md new file mode 100644 index 000000000..262085add --- /dev/null +++ b/packages/array-upsert/README.md @@ -0,0 +1,25 @@ + + + +## just-upsert + +Part of a [library](https://anguscroll.com/just) of zero-dependency npm modules that do just do one thing. +Guilt-free utilities for every occasion. + +[`🍦 Try it`](https://anguscroll.com/just/just-upsert) + +```shell +npm install just-upsert +``` +```shell +yarn add just-upsert +``` + +Upsert (update or insert) a value into an array at a target index + +```js +import upsert from 'just-upsert'; + +upsert([1,2,3,4],-1,2) // [1,2,-1,4] +upsert(['a','b','c'],'d',6) // ['a','b','c','d'] +``` diff --git a/packages/array-upsert/index.cjs b/packages/array-upsert/index.cjs new file mode 100644 index 000000000..c6644c9b3 --- /dev/null +++ b/packages/array-upsert/index.cjs @@ -0,0 +1,22 @@ +module.exports = upsert; + +/** + * upsert([1,2,3,4], -1,2) // [1,2,-1,4] + * upsert(["a","b","c"], "d", 6) // ["a","b","c","d"] + */ +function upsert(arr, newItem, targetIndex) { + var result = arr.slice(); + + if (typeof targetIndex !== 'number') { + throw new Error('third parameter must be a number'); + } + + if (targetIndex < 0 || targetIndex >= result.length) { + // If target index is outside of the array, push the new item to the end. + result.push(newItem); + } else { + result[targetIndex] = newItem; + } + + return result; +} diff --git a/packages/array-upsert/index.d.ts b/packages/array-upsert/index.d.ts new file mode 100644 index 000000000..59f3a599e --- /dev/null +++ b/packages/array-upsert/index.d.ts @@ -0,0 +1,3 @@ +declare function upsert(arr: T[], newValue: T, targetIndex: number): T[]; + +export default upsert; diff --git a/packages/array-upsert/index.mjs b/packages/array-upsert/index.mjs new file mode 100644 index 000000000..ee496b8ee --- /dev/null +++ b/packages/array-upsert/index.mjs @@ -0,0 +1,24 @@ +var arrayUpsert = upsert; + +/** + * upsert([1,2,3,4],-1,2) // [1,2,-1,4] + * upsert(["a","b","c"],"d",6) // ["a","b","c","d"] + */ +function upsert(arr, newItem, targetIndex) { + var result = arr.slice(); + + if (typeof targetIndex !== 'number') { + throw new Error('third parameter must be a number'); + } + + if (targetIndex < 0 || targetIndex >= result.length) { + // If target index is outside of the array, push the new item to the end. + result.push(newItem); + } else { + result[targetIndex] = newItem; + } + + return result; +} + +export {arrayUpsert as deafult}; diff --git a/packages/array-upsert/index.tests.ts b/packages/array-upsert/index.tests.ts new file mode 100644 index 000000000..699c08653 --- /dev/null +++ b/packages/array-upsert/index.tests.ts @@ -0,0 +1,22 @@ +import upsert from './index'; + +upsert([1, 2, 3], 1, 22); +upsert([1, 2, 3], 22, 10); +upsert([], 'a', 1); +upsert(['a', 'b', 'c'], 5, 2); + +// @ts-expect-error +upsert(); + +// @ts-expect-error +upsert([1, 2, 3]); + +// @ts-expect-error +upsert([1, 2, 3], 5); + +// @ts-expect-error +upsert([1, 2, 3], 5, 'a'); + +// @ts-expect-error +upsert(['a', 'b', 'c'], 5, 2); + diff --git a/packages/array-upsert/package.json b/packages/array-upsert/package.json new file mode 100644 index 000000000..f5bda9bfa --- /dev/null +++ b/packages/array-upsert/package.json @@ -0,0 +1,34 @@ +{ + "name": "just-upsert", + "version": "1.0.0", + "description": "Upsert (update or insert) a value into an array at a target index", + "type": "module", + "exports": { + ".": { + "types": "./index.d.ts", + "require": "./index.cjs", + "import": "./index.mjs" + }, + "./package.json": "./package.json" + }, + "main": "index.cjs", + "types": "index.d.ts", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "build": "rollup -c" + }, + "repository": "https://github.com/angus-c/just", + "keywords": [ + "array", + "upsert", + "update", + "insert", + "no-dependencies", + "just" + ], + "author": "Thomas Clark", + "license": "MIT", + "bugs": { + "url": "https://github.com/angus-c/just/issues" + } +} diff --git a/packages/array-upsert/rollup.config.js b/packages/array-upsert/rollup.config.js new file mode 100644 index 000000000..fb9d24a3d --- /dev/null +++ b/packages/array-upsert/rollup.config.js @@ -0,0 +1,3 @@ +const createRollupConfig = require('../../config/createRollupConfig'); + +module.exports = createRollupConfig(__dirname); diff --git a/test/array-upsert/index.js b/test/array-upsert/index.js new file mode 100644 index 000000000..db1623c08 --- /dev/null +++ b/test/array-upsert/index.js @@ -0,0 +1,223 @@ +const upsert = require('../../packages/array-upsert'); +const test = require('../util/test')(__filename); + +test('Basic', function(t) { + t.plan(2); + + var input = [1, 2, 3, 4]; + var result = upsert(input, -1, 2); + t.deepEqual(result, [1, 2, -1, 4]); + t.notDeepEqual(input, result); // Check input was not mutated + + t.end(); +}); + +test('Upsert to first/last index', function(t) { + t.plan(4); + + var input = ['a', 'b', 'c', 'd']; + var firstIndexResult = upsert(input, 'a+', 0); + var lastIndexResult = upsert(input, 'd+', 3); + t.deepEqual(firstIndexResult, ['a+', 'b', 'c', 'd']); + t.deepEqual(lastIndexResult, ['a', 'b', 'c', 'd+']); + t.notDeepEqual(input, firstIndexResult); + t.notDeepEqual(input, lastIndexResult); + + t.end(); +}); + +test('Target index outside of input', function(t) { + t.plan(4); + + var input = ['a', 'b', 'c', 'd']; + var justOutsideResult = upsert(input, 'd+', 4); + var veryFarOutResult = upsert(input, 'd++', 9999999); + + t.deepEqual(justOutsideResult, ['a', 'b', 'c', 'd', 'd+']); + t.deepEqual(veryFarOutResult, ['a', 'b', 'c', 'd', 'd++']); + t.notDeepEqual(input, justOutsideResult); + t.notDeepEqual(input, veryFarOutResult); + + t.end(); +}); + +test('Negative target index', function(t) { + t.plan(4); + + var input = [0, 1, 2, 3, 4]; + var smallNegativeResult = upsert(input, 'a', -1); + var largeNegativeResult = upsert(input, 'b', -99999); + + t.deepEqual(smallNegativeResult, [0, 1, 2, 3, 4, 'a']); + t.deepEqual(largeNegativeResult, [0, 1, 2, 3, 4, 'b']); + t.notDeepEqual(input, smallNegativeResult); + t.notDeepEqual(input, largeNegativeResult); + + t.end(); +}); + +test('Empty input array', function(t) { + t.plan(2); + + var input = []; + var result = upsert(input, new Date(1), 0); + + t.deepEqual(result, [new Date(1)]); + t.notDeepEqual(input, result); + + t.end(); +}); + +test('Objects', function(t) { + t.plan(4); + + var input = [ + { + 'J': 131234188, + '': { + 'h': 425294562, + 'V': '', + }, + }, + { + '&': false, + m: { + '': true, + '&a': 'H=', + }, + }, + { + ",]'": '', + '4~': true, + }, + ]; + var updateResult = upsert(input, { + '#': -1303827106, + 'Q=': '', + }, 1); + var pushResult = upsert(input, { + '1': -867336401.0513263, + 'If': true, + }, 8); + + t.deepEqual(updateResult, [ + { + 'J': 131234188, + '': { + 'h': 425294562, + 'V': '', + }, + }, + { + '#': -1303827106, + 'Q=': '', + }, + { + ",]'": '', + '4~': true, + }, + ]); + t.deepEqual(pushResult, [ + { + 'J': 131234188, + '': { + 'h': 425294562, + 'V': '', + }, + }, + { + '&': false, + m: { + '': true, + '&a': 'H=', + }, + }, + { + ",]'": '', + '4~': true, + }, + { + '1': -867336401.0513263, + 'If': true, + }, + ]); + t.notDeepEqual(input, updateResult); + t.notDeepEqual(input, pushResult); + + t.end(); +}); + +test('Arrays', function(t) { + t.plan(2); + + var input = [ + [ + '', + { + '': true, + Hb: true, + }, + ], + [true, false], + [-1134375265.6151595], + ]; + + var updateResult = upsert(input, [12, 34], 1); + + t.deepEqual(updateResult, [ + [ + '', + { + '': true, + Hb: true, + }, + ], + [12, 34], + [-1134375265.6151595], + ]); + t.notDeepEqual(input, updateResult); + + t.end(); +}); + +test('Update with empty array/object', function(t) { + t.plan(4); + + var arrayInput = [[1, 2], ['asf', 'ar'], []]; + var objectInput = [{a: 1}, {b: 2}, {}]; + + var arrayResult = upsert(arrayInput, [], 1); + var objectResult = upsert(objectInput, {}, 1); + + t.deepEqual(arrayResult, [[1, 2], [], []]); + t.deepEqual(objectResult, [{a: 1}, {}, {}]); + t.notDeepEqual(arrayInput, arrayResult); + t.notDeepEqual(objectInput, objectResult); + + t.end(); +}); + +test('Errors', function(t) { + t.plan(11); + + t.comment('non-arrays as first param'); + t.throws(() => upsert(null, 'a'), 0); + t.throws(() => upsert(null, 5), -100); + t.throws(() => upsert(2, 5), 60); + t.throws(() => upsert('a', 5), 1); + + t.comment('object as first param'); + t.throws(() => upsert({}, 5), 2); + + t.comment('non-number target indexes'); + t.throws(() => upsert([1, 3], 5, 'a'), 2); + t.throws(() => upsert([1, 3], 5, new Date()), 2); + t.throws(() => upsert([1, 3], 5, {}), 2); + t.throws(() => upsert([1, 3], 5, []), 2); + + t.comment('not enough parameters'); + t.throws(() => upsert([])); + t.throws(() => upsert()); + + t.end(); +});