crystalline v0.2.3
Crystalline
A modern utility library with a strong emphasis on readability. Make your code crystal clear.
Inspired by Jest Matchers.
Table of Contents
Introduction
The problem
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.
The solution
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.
Guiding Principles
1) Readable code is maintainable code. 2) Write code as if you were writing a sentence. 3) Don't reinvent the wheel when a readable native solution already exists. 4) Favour brevity but not at the expense of readability.
Installation
NPM:
npm install crystalline
Yarn:
yarn add crystalline
Usage
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(...);
API Reference
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.
arrays
alter
const input = ["a", "b", "c", "d"]; const result = alter(input) .byApplying((n) => n.toUpperCase()) .atIndex(1); expect(result).toEqual(["a", "B", "c", "d"]);
const input = ["b", "n", "n", "s"]; const result = alter(input).byInsertingBetweenEachItem("a"); expect(result).toEqual(["b", "a", "n", "a", "n", "a", "s"]);
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"]);
const input = ["a", "b", "c", "d", "e", "f"]; const result = alter(input).byMovingItemAtIndex(2).toTheStart(); expect(result).toEqual(["c", "a", "b", "d", "e", "f"]);
const input = ["a", "b", "c", "d", "e", "f"]; const result = alter(input).byMovingItemAtIndex(2).toTheEnd(); expect(result).toEqual(["a", "b", "d", "e", "f", "c"]);
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]]);
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]);
const input = [1, 2, 3, 4, 5, 6, 7, 8]; const result = alter(input).byRemovingItemsBetweenIndex(3).andTheEnd(); expect(result).toEqual([1, 2, 3]);
const input = [1, 2, 1, 3, 4]; const result = alter(input).byRemovingItemsEqualTo(1, 2); expect(result).toEqual([3, 4]);
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
const input1 = [1, 2, 3, 4]; const input2 = [7, 6, 5, 4, 3]; const result = findItemsIn(input1).containedIn(input2); expect(result).toEqual([3, 4]);
const input1 = [1, 2, 3, 4]; const input2 = [7, 6, 5, 4, 3]; const result = findItemsIn(input1).notContainedIn(input2); expect(result).toEqual([1, 2]);
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
const result = from(input).pickQuantity(2).fromTheStart(); expect(result).toEqual(["foo", "bar"]); ``` </p> </details>
const result = from(input) .pickWhile((n) => n !== 4) .fromTheStart();
expect(result).toEqual(1, 2, 3);
</p> </details>
const result = from(input) .pickWhile((n) => n !== 4) .fromTheEnd(); expect(result).toEqual([3, 2, 1]);
</p> </details> </p> </details> <details> <summary><code>pickFirst</code></summary> <p> <br/> Return the first item from the input array. ```javascript const result = from(["fi", "fo", "fum"]).pickFirst(); expect(result).toBe("fi");
expect(result).toBe("fum");
</p> </details> <details> <summary><code>dropQuantity</code></summary> <p>
const result = from(input).dropQuantity(2).fromTheStart();
expect(result).toEqual("baz");
</p> </details>
expect(result).toEqual("foo");
</p> </details> </p> </details> <details> <summary><code>dropWhile</code></summary> <p>
expect(result).toEqual(3, 4, 3, 2, 1);
</p> </details>
const result = from(input) .dropWhile((n) => n <= 3) .fromTheEnd();
expect(result).toEqual(1, 2, 3, 4);
</p> </details> </p> </details> <details> <summary><code>dropFirst</code></summary> <p> <br/> Create a new array containing every item from the input array except the first. ```javascript const result = from(["fi", "fo", "fum"]).dropFirst(); expect(result).toEqual(["fo", "fum"]);
const result = from(input).dropConsecutiveRepeats();
expect(result).toEqual(1, 2, 3, 4, 2);
</p> </details> <details> <summary><code>dropConsecutiveRepeatsSatisfying</code></summary> <p> <br/> Create a new array containing every item from the input array with any consecutive elements satisfying the predicate removed. ```javascript 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
const result = sort(input).ascendingByProperty("age");
expect(result).toEqual( { name: "Mikhail", age: 62 }, { name: "Emma", age: 70 }, { name: "Peter", age: 78 }, );
</p> </details> <details> <summary><code>descendingByProperty</code></summary> <p> <br/> Create a new array with items from the input array sorted in descending order by a given property. ```javascript 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 }, ]);
</p> </details>
const input = clara, bob, alice;
const result = sort(input) .firstDescendingByProperty("age") .thenAscendingByProperty("name");
expect(result).toEqual(alice, clara, bob);
</p> </details>
const result = sort(input) .firstDescendingByProperty("age") .thenDescendingByProperty("name");
expect(result).toEqual(clara, alice, bob);
</p> </details> </p> </details>
split
const result = split(input).atFirstEncounterOf((n) => n === 2);
expect(result).toEqual([1, 2, 3, 1, 2, 3]);
</p> </details> <details> <summary><code>atIndex</code></summary> <p> <br/> Create a new array that contains two arrays after splitting the original at the index specified. ```javascript const input = [1, 2, 3]; const result = split(input).atIndex(1); expect(result).toEqual([[1], [2, 3]]);
tally
const result = tally(input).byApplying(Math.floor);
expect(result).toEqual({ 1: 3, 2: 2, 3: 1 });
</p> </details>
objects
alter
const result = alter(input) .byApplying((n) => n.trim()) .toKey("firstName"); expect(result).toEqual({ firstName: "Tomato", data: { elapsed: 100, remaining: 1400 }, id: 123, }); ``` </p> </details>
copy
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);
</p> </details> <details> <summary><code>discardKeys</code></summary> <p> <br/> Create a partial copy of the object omitting the keys specified. ```javascript const input = { a: 1, b: 2, c: 3, d: 4 }; const result = copy(input).discardKeys("a", "d"); expect(result).toEqual({ b: 2, c: 3 });
const result = copy(input).keepKeys("a", "c");
expect(result).toEqual({ a: 1, c: 3 });
</p> </details>
merge
const result = merge(obj1) .deeplyWith(obj2) .resolvingConflictsViaFirstObject(); expect(result).toEqual({ name: "fred", age: 10, hair: "blonde", contact: { email: "moo@example.com" }, }); ``` </p> </details>
const result = merge(obj1) .deeplyWith(obj2) .resolvingConflictsViaSecondObject();
expect(result).toEqual({ name: "fred", age: 40, hair: "blonde", contact: { email: "baa@example.com" }, });
</p> </details>
const result = merge(obj1) .deeplyWith(obj2) .resolvingConflictsByApplying((x, y) => ...x, ...y);
expect(result).toEqual({ a: true, b: true, c: { values: 10, 20, 15, 35 }, });
</p> </details> </p> </details>
numbers
clamp
misc
sequenceFrom
const result = sequenceFrom(rule) .startingWith(seed) .untilCondition(terminator); expect(result).toEqual([10, 100, 10000, 100000000]); ``` </p> </details>
Contributing
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.