Git Product home page Git Product logo

utils.js's Introduction

Tinkoff Utils Build status Coverage Status

Fast, small and purely functional utility library

Install

$ npm install @tinkoff/utils

Features

Structure of the library

  • /object – for objects
  • /string – for strings
  • /promise – for promises
  • /array – for arrays or array-like objects
  • /function – for functions – composition, currying and so on, also a set of simple functions (noop, T, F)
  • /is – set of type checking methods
  • / – root contains utilities which don't satisfy any of the above categories or are universal

Usage

import pathOr from '@tinkoff/utils/object/pathOr';
import compose from '@tinkoff/utils/function/compose';
import toLower from '@tinkoff/utils/string/toLower';
import map from '@tinkoff/utils/array/map'

const toLowerName = compose(
    toLower,
    pathOr(['name'], '')
);
const result = map(toLowerName)([{name: 'testA'}, {name: 'testb'}])

Benchmarks

$ npm run benchmark
Utility Lodash Ramda Utils
clone 120,807 ops/sec 112,053 ops/sec 293,572 ops/sec
array/filter 2,080,728 ops/sec 1,849,633 ops/sec 2,046,113 ops/sec
is/empty 1,506,963 ops/sec 474,177 ops/sec 3,731,564 ops/sec
function/flip 7,528,745 ops/sec 3,735,143 ops/sec 3,490,207 ops/sec
object/path 12,023,128 ops/sec 8,894,639 ops/sec 7,587,076 ops/sec
string/trim 4,215,928 ops/sec 1,034,655 ops/sec 6,029,794 ops/sec

Browser support

  • Chrome >= 40
  • Firefox >= 52
  • Edge >= 14
  • IE >= 11
  • Safari >= 10
  • iOS >= 10
  • Android >= 4.4

Node support

  • 6.4.0 and higher

Bundle size

Library Bundle size
import _ from 'lodash' 70.1 kb
import ... from 'lodash/...' 21.8 kb
import R from 'ramda' 41.3 kb
import ... from 'ramda/src/...' 10 kb
import ... from '@tinkoff/utils/...' 2.32 kb

For detailed comparison with specific libraries see COMPARE.md

utils.js's People

Contributors

alexkvak avatar chebyrash avatar dchebakov avatar dmitry-korolev avatar inpokon avatar kkamik avatar markelog avatar maximkorobovattinkoff avatar meskill avatar osorokotyaga avatar smaillz avatar superoleg39 avatar temoncher avatar tinkoff-bot avatar tom910 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

utils.js's Issues

Types improvement for `object/path` util

Problem

For now there are two problems with object/path types:

  1. path doesn't infer type of a prop based on the strings inside array in all cases except a single value array
const user = {
  nestedProp: {
    name: 'John'
  },
};

const userName = path(['nestedProp', 'name'], user); // results in `unknown` type
  1. using a type argument is not typesafe and repetative
// can do the trick
// but we throw all the typesafety away, because type of `name` can change in the future
// and this dirty cast will only error out in runtime.
// Also it feels like we gave compiler all the information it needs to infer this type
// and it feels weird that we have to add types ourselves
const userName1 = path<string>(['nestedProp', 'name'], user);

Solutions

I see two solutions here: implement ValueByPath helper type ourselves or use https://github.com/millsp/ts-toolbelt util
Both solutions are available in codesandbox: https://codesandbox.io/s/relaxed-lewin-e3njhm?file=/src/index.ts

Manual

import type { Prop, Paths } from "@tinkoff/utils/typings/types";
import curryN from "@tinkoff/utils/function/curryN";

type IsExactKey<T> = string extends T
  ? false
  : number extends T
  ? false
  : symbol extends T
  ? false
  : true;

type ValueByPath<P extends Paths, O, U = true> = P extends readonly [
  infer F,
  ...(infer R)
]
  ? F extends keyof O
    ? R extends []
      ? U extends true
        ? O[F] | undefined
        : O[F]
      : R extends Paths // In case we run into some kind of dynamic dictionary // something like Record<string, ..> or Record<number, ..> // We want to make sure that we get T | undefined instead of T as a result
      ? IsExactKey<keyof O> extends true
        ? ValueByPath<R, O[F], U>
        : ValueByPath<R, O[F], true>
      : undefined
    : undefined
  : undefined;

type Path = {
  (pathToProp: Prop[]): (obj: object) => unknown;
  (pathToProp: Prop[], obj: object): unknown;
  <P extends Paths>(pathToProp: P): <O>(obj: O) => ValueByPath<P, O>;
  <P extends Paths, O>(pathToProp: P, obj: O): ValueByPath<P, O>;
};

const _path = <K extends Prop, O extends Record<K, any>>(
  paths: Paths = [],
  obj: O = {} as any
) => {
  let val = obj;

  for (let i = 0; i < paths.length; i++) {
    if (val == null) {
      return undefined;
    }

    val = val[paths[i]];
  }

  return val;
};

export const path = curryN(2, _path) as Path;

TS-toolbelt

import type { Any, Object } from "ts-toolbelt";
import curryN from "@tinkoff/utils/function/curryN";

type Path = {
  (pathToProp: Any.Key[]): (obj: object) => unknown;
  (pathToProp: Any.Key[], obj: object): unknown;
  <P extends readonly Any.Key[]>(pathToProp: P): <O>(
    obj: O
  ) => Object.Path<O, P>;
  <P extends readonly Any.Key[], O>(pathToProp: P, obj: O): Object.Path<O, P>;
};

const _path = <K extends Any.Key, O extends Record<K, any>>(
  paths: readonly Any.Key[] = [],
  obj: O = {} as any
) => {
  let val = obj;

  for (let i = 0; i < paths.length; i++) {
    if (val == null) {
      return undefined;
    }

    val = val[paths[i]];
  }

  return val;
};

export const pathT = curryN(2, _path) as Path;

Migration problem

In case we implement any of these solutions some of the library dependants can have migration issues, because they could have leaned on the usage of type parameters and now the generic is completely different. To provide a more smooth migration story we can add two more overloads with a single type parameter, mark them as deprecated and remove them completely in future versions

type Path = {
  (pathToProp: Prop[]): (obj: object) => unknown;
  (pathToProp: Prop[], obj: object): unknown;
  /** @deprecated please use `path` without type parameters instead */
  <T>(pathToProp: Prop[]): (obj: object) => T;
  /** @deprecated please use `path` without type parameters instead */
  <T>(pathToProp: Prop[], obj: object): T;
  <P extends Paths>(pathToProp: P): <O>(obj: O) => ValueByPath<P, O>;
  <P extends Paths, O>(pathToProp: P, obj: O): ValueByPath<P, O>;
};

Не хватает параметров callback в findIndex

Заметил проблемку в findIndex, при вызове callback передается только один аргумент, тогда как в Array.prototype.findIndex их три. Не хватает индекса и массива. В find так же.

Sanitize supports only one tag

Whether it was made intentionally or not, src/sanitize.js supports only one <em> tag. Are you planning to add some more important tags ( i.e <script>) or better yet let a developer himself to pass a Regex as an argument?

Opt-in for currying utils

From README.md:

Most of the utilities are automatically curried

Auto-currying may be very convenient for those who already get it, but it also may be extremely confusing for newcomers (curried functions and higher-order functions in general are harder to use, let alone debug or build new code using them). We could transpile using a custom babel plugin and cut out curry() calls where appropriate (from top-level exports at least) by default, and let people opt-in using a different entry point.

I propose that we either implement the following schema using paths:

import map from '@tinkoff/utils/object/map' // requires all args

import mapR from '@tinkoff/utils/R/object/map' // auto-curried
// or
import mapR from '@tinkoff/utils/object/map/R' // auto-curried

or named exports

import default as map, {R as mapR } from '@tinkoff/utils/object/map' // for auto-curried exports

Добавьте метод insertAt(index, element, array) для массива, пожалуйста

Очень часто нужно добавить элемент в массив по индексу и вернуть новый массив.
Такое простое действие каждый раз порождает неприятные конструкции в коде:

 const newArray = [...array]

 newArray.splice(index, 0, newElement)

или

const newArray = array.slice(0, index).concat([newElement]).concat(array.slice(index))

Not obvious behavior of omit

Omit not stringifies numbers for object key equation
ramda.omit([1], {'1': 'hello'}); // {} - empty object lodash.omit({'1': 'hello' }, [1]) // {} - empty object omit([1], {'1': 'hello'}); // {'1': 'hello} - not empty object, because 1 is number

Incorrect behaviour of throttleEnd

Problem:
In a case when a function throttled with throttleEnd is invoked several times within a wait period it is finally called by throttleEnd with arguments, provided at first invoke.

const throttled = throttleEnd(100, console.log)
throttled(1)
throttled(2)
throttled(3)
// -> 1

Lodash throttle behaves differentely:

const throttled = _.throttle(console.log, 100, { leading: false, trailing: true })
throttled(1)
throttled(2)
throttled(3)
// -> 3

RxJS operator auditTime, which is similar to throttleTime, but emits at the end of the wait period, behaves likewise:

const throttled = new Observable(observer => {
	observer.next(1)
	observer.next(2)
	observer.next(3)
}).pipe(auditTime(100))
throttled.subscribe(console.log)
// -> 3

Using the last provided set of arguments seems much more logical to me.

I suggest changing the throttleEnd implementation to make it more sense.

Add ability to chunk array by function

Sometimes (not often) i need logic like this:

const positiveNumbers = numbers.filter(isPositiveNumber)
const negativeNumbers = numbers.filter(complement(isPositiveNumbers))
/// some tramsforms of this values
...

so it's not very fast and declarative. Also it becomes too repetetive, when there are more then 2 groups. So I need something like this:

const [positiveNumbers, negativeNumbers] = groupByArrays([isPositiveNumber], list);

So I mean that i can pick my sublist by index/
Also there is other usecase with more than 2 groups:

const [positiveNumbers, negativeNumbers, strings, rest] = groupByArrays([isPositiveNumber, isNegativeNumber, isString], list);

So realization can be something like this:

groupByArrays = (fns, arr) => {
    const result = [];
    let a, b = arr;
    
    return fns.map((fn) => {
        [a, b] = partition(fn, b);
        
        return a;
    }).concat([b])
}

Add converge analog from ramda

In some cases i need converge function, f.e.
`
const extractOfferSuggestions = pathOr(["offerSuggestions"], []);
const extractWordSuggestions = pathOr(["wordSuggestions"], []);
const mapSuggestionsToOptions = (suggestions): SuggestOption[] => [];

// without converge
const transformSuggestionsToOptions = suggestions => {
const wordSuggestions = extractWordSuggestions(suggestions);
const offerSuggestions = extractOfferSuggestions(suggestions);
return mapSuggestionsToOptions({
offerSuggestions,
wordSuggestions
});
};

// with converge
const transformSuggestionsToOptions = converge(
([wordSuggestions, offerSuggestions]) =>
mapSuggestionsToOptions({ offerSuggestions, wordSuggestions }),
[extractWordSuggestions, extractOfferSuggestions]
);
// or with other mapSuggestionsToOptions signature (two params instead of one object param)
const transformSuggestionsToOptions = converge(
mapSuggestionsToOptions,
[extractWordSuggestions, extractOfferSuggestions]
);
`
Some points on this combinator https://jrsinclair.com/articles/2019/compose-js-functions-multiple-parameters/

I think, createSelector from 'reselect' has the same idea (but with memoization)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.