A modern utility library with a strong emphasis on readability. Make your code crystal clear.
Inspired by Jest Matchers.
Manipulating data is part and parcel of developing software, but the resulting code can quickly become difficult to read. You want to minimise the complexity of your codebase to ensure it's doing what you intended and have the confidence to make changes in the future.
Jest Matchers help make your tests easier to reason about. Crystalline takes this approach and applies it to your application code. It is a library of highly comprehensible functions that perform operations commonly found in code.
- Readable code is maintainable code.
- Write code as if you were writing a sentence.
- Don't reinvent the wheel when a readable native solution already exists.
- Favour brevity but not at the expense of readability.
NPM:
npm install crystalline
Yarn:
yarn add crystalline
Import specific modules to reduce the size of your bundle:
// ECMAScript modules
import { sort } from 'crystalline/arrays/sort';
// CommonJS
const { sort } = require('crystalline/arrays/sort');
sort(...);
Alternatively you can import the whole library:
// ECMAScript modules
import crystalline from 'crystalline';
// CommonJS
const crystalline = require('crystalline').default;
crystalline.arrays.sort(...);
The library organises its functions into categories based on the type of variable they primarily operate on. All functions within a category expect that type of variable as their first parameter. Functions are always pure. Vocabulary is reused across categories to reduce the learning curve.
-
alter
byApplying
-
atIndex
Create a new array by applying the function supplied at the given index.const input = ["a", "b", "c", "d"]; const result = alter(input) .byApplying((n) => n.toUpperCase()) .atIndex(1); expect(result).toEqual(["a", "B", "c", "d"]);
byInsertingBetweenEachItem
Create a new array with the value supplied inserted between each item.const input = ["b", "n", "n", "s"]; const result = alter(input).byInsertingBetweenEachItem("a"); expect(result).toEqual(["b", "a", "n", "a", "n", "a", "s"]);
byMovingItemAtIndex
-
toIndex
Create a new array with the item at the index specified moved to the chosen index.const input = ["a", "b", "c", "d", "e", "f"]; const result = alter(input).byMovingItemAtIndex(0).toIndex(2); expect(result).toEqual(["b", "c", "a", "d", "e", "f"]);
-
toTheStart
Create a new array with the item at the index specified moved to the start of the array.const input = ["a", "b", "c", "d", "e", "f"]; const result = alter(input).byMovingItemAtIndex(2).toTheStart(); expect(result).toEqual(["c", "a", "b", "d", "e", "f"]);
-
toTheEnd
Create a new array with the item at the index specified moved to the end of the array.const input = ["a", "b", "c", "d", "e", "f"]; const result = alter(input).byMovingItemAtIndex(2).toTheEnd(); expect(result).toEqual(["a", "b", "d", "e", "f", "c"]);
byRemovingDuplicates
Create a new array with any duplicates from the original removed.const input1 = [1, 1, 2, 1]; const input2 = [1, "1"]; const input3 = [[42], [42]]; const result1 = alter(input1).byRemovingDuplicates(); const result2 = alter(input2).byRemovingDuplicates(); const result3 = alter(input3).byRemovingDuplicates(); expect(result1).toEqual([1, 2]); expect(result2).toEqual([1, "1"]); expect(result3).toEqual([[42]]);
byRemovingItemsBetweenIndex
-
andIndex
Create a new array with all items between the two indexes removed.const input = [1, 2, 3, 4, 5, 6, 7, 8]; const result = alter(input).byRemovingItemsBetweenIndex(2).andIndex(3); expect(result).toEqual([1, 2, 6, 7, 8]);
-
andTheEnd
Create a new array with all items between the index specified and the end removed.const input = [1, 2, 3, 4, 5, 6, 7, 8]; const result = alter(input).byRemovingItemsBetweenIndex(3).andTheEnd(); expect(result).toEqual([1, 2, 3]);
byRemovingItemsEqualTo
Create a new array with any items matching those supplied removed.const input = [1, 2, 1, 3, 4]; const result = alter(input).byRemovingItemsEqualTo(1, 2); expect(result).toEqual([3, 4]);
byRemovingFalsyItems
Create a new array with all falsy items removed.const input = [ "a", false, "b", null, "c", undefined, "d", 0, "e", -0, "f", NaN, "g", "", ]; const result = alter(input).byRemovingFalsyItems(); expect(result).toEqual(["a", "b", "c", "d", "e", "f", "g"]);
-
-
findItemsIn
containedIn
Create a new array containing only items that are present in both the first and second array.const input1 = [1, 2, 3, 4]; const input2 = [7, 6, 5, 4, 3]; const result = findItemsIn(input1).containedIn(input2); expect(result).toEqual([3, 4]);
notContainedIn
Create a new array containing only items from the first array that are not present in second array.const input1 = [1, 2, 3, 4]; const input2 = [7, 6, 5, 4, 3]; const result = findItemsIn(input1).notContainedIn(input2); expect(result).toEqual([1, 2]);
thatAreUnique
Create a new array containing items that are only present in one of the two input arrays.const input1a = [1, 2, 3, 4]; const input1b = [7, 6, 5, 4, 3]; const result = findItemsIn(input1a).and(input1b).thatAreUnique(); expect(result).toEqual([1, 2, 7, 6, 5]);
-
from
pickQuantity
-
fromTheStart
Create a new array containing the first N number of items from the input array.const input = ["foo", "bar", "baz"]; const result = from(input).pickQuantity(2).fromTheStart(); expect(result).toEqual(["foo", "bar"]);
-
fromTheEnd
Create a new array containing the last N number of items from the input array.const input = ["foo", "bar", "baz"]; const result = from(input).pickQuantity(2).fromTheEnd(); expect(result).toEqual(["bar", "baz"]);
pickWhile
-
fromTheStart
Create a new array by selecting items from the start of the input array until the predicate returns false.const input = [1, 2, 3, 4, 3, 2, 1]; const result = from(input) .pickWhile((n) => n !== 4) .fromTheStart(); expect(result).toEqual([1, 2, 3]);
-
fromTheEnd
Create a new array by selecting items from the end of the input array until the predicate returns false.const input = [1, 2, 3, 4, 3, 2, 1]; const result = from(input) .pickWhile((n) => n !== 4) .fromTheEnd(); expect(result).toEqual([3, 2, 1]);
pickFirst
Return the first item from the input array.const result = from(["fi", "fo", "fum"]).pickFirst(); expect(result).toBe("fi");
pickLast
Return the last item from the input array.const result = from(["fi", "fo", "fum"]).pickLast(); expect(result).toBe("fum");
dropQuantity
-
fromTheStart
Create a new array containing all items from the input array with the first N items removed.const input = ["foo", "bar", "baz"]; const result = from(input).dropQuantity(2).fromTheStart(); expect(result).toEqual(["baz"]);
-
fromTheEnd
Create a new array containing all items from the input array with the last N items removed.const input = ["foo", "bar", "baz"]; const result = from(input).dropQuantity(2).fromTheEnd(); expect(result).toEqual(["foo"]);
dropWhile
-
fromTheStart
Create a new array by removing items from the start of the input array until the predicate returns false.const input = [1, 2, 3, 4, 3, 2, 1]; const result = from(input) .dropWhile((n) => n <= 2) .fromTheStart(); expect(result).toEqual([3, 4, 3, 2, 1]);
-
fromTheEnd
Create a new array by removing items from the end of the input array until the predicate returns false.const input = [1, 2, 3, 4, 3, 2, 1]; const result = from(input) .dropWhile((n) => n <= 3) .fromTheEnd(); expect(result).toEqual([1, 2, 3, 4]);
dropFirst
Create a new array containing every item from the input array except the first.const result = from(["fi", "fo", "fum"]).dropFirst(); expect(result).toEqual(["fo", "fum"]);
dropLast
Create a new array containing every item from the input array except the last.const result = from(["fi", "fo", "fum"]).dropLast(); expect(result).toEqual(["fi", "fo"]);
dropConsecutiveRepeats
Create a new array containing every item from the input array with any consecutively repeated elements removed.const input = [1, 1, 1, 2, 3, 4, 4, 2, 2]; const result = from(input).dropConsecutiveRepeats(); expect(result).toEqual([1, 2, 3, 4, 2]);
dropConsecutiveRepeatsSatisfying
Create a new array containing every item from the input array with any consecutive elements satisfying the predicate removed.const input = [1, -1, 1, 3, 4, -4, -4, -5, 5, 3, 3]; const result = from(input).dropConsecutiveRepeatsSatisfying( (x, y) => Math.abs(x) === Math.abs(y) ); expect(result).toEqual([1, 3, 4, -5, 3]);
-
-
sort
ascendingByProperty
Create a new array with items from the input array sorted in ascending order by the given property.const input = [ { name: "Emma", age: 70 }, { name: "Peter", age: 78 }, { name: "Mikhail", age: 62 }, ]; const result = sort(input).ascendingByProperty("age"); expect(result).toEqual([ { name: "Mikhail", age: 62 }, { name: "Emma", age: 70 }, { name: "Peter", age: 78 }, ]);
descendingByProperty
Create a new array with items from the input array sorted in descending order by a given property.const input = [ { name: "Emma", age: 70 }, { name: "Peter", age: 78 }, { name: "Mikhail", age: 62 }, ]; const result = sort(input).descendingByProperty("age"); expect(result).toEqual([ { name: "Peter", age: 78 }, { name: "Emma", age: 70 }, { name: "Mikhail", age: 62 }, ]);
firstAscendingByProperty
-
thenAscendingByProperty
Create a new array with items from the input array sorted in ascending order by the first property, then ascending by the second property.const alice = { name: "alice", age: 40, }; const bob = { name: "bob", age: 30, }; const clara = { name: "clara", age: 40, }; const input = [alice, bob, clara]; const result = sort(input) .firstAscendingByProperty("age") .thenAscendingByProperty("name"); expect(result).toEqual([bob, alice, clara]);
-
thenDescendingByProperty
Create a new array with items from the input array sorted in ascending order by the first property, then descending by the second property.const alice = { name: "alice", age: 40, }; const bob = { name: "bob", age: 30, ; const clara = { name: "clara", age: 40, }; const input = [clara, bob, alice]; const result = sort(input) .firstAscendingByProperty("age") .thenDescendingByProperty("name"); expect(result).toEqual([bob, clara, alice]);
firstDescendingByProperty
-
thenAscendingByProperty
Create a new array with items from the input array sorted in descending order by the first property, then ascending by the second property.const alice = { name: "alice", age: 40, }; const bob = { name: "bob", age: 30, }; const clara = { name: "clara", age: 40, }; const input = [clara, bob, alice]; const result = sort(input) .firstDescendingByProperty("age") .thenAscendingByProperty("name"); expect(result).toEqual([alice, clara, bob]);
-
thenDescendingByProperty
Create a new array with items from the input array sorted in descending order by the first property, then descending by the second property.const alice = { name: "alice", age: 40, }; const bob = { name: "bob", age: 30, }; const clara = { name: "clara", age: 40, }; const input = [clara, bob, alice]; const result = sort(input) .firstDescendingByProperty("age") .thenDescendingByProperty("name"); expect(result).toEqual([clara, alice, bob]);
-
-
split
atFirstEncounterOf
Create a new array that contains two arrays after splitting the original at the first point where the predicate holds true.const input = [1, 2, 3, 1, 2, 3]; const result = split(input).atFirstEncounterOf((n) => n === 2); expect(result).toEqual([[1], [2, 3, 1, 2, 3]]);
atIndex
Create a new array that contains two arrays after splitting the original at the index specified.const input = [1, 2, 3]; const result = split(input).atIndex(1); expect(result).toEqual([[1], [2, 3]]);
byItemsSatisfying
Create a new array that contains two arrays after separating the contents of the original into items that satisfy the predicate and those that don't.const input = ["sss", "ttt", "foo", "bars"]; const result = split(input).byItemsSatisfying((n) => n.includes("s")); expect(result).toEqual([ ["sss", "bars"], ["ttt", "foo"], ]);
everyNthIndex
Create a new array that contains multiple other arrays that are the result of splitting the original every N items.const input = [1, 2, 3, 4, 5, 6, 7]; const result = split(input).everyNthIndex(3); expect(result).toEqual([[1, 2, 3], [4, 5, 6], [7]]);
-
tally
byApplying
Create an object that contains a count of elements in an array according to how many match a key generated by the supplied function.const input = [1.0, 1.1, 1.2, 2.0, 3.0, 2.2]; const result = tally(input).byApplying(Math.floor); expect(result).toEqual({ 1: 3, 2: 2, 3: 1 });
-
alter
byApplying
-
toKey
Create a new object that is a copy of the original but with the transformation applied to the key specified.const input = { firstName: " Tomato ", data: { elapsed: 100, remaining: 1400 }, id: 123, }; const result = alter(input) .byApplying((n) => n.trim()) .toKey("firstName"); expect(result).toEqual({ firstName: "Tomato", data: { elapsed: 100, remaining: 1400 }, id: 123, });
-
-
copy
deeply
Create a deep copy of the object including any nested objects.const input = { a: [1, 2, 3], b: "foo", c: { c1: 123, }, }; const result = copy(input).deeply(); expect(input).toEqual(result); // Referential checks expect(input !== result).toBe(true); expect(input.a !== result.a).toBe(true); expect(input.c !== result.c).toBe(true);
discardKeys
Create a partial copy of the object omitting the keys specified.const input = { a: 1, b: 2, c: 3, d: 4 }; const result = copy(input).discardKeys("a", "d"); expect(result).toEqual({ b: 2, c: 3 });
keepKeys
Create a partial copy of an object containing only the keys specified.const input = { a: 1, b: 2, c: 3, d: 4 }; const result = copy(input).keepKeys("a", "c"); expect(result).toEqual({ a: 1, c: 3 });
-
merge
deeplyWith
-
resolvingConflictsViaFirstObject
Create a new object with all properties from the input objects, using values from the first object when the same keys exist in both.const obj1 = { name: "fred", age: 10, contact: { email: "[email protected]" }, }; const obj2 = { age: 40, hair: "blonde", contact: { email: "[email protected]" }, }; const result = merge(obj1) .deeplyWith(obj2) .resolvingConflictsViaFirstObject(); expect(result).toEqual({ name: "fred", age: 10, hair: "blonde", contact: { email: "[email protected]" }, });
-
resolvingConflictsViaSecondObject
Create a new object with all properties from the input objects, using values from the second object when the same keys exist in both.const obj1 = { name: "fred", age: 10, contact: { email: "[email protected]" }, }; const obj2 = { age: 40, hair: "blonde", contact: { email: "[email protected]" }, }; const result = merge(obj1) .deeplyWith(obj2) .resolvingConflictsViaSecondObject(); expect(result).toEqual({ name: "fred", age: 40, hair: "blonde", contact: { email: "[email protected]" }, });
-
resolvingConflictsByApplying
Create a new object with all properties from the input objects, using the resolver function to derive a value for keys that exist in both.const obj1 = { a: true, c: { values: [10, 20] } }; const obj2 = { b: true, c: { values: [15, 35] } }; const result = merge(obj1) .deeplyWith(obj2) .resolvingConflictsByApplying((x, y) => [...x, ...y]); expect(result).toEqual({ a: true, b: true, c: { values: [10, 20, 15, 35] }, });
-
-
clamp
between
Restrict a number to be within the range specified.expect(clamp(-5).between(1, 10)).toBe(1); expect(clamp(15).between(1, 10)).toBe(10); expect(clamp(4).between(1, 10)).toBe(4);
-
sequenceFrom
startingWith
-
untilCondition
Create an array of items using the rule and seed value up until the terminator condition is met.⚠️ Ensure your rule function is pure and terminator condition will always be met, otherwise you risk creating an infinite loop.const rule = (n: number) => Math.pow(n, 2); const terminator = (n: number) => n > 1e10; const seed = 10; const result = sequenceFrom(rule) .startingWith(seed) .untilCondition(terminator); expect(result).toEqual([10, 100, 10000, 100000000]);
-
Thank you for thinking about contributing to Crystalline, we welcome all feedback and collaboration from the community. We don't want the process to be laborious, so we've kept our contributing guide reeeeally short. Please take a moment to read through it as doing so will help ensure the library remains consistent as it grows.