declarative-js-symbols

Symbols to extend JavaScript Array and Object prototypes with declarative-js functionality.

Usage no npm install needed!

<script type="module">
  import declarativeJsSymbols from 'https://cdn.skypack.dev/declarative-js-symbols';
</script>

README

declarative-js-symbols

npm version Build Status Coverage Status

Symbol that extends array and object with additional functions from declarative-js package.

Install

npm i declarative-js-symbols --save

Usage

import { extends } from 'declarative-js-symbols'
import { data } from './businessData'

const transformedData = 
    data[extends]() //extending object prototypes
        .values()[extends]() //extending array prototypes
        .present()[extends]()
        .sortBy('severity')[extends]()
        .takeWhile(x => x.severity === 'Critical')
        .map(x => x.task)[extends]()
        .groupBy('taskName')

Imagine, that we maintain two npm packages and want to ensure that all dependencies have same versions.

import { extends } from 'declarative-js-symbols'

const p1 = {
    "name": "package1",
    "version": "1.0.0",
    "devDependencies": {
        "jest": "23.6.0", // <==
        "prettier": "1.15.3"
    }
}
const p2 = {
    "name": "package2",
    "version": "1.0.0",
    "devDependencies": {
        "jest": "22.0.0", // <==
        "prettier": "1.15.3"
    }
}

function toHaveMoreVersions(entry) {
    return entry.value[extends]()
        .unique()
        .length > 1
}

function toInvalidDependency(entry) {
    return {
            name: entry.key, 
            versions: entry.value.map(e => e.value)
        }
}

const invalidDeps = [p1, p2]
    .map(pck => pck.devDependencies) // object[]
    .map(pck => pck[extend]().entries())[extend]() // {key, value}[][]
    .flat()[extend]() // {key, value}[]
    .groupBy('key')[extend]() // {dependencyName: {name: version}[]}
    .entries() // {key, value}[]
    .filter(toHaveMoreVersions)
    .map(toInvalidDependency)
// [{name: 'Jest', versions: ['23.6.0', '22.0.0']}]

Array

equal

Filters out items, that are not equal to provided item in parameters. Objects are compared to be deep equal.

interface ArrayExtension<T> {
    equal(valueToMatch: T): Array<T>
}

notEqual

Filters out items, that are equal to provided item in parameters. Objects are compared to be deep equal.

interface ArrayExtension<T> {
    notEqual(valueToMatch: T): Array<T>
}

unique

Determines uniqueness of an objects in array. Be aware that if value is not primitive, deep object equality will be checked to determine uniqueness.

interface ArrayExtension<T> {
    unique(): Array<T>
}
 [1, 2, 2][extend]().unique()
 // [1, 2]
 [{a: 1}, {a: 1}, {a: 2}][extend]().unique()
 // [{a: 1}, {a: 2}]

notEmpty

Filters out items that are empty

interface ArrayExtension<T> {
    notEmpty(): Array<T>
}
 [1, '', [], {}][extend]().notEmpty()
 // [1]

present

Filters out items that are not present undefined and null in array

    present(): Array<NonNullable<T>>

uniqueBy

Determines uniqueness objects key from callback. This value must be comparable with strict equals

interface ArrayExtension<T> {
    uniqueBy<K extends keyof T>(key: K): Array<T>
    uniqueBy(key: StringGetter<T>): Array<T>
}
 [
  { title: 'Predator', genre: 'sci-fi' },
  { title: 'Predator 2', genre: 'sci-fi' },
  { title: 'Alien vs Predator', genre: 'sci-fi' },
  { title: 'Tom & Jerry', genre: 'cartoon' },
 ][extend]()
        .uniqueBy(movie => movie.genre) 
        .uniqueBy('genre') // overload
 // [
 //  { title: 'Predator', genre: 'sci-fi' },
 //  { title: 'Tom & Jerry', genre: 'cartoon' }
 // ]

takeWhile

It will pass items from array, while predicate matches. When predicate returns false none of the items will pass.

interface ArrayExtension<T> {
    takeWhile(predicate: Predicate<T>): Array<T>
}
 [
  { title: 'Predator', genre: 'sci-fi' },
  { title: 'Predator 2', genre: 'sci-fi'},
  { title: 'Tom & Jerry', genre: 'cartoon' },
  { title: 'Alien vs Predator', genre: 'sci-fi' },
 ][extend]()
      .takeWhile(film => film.genre === 'sci-fi')
 // [
 //  { title: 'Predator', genre: 'sci-fi' },
 //  { title: 'Predator 2', genre: 'sci-fi' }
 // ]

groupBy

Function to group by provided key.

interface ArrayExtension<T> {
    groupBy<K extends keyof T>(key: K): Indexed<T[]>
    groupBy(getKey: StringGetter<T>): Indexed<T[]>
}
 [
  { title: 'Predator', genre: 'sci-fi' },
  { title: 'Predator 2', genre: 'sci-fi'},
  { title: 'Alien vs Predator', genre: 'sci-fi' },
  { title: 'Tom & Jerry', genre: 'cartoon' },
 ][extend]()
      .groupBy('genre')
    // overload  
    //.groupBy(movie => movie.genre) 

flat

Function to make from 2d array simple array

interface ArrayExtension<T> {
    flat(): T
}
[[1,2],[3,4]][extend]()
    .flat() 
// [1,2,3,4]

toObject

Collects items to object by key from callback. If function resolves key, that already exists it will throw an Error. Second callback is value mapper.

interface ArrayExtension<T> {
    toObject<K>(
        getKey: (value: T) => string, 
        getValue: (value: T) => K
    ): Indexed<K>
}
[
    { title: 'Predator', genre: 'sci-fi' },
    { title: 'Predator 2', genre: 'sci-fi'},
    { title: 'Alien vs Predator', genre: 'sci-fi' },
    { title: 'Tom & Jerry', genre: 'cartoon' },
][extend]()
    .toObject(movie => movie.title, movie => movie.genre)

Collects items to object by key from callback. If function resolves key, that already exists it will throw an Error

interface ArrayExtension<T> {
    toObject(key: (value: T) => string): Indexed<T>
}
 [
  { title: 'Predator', genre: 'sci-fi' },
  { title: 'Predator 2', genre: 'sci-fi'},
  { title: 'Alien vs Predator', genre: 'sci-fi' },
  { title: 'Tom & Jerry', genre: 'cartoon' },
 ][extend]()
      .toObject(movie => movie.title)

merge

Reduces array of objects to one object, There is three merge strategies

@see Reducer.MergeStrategy (declarative-js)

@param merge {@link MergeStrategy} = default is OVERRIDE

interface ArrayExtension<T> {
    merge<R extends object>(strategy?: Reducer.MergeStrategy): T & R
}

ascendingBy

Sorts array in ascending order by values provided. First value has highest priority in sorting and so on. It accepts as many value resolvers as You need.

interface ArrayExtension<T> {
    ascendingBy<K extends keyof T>(...keys: K[]): Array<T>
    ascendingBy(...getters: ((val: T) => string | number)[]): Array<T>
}
persons[extend]().ascendingBy(
    x => x.name,
    x => x.lastName,
    x => x.age
)
//overload
persons[extend]().ascendingBy(
    'name',
    'lastName',
    'age'
)
 // [
 //  { name: 'andrew', lastName: 'Aa', age: 1 },
 //  { name: 'andrew', lastName: 'Bb', age: 1 },
 //  { name: 'andrew', lastName: 'Bb', age: 2 },
 //  { name: 'billy', lastName: 'Cc', age: 1 },
 //  { name: 'billy', lastName: 'Cc', age: 5 },
 // ]

descendingBy

Sorts array in descending order by values provided. First value has highest priority in sorting and so on. It accepts as many values as You need.

interface ArrayExtension<T> {
    descendingBy<K extends keyof T>(...keys: K[]): Array<T>
    descendingBy(...getters: ((val: T) => string | number)[]): Array<T>
}
 persons[extend]().ascendingBy(
    x => x.name,
    x => x.lastName,
    x => x.age
)
// overload
persons[extend]().ascendingBy(
    'name',
    'lastName',
    'age'
)
 // [
 //  { name: 'billy', lastName: 'Cc', age: 5 },
 //  { name: 'billy', lastName: 'Cc', age: 1 },
 //  { name: 'andrew', lastName: 'Bb', age: 2 },
 //  { name: 'andrew', lastName: 'Bb', age: 1 },
 //  { name: 'andrew', lastName: 'Aa', age: 1 },
 // ]

sortBy

Function that will sort items in array with custom values, by provided order. It accepts as a parameter object with valueToOrderElement mapper and array of custom order rule

import { Sort } from 'declarative-js'

interface ArrayExtension<T> {
    sortBy(...conditions: Sort.SortingCondition<T, any>[]): Array<T>
    sortBy<K extends keyof T>(key: K, values: T[K][]): Array<T>
}
testTodoData[extend]().sortBy(
    { toValue: x => x.severity, order: ['low', 'medium', 'high'] },
    { toValue: x => x.task, order: ['Sleep', 'Drink'] }
)
// overload with one sorting condition
testTodoData[extend]().sortBy('severity', ['low', 'medium', 'high'])
      // { task: 'Sleep', severity: 'low' },
      // { task: 'Drink', severity: 'low' },
      // { task: 'Eat', severity: 'medium' },
      // { task: 'Code', severity: 'high' },

orderedBy

Function that will sort items in array, by provided order. It accepts as a parameter array of custom order rule. Element, that are not present in order array will be at he the end of the sorted list.

interface ArrayExtension<T> {
    orderedBy(order: Array<T>): Array<T>
}
 const testData =
   ['bar', 'medium', 'foo', 'low']
 const result =
   testData[extend]()
      .orderedBy(['low', 'medium', 'high'])
 // ['low', 'medium', 'bar', 'foo', ]

Object

keys

Returns object keys

interface ObjectExtension<T> {
    keys(): string[]
}

Returns object values

values

interface ObjectExtension<T> {
    values(): T[]
}

entries

Returns object entries as a {key, value} objects array

interface ObjectExtension<T> {
    entries(): ObjectEntry<T>[]
}
interface ObjectEntry<T> {
    key: PropertyKey,
    value: T
}

containsKey

Is object contains key

interface ObjectExtension<T> {
    containsKey(key: string): boolean
}

containsValue

Is object contains value

interface ObjectExtension<T> {
    containsValue(value: T): boolean
}