Coder Social home page Coder Social logo

rayepps / radash Goto Github PK

View Code? Open in Web Editor NEW
4.0K 20.0 160.0 691 KB

Functional utility library - modern, simple, typed, powerful

Home Page: https://radash-docs.vercel.app

License: MIT License

JavaScript 26.19% TypeScript 73.81%
functions javascript javascript-library typescript hacktoberfest

radash's Introduction

Radash

🔊 /raw-dash/

radash

Functional utility library - modern, simple, typed, powerful

bundle size npm downloads npm version MIT license

Install

yarn add radash

Usage

A very brief kitchen sink. See the full documentation here.

import * as _ from 'radash'

const gods = [{
  name: 'Ra',
  power: 'sun',
  rank: 100,
  culture: 'egypt'
}, {
  name: 'Loki',
  power: 'tricks',
  rank: 72,
  culture: 'norse'
}, {
  name: 'Zeus',
  power: 'lightning',
  rank: 96,
  culture: 'greek'
}]

_.max(gods, g => g.rank) // => ra
_.sum(gods, g => g.rank) // => 268
_.fork(gods, g => g.culture === 'norse') // => [[loki], [ra, zeus]]
_.sort(gods, g => g.rank) // => [ra, zeus, loki]
_.boil(gods, (a, b) => a.rank > b.rank ? a : b) // => ra

_.objectify(
  gods, 
  g => g.name.toLowerCase(), 
  g => _.pick(g, ['power', 'rank', 'culture'])
) // => { ra, zeus, loki }

const godName = _.get(gods, g => g[0].name)

const [err, god] = await _.try(api.gods.findByName)(godName)

const allGods = await _.map(gods, async ({ name }) => {
  return api.gods.findByName(name)
})

Contributing

Contributions are welcome and appreciated! Check out the contributing guide before you dive in.

radash's People

Contributors

ahmedhesham6 avatar aki77 avatar angadsethi avatar apstanisic avatar danmichaelo avatar dependabot[bot] avatar dsherret avatar fresult avatar giuliocaccin avatar jacobweisenburger avatar jukanntenn avatar ktmouk avatar lesiuk avatar marlonpassos-git avatar matteosacchetto avatar mertdy avatar minhir avatar mukunku avatar nickersoft avatar phanghos avatar raphaelpra avatar rociodev avatar rphlmr avatar shakil-aibees avatar sodiray avatar unknown-consortium avatar vchikalkin avatar xiamibuchi avatar yakovmeister avatar yeswing 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

radash's Issues

Feature request: Add a method to transform an object

Big fan of the project so far, the only thing preventing me from removing lodash is how I use it's transform function to transform the shape of an array. Here's an example:

Say I get an object from a remote api that looks like this:

const data = {
  id: 1,
  source: {
    image: 'https://example.com/test.jpg'
  }
}

And I want to convert it to:

const data = {
  id: 1,
  imageUrl: 'https://example.com/test.jpg'
}

With lodash I can use transform & get like this:

const data = {
  id: 1,
  source: {
    image: 'https://example.com/test.jpg'
  }
}

const keys = {
  id: "id",
  "source.imageUrl": "imageUrl"
}

const transformKeys = transform(keys, (result, value, key) => get(data, key))

const result = transformKeys(data, keys) 
// { id: 1, imageUrl: 'https://example.com/test.jpg' }

I'm wondering if there's already an easy way to achieve the same thing using radash, or if it could potentially be added?

Add index to `select`

The mapper used by select should receive as argument the index of the element that is being mapped, in addition to the actual element being mapped.

Typing for `sift` function

Right now, the sift function returns type T[] which could be inferred as a type that includes undefined or null (which are omitted from the actual return value, by design). The outcome is that sift doesn't work well as a type guard. Would be great if it would narrow the return type to NonNullable<T>[].

I'm happy to open a PR if it would be welcome.

isString and TypeScript narrowing

isString does not behave as a typeof type guard in TypeScript.

In this example, isString(str) should have the same type guarding effect as typeof str === 'string'

image

Add a pipe function

The pipe function would be like chain but instead of returning a function, it would pass the value through and execute the function.

Logically, pipe is a way of executing many functions, one after another.

See this article for a detailed concept.

The Interface

import { pipe } from 'radash'

const addFive = (num) => num + 5
const divideByTwo = (num) num / 2

pipe(5, addFive, divideByTwo) // 5

This made me consider a skip function. Some functions have a side effect and return nothing, you might want to run it in a pipe but not break the value passing through the pipe.

const skip = (func) => (value) => {
  func(value)
  return value
}

pipe(5, addFive, console.log, divideByTwo) // errors
pipe(5, addFive, skip(console.log), divideByTwo) // console.logs 10 and returns 5

PR template issues

The PR template has the following issues.

  • misspelled word behavior
  • checkboxes not correctly formatted
    • [] should become - [ ]

Add the `sample` function

Hi,

I'm missing the sample function from lodash. The purpose of this function is to return a random element from an array.
Take a look at the reference: https://lodash.com/docs/4.17.15#sample

It should work like this:

_.sample([1, 2, 3]) // 3
_.sample([1, 2, 3]) // 1
_.sample([]) // undefined

Let me know what do you think. I can send a PR to this one.

Feedback about the docs

First of all, congrats on the lib, some of the functions are even mind blowing :)

Just reviewing the doc. Not sure if the repo is public but i don't see it so here i go :)

  1. Ctrl + C in windows does the equivalent to ctrl + K, so i'm unable to copy text

Boil => Not sure about its naming 🤔

image
I would probably use an array with 6 items so it is easier to understand the way this function packages items

counting => I would call it groupCount

flat => Not sure why having this one. Radash is supposed to be "modern". What is the advantage agains native [].flat()?

image
Should include Zues, and i guess it's a typo should be Zeus 🤔

image
output should be: //[{name: "Marlin" weight: 105}, {name: "Trout", weight: 13}]

[...]

Checking the rest of the docs there are a lot of little issues like this. Specially in the output.

Also the playground is loading an older version of radish and not working!

Hacktorfest

Can add the "hacktoberfest" topic on the rep?
i have some contributions in mind, and want this to count on the event

Feature - map: asyncMapFunc should have item and index?

Good morning,

I wanted to use radash in my react front-end application and i missed a nice feature what could potentially be added into the map function.

in the map function parameter: asyncMapFunc: (item: T) => Promise<K> if it's possible to addition something like this: asyncMapFunc: (item: T, index: number) => Promise<K>

I forked the repository and made some edits. If you find it a nice addon to the map function i can create a pull request if you want.

In waiting for you reply wish you a nice day.

CRA/webpack: Failed to parse source map from typed.ts

Hey, we are using creat-react-app which uses webpack 5 and getting the following warning when using radash.

WARNING in ./node_modules/radash/dist/typed.js
Module Warning (from ./node_modules/source-map-loader/dist/cjs.js):
Failed to parse source map from '/frontend/node_modules/radash/src/typed.ts' file: Error: ENOENT: no such file or directory, open '/frontend/node_modules/radash/src/typed.ts'
 @ ./node_modules/radash/dist/index.js 168:14-32
 @ ./src/modules/buyer-side/contacts/components/ContactImporter/helpers.ts 5:0-34 54:46-54
 @ ./src/modules/buyer-side/contacts/components/ContactImporter/ContactImporter.tsx 12:0-100 65:15-29
 @ ./src/modules/buyer-side/contacts/routes/ContactImportView.tsx 9:0-105 43:35-50
 @ ./src/routes.tsx 289:15-81
 @ ./src/App.tsx 25:0-35 91:49-58
 @ ./src/index.tsx 17:0-26 62:47-50

Seems like source maps are not emitted. Can someone point me in a direction on how to fix this? Would love to contribute a fix here!

Related:
facebook/create-react-app#11752

Best regards
Linus

'pick' picks provided key even if it is not in the object

Hello @rayepps! Thanks for this awesome library!
I found an issue with pick function. It picks the keys that are not even in the object:

Code: pick({ a:1, b:2 }, ['c'])
Actual return value: { c: undefined }
Expected return value: {}

Lodash's pick doesn't pick the key that is not in object. I expect that too.
Is it intended or it should be fixed? Thanks!

Test for async sleep function is flaky

The test for the sleep function tries to assert time has passed which makes it a bit flaky and sometimes fails. See this build.

FAIL src/tests/async.test.ts (10.106 s)
  ● async module › _.sleep function › returns error when error is thrown

    assert.strictEqual(received, expected)

    Expected value to strictly be equal to:
      1665729789402
    Received:
      1665729789401

    Message:
      expected 1665729789401 to be at least 1665729789402

      216 |       await _.sleep(1000)
      217 |       const after = Date.now()
    > 218 |       assert.isAtLeast(after, before + 1000)
          |              ^
      219 |     })
      220 |   })
      221 |   

      at src/tests/async.test.ts:218:[14](https://github.com/rayepps/radash/actions/runs/3247942713/jobs/5328559507#step:5:15)
      at fulfilled (src/tests/async.test.ts:5:58)

Jest has some utilities for testing async time-based functions. We should use something like that (or anything that is not flaky) so tests do not occasionally fail.

Add github workflow that runs unit tests on PRs

Back when it was just me this wasn't such a big deal. But now there are others and having the unit tests run in CI would be incredibly helpful so nobody has to wait for me to see the PR, pull it locally, run the tests, and tell them what failed -- whever I'm available.

The workflow action needs to:

  • run yarn, then yarn test.
  • run Node v16+

Suggestion: a more thorough explanation of isEmpty function

I've seen the isEmpty function which I find extremely useful (and actually I find myself a lot writing similar functions for the projects I work with). But I believe the docs should explain what "empty" actually means for each use case.

Thanks very much for this great library

PS: I've seen that in the source code there are some other functions that aren't documented like isInt, isFloat, isDate, isEqual and I wonder if they aren't available yet for using or they are just not documented yet

Improve the omit function's performance

Your omit function isn't performant. If you have an object with 20 keys, and you want to omit 5 of them, you have to create 15 objects in the process, not including the 5 objects you're creating when building the key map. I don't know TypeScript, so I don't plan on opening a PR, but you could do something like the following, which only results in the creation of one new object.

export const omit = (
  obj,
  keys
) => {
  if (!obj) {
    return { };
  }

  if (!keys || keys.length === 0) {
    return obj;
  }

  const objCopy = { ...obj };

  keys.forEach((key) => {
    // Gross, I know, it's mutating the object, but it doesn't really matter
    // since you created the new object in this scope without modifying the
    // original object.
    delete objCopy[key];
  });

  return objCopy;
};

Also, your documentation for the omit function has a typo in it - 'source <-- missing the closing single quote.

TypeError: Cannot read properties of undefined

When I try to run the following script, I get TypeError: Cannot read properties of undefined (reading 'try'):

import _ from 'radash';

async function main() {
  console.debug('radash', { radash: _ });

  const [err, res] = await _.try(() => {
    throw new Error('O NOES');
  })();
}

main().then()

Full output:

$ npx ts-node packages/nebula-api/sketches/radash.ts

radash { radash: undefined }
/Users/cpete0624/work/ops-nebula/packages/nebula-api/sketches/radash.ts:8
  const [err, res] = await _.try(() => {
                             ^
TypeError: Cannot read properties of undefined (reading 'try')
    at main (/Users/cpete0624/work/ops-nebula/packages/nebula-api/sketches/radash.ts:8:30)
    at Object.<anonymous> (/Users/cpete0624/work/ops-nebula/packages/nebula-api/sketches/radash.ts:13:1)
    at Module._compile (node:internal/modules/cjs/loader:1126:14)
    at Module.m._compile (/Users/cpete0624/work/ops-nebula/node_modules/ts-node/src/index.ts:1618:23)
    at Module._extensions..js (node:internal/modules/cjs/loader:1180:10)
    at Object.require.extensions.<computed> [as .ts] (/Users/cpete0624/work/ops-nebula/node_modules/ts-node/src/index.ts:1621:12)
    at Module.load (node:internal/modules/cjs/loader:1004:32)
    at Function.Module._load (node:internal/modules/cjs/loader:839:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)
    at phase4 (/Users/cpete0624/work/ops-nebula/node_modules/ts-node/src/bin.ts:649:14)

Using Typescript 4.8.4, radash 8.0.3

The case about `get`

So I understand why you went the way you did with get, but big part of the string path approach is you don't need to access the nested data to find a value, for me it's invaluable as I've encountered many cases where you know, say a dot notated string path and you want the value without having to manually traverse it. For example there are validators that return string path key e.g. children.0.name failed and you want to get the value from said key, or sometimes you have to traverse DOM elements and build an object path to read or write, which current approach makes it impossible and requires manual handling.

Also the current approach of

const fish = {
  name: 'Bass',
  weight: 8,
  sizes: [{
    maturity: 'adult',
    range: [7, 18],
    unit: 'inches'
  }]
}

const maxAdultSize = get(fish, f => f.sizes[0].range[1])

for me looks nothing more than

const maxAdultSize = fish.sizes?.[0].range?.[1];

Would this be something you'd be willing to look at?

Thoughts on adding `zip` methods?

I recently found myself hand-rolling a zipObject method to avoid using Lodash, and even though it can be achieved via reduce I think adding some zip methods (such as unzip, zipObject, and zip/unzipWith) could offer a much more succinct interface:

/**
 * Zips two arrays of even length together into an object
 *
 * @param keys The object's keys
 * @param values The object's values
 * @returns
 */
export function zipObject<Key extends string | number | symbol, Value>(keys: Key[], values: Value[]) {
  return keys.reduce((acc, key, idx) => ({ ...acc, [key]: values[idx] }), {} as Record<Key, Value>);
}

Happy to pick up the implementation, but wanted to make sure this stays in line with this project's philosophies.

Thoughts?

_.try is incompatible with AWS SDK v3

Typescript 4.8.4
Radash 8.0.3-alpha.1

This may be related to #60

If I'm reading the documentation for _.try correctly, the following two code snippets should be (roughly) functionally identical

// try/catch
import { S3Client, GetObjectCommand, GetObjectCommandInput } from '@aws-sdk/client-s3';

const params = {
  Bucket: 'my-bucket',
  Key: 'my-key'
};

const s3client = new S3Client({});

let [res, err];
try {
  res = await s3client.send(new GetObjectCommand(params));
} catch (e) {
  err = e;
}
// _.try
import { S3Client, GetObjectCommand, GetObjectCommandInput } from '@aws-sdk/client-s3';

const params = {
  Bucket: 'my-bucket',
  Key: 'my-key'
};

const s3client = new S3Client({});

const [res, err] = _.try(s3client.send)(new GetObjectCommand(params));

However, with the latter construction, Typescript brings up the error: Expected 3 arguments, but got 1. ts(2554)

I tried the workaround listed in #60:

 const [err, res] = await _.try(async () => await s3client.send(new GetObjectCommand(params)))

But that produced the error Type '() => Promise<[Error, null] | [null, GetObjectCommandOutput]>' must have a '[Symbol.iterator]()' method that returns an iterator. ts(2488).

Any thoughts on how I could use _.try with AWS SDK v3?

License in package.json is different from the LICENSE.md in the repo

In the repo the LICENSE.md file is indicated by GitHub as a BSD 3-Clause "New" or "Revised" License, while in the package.json the LICENSE is indicated as MIT.

I would suggest uniforming the licenses and have both the repo and the package.json file indicating the same license.

Since the LICENSE.md files indicates a BSD 3-Clause "New" or "Revised" License, I would suggest changing the package.json property from MIT to BSD-3-Clause (The license identifier is specified in this list)

Parallel signature question

Hi,

First, thanks for this lib ;)

Do you have a reason/use case to set limit as the first parallel function argument?

I don't pretend to know how everyone works but limiting parallel async calls is an exception in my use cases.

When I see parallel I guess it'll run all callback functions in parallel, based on the array.

I think of something like that :

export const parallel = async <T, K>(
  array: T[],
  func: (item: T) => Promise<K>,
  limit: number = array.length,
): Promise<K[]>

Not dramatic, I still can do it like:

const users = await parallel(userIds.length, userIds, async (userId) => {
  return await api.users.find(userId)
})

It's like map signature in the end, but running all callbacks in parallel and not one after one.

try/tryit is incompatible with certain function overloads

I have been trying to use try with fs.readdir from Node's fs/promises module. The typical function signature you would use is simply:

const fileNames = await fs.readdir(path) // <- returns an array of strings  

Unfortunately, fs.readdir has 3 other overloads:

function readdir(
        path: PathLike,
        options:
            | {
                  encoding: 'buffer';
                  withFileTypes?: false | undefined;
              }
            | 'buffer'
    ): Promise<Buffer[]>;

function readdir(
        path: PathLike,
        options?:
            | (ObjectEncodingOptions & {
                  withFileTypes?: false | undefined;
              })
            | BufferEncoding
            | null
    ): Promise<string[] | Buffer[]>;

function readdir(
        path: PathLike,
        options: ObjectEncodingOptions & {
            withFileTypes: true;
        }
    ): Promise<Dirent[]>;

Using try with fs.readdir results in the last overload being used, making the second function parameter options required (meaning I can't omit it at all) and returning Dirent[] in the promise.

// fileNames is now 'Dirent[]', not 'string[]'
const [err, fileNames] = await _.try(fs.readdir)(path, { 
    // withFileTypes is constrained to be 'true'
    withFileTypes: true,
 })

A possible workaround would be to place fs.readdir into a temp function like so:

const read = fs.readdir(path);
const [err, fileNames] = await _.try(read)(); // <- returns string[]

However, I find that the workaround loses its elegance in utility libraries like radash or lodash.

Make `min` function require a getter if the first argument is an array of objects

The min function will accept an array of objects and no getter argument without error which is very error prone (I just found out the hard way).

The Problem

import { min } from 'radash'

const books = [{ title: 'About Trains', published: 298448934 }, { title: 'About Trains: Revised', published: 2948445822 }]

const least = min(books)

This works but it shouldn't! Inside min, the two book objects are being compared (which will always result in false). What we expect is this

const least = min(books, b => b.published)

The Solution

The min function should use better typing to insist that a second arg (the getter function) is required when the first argument is not an array of numbers.

I think this can be done using conditional types, I haven't dug into it yet but possibly something like:

const min = <
    TItem extends number | object, 
    TFunc extends TItem extends number ? undefined | (item: number) => number : (item: TItem) => number
> (
  array: readonly TItem[],
  getter?: TFunc
) => {
 // ...
} 

I'm not sure if this will work, just something to explore.

AC

min([0, 1]) // ok
min([{ value: 0 }, { value: 1 }]) // type error
min([{ value: 0 }, { value: 1 }], x => x.value) // ok

isEmpty function bug

isEmpty function is not work for number.

Expected return false
Actual return true

Code from Git
Screenshot 2022-10-06 at 10 58 08 AM

Actual bundled code from npm package
Screenshot 2022-10-06 at 10 55 26 AM

Online Testing result
Screenshot 2022-10-06 at 10 53 48 AM

Spread operator inside reduce is causing unnecessary allocations and poor performance, benchmarks say there is 4x difference

I was wondering why using counting inside my API methods is affecting performance so much and found out the solution.

const countingOriginal = <T, TId extends string | number | symbol>(
  list: readonly T[],
  identity: (item: T) => TId
): Record<TId, number> => {
  return list.reduce((acc, item) => {
    const id = identity(item)
    return {
      ...acc,
      [id]: (acc[id] ?? 0) + 1
    }
  }, {} as Record<TId, number>)
}
const countingFast = <T, TId extends string | number | symbol>(
  list: readonly T[],
  identity: (item: T) => TId
): Record<TId, number> => {
  return list.reduce((acc, item) => {
    const id = identity(item)
    acc[id] = (acc[id] ?? 0) + 1;
    return acc;
  }, {} as Record<TId, number>)
}
xxx@xxx test % deno bench --allow-all
Check file:///Users/xxx/WebstormProjects/test/main_bench.ts
cpu: Apple M1 Pro
runtime: deno 1.29.1 (aarch64-apple-darwin)

file:///Users/sg0227518/WebstormProjects/test/main_bench.ts
benchmark      time (avg)             (min … max)       p75       p99      p995
------------------------------------------------- -----------------------------
fast       112.71 µs/iter  (92.25 µs … 217.08 µs) 127.71 µs 149.92 µs 155.71 µs
original   709.97 µs/iter (685.62 µs … 863.38 µs)    721 µs 814.33 µs    848 µs

summary
  original
   6.3x slower than fast

Questions - Correcting typescript errors is something valid?

I started using radash and found it very cool.
I was looking at the code to see if there was something for me to help and I debuted something, but I don't know if it's something valid.

I particularly don't like red things on the screen, I always try to correct the error or adjust the rule that causes it.
In this case I saw that in many cases in the tests you pass null values to check if the function will work as you expect.
I really don't think it's necessary or recommended to change the typing of the functions themselves, but forcing a type to be able to open the test files and not see an error I think it's valid.

It may be silly of me, that's why I came to ask you your opinion on. And if you think it's valid, I may be doing a PR to correct these errors.

image

Shouldn't `diff([], ['a'])` be `[]`?

Code: diff([], ['a'])
Expected result: []
Actual result: ['a']

The description of the function is:

Given two arrays, returns an array of all items that exist in the first array but do not exist in the second array.
https://radash-docs.vercel.app/docs/array-diff

In other words, an item should be included in the result if:

  • it exists in the first array, and
  • it doesn't exist in the second array.

The first condition is not met for 'a', therefore it should not be included in the result.

If my logic is right, then this line should be changed to if (!root?.length) return [] and this test case should be changed to assert.deepEqual(result, []). It's how Lodash's difference() works by the way.

Version 8 seems to be around the corner, so it seems like a good time for such a breaking change.

Bump rollup-plugin-esbuild to 5.0.0

rollup-plugin-esbuild 4.10.2 was meant to be the major version 5.0.0 since it contained breaking changes (node support dropped below node 14), so I would suggest bumping the dependency version in package.json and yarn.lock to version 5.0.0, to ensure support for rollup 3.

The code contained in version 5.0.0 is exactly the same as version 4.10.2, so there is no change required on our side, other than a simple version bump

For reference here is the github release: https://github.com/egoist/rollup-plugin-esbuild/releases/tag/v5.0.0

Alias `try` as `tryit`

Problem

Because try is a reserved word I find myself doing this a lot import { try as tryit } from 'radash'. It's annoying. I shouldn't have to alias it myself considering its obviously a reserved word -- everyone has to alias it (unless they import the whole library import * as _ from 'radash').

Solution

Add an aliased export to the src/index.ts so it can be imported as tryit.

export {
  tryit as try,
  tryit
} from './curry'

Add a Round function

We need a round function to mimic the lodash round function (allows optional fixed rounding decimal place precision)

example definition: _.round(n: number, p?: number)

This would be added to the numbers category in the docs.

lodash reference: https://lodash.com/docs/4.17.15#round

My current workaround, with JS Math.round, Math.pow, and radash toFloat:

const num = 123.456
const precision = 2
const mult = Math.pow(10, precision)
const rounded = Math.round(num * mult) / mult
const fixed = _.toFloat(rounded.toFixed(precision))
// => 123.46

My Ideal workflow using only radash round:

const num = 123.456
const precision = 2
const rounded = _.round(num, precision)
// => 123.46

[Feature Request-Object] PickBy

Hello!
I was wondering if somebody is up to build this one

const root = {a: 2, b: 3, c: {d: 7}, e:8}
pickBy(root, ((x) => x>2) // result: {b:3, e:8}
pickBy(root, ((x) => x.d == 7) // result {c: {d: 7}}

is similar to array.find() but for objects

Smething like this:

const pickBy = (obj, fn) => Object.fromEntries(Object.entries(obj).filter(fn))
PickBy(root, ((k,v])=> v>2)

Provide ESM option with tree-shakable exports

It would be nice to provide an ESM version of the package, especially for those who use bundlers like Webpack, and add sideEffects: false to package.json, so that if I, say, use import { objectify } from 'radash' in my code, my bundler only includes that function's definition in my bundle, without all the extra stuff.

Cancel debouce

there is some way to cancel debounced function's execution?, this is a very useful use case and since this lib wants to replace lodash, a think this is a must have

isEmpty logic and tests not coinciding

I tried to use isEmpty() and received some illogical results. So a dig a little deeper and found some incorrect logic and tests.

Below is my code to just print to the console some common scenario using isEmpt():

import { isEmpty } from 'radash'

console.log(`\n🟠====== typed | isEmpty() ========`)
// console.log(`\n🔷 Comparing radash isEmpty() to fxts isEmpty()`)

console.log(`\n🔷 Number`)
console.log(`✅ isEmpty(1)`, isEmpty(1)) // false
console.log(`✅ isEmpty(0)`, isEmpty(0)) // false

console.log(`\n🔷 Boolean`)
console.log(`✅ isEmpty(false)`, isEmpty(false)) // false
console.log(`✅ isEmpty(true)`, isEmpty(true)) // false

console.log(`\n🔷 Date`)
console.log(`✅ isEmpty(new Date())`, isEmpty(new Date())) // false

console.log(`\n🔷 Null and Undefined`)
console.log(`✅ isEmpty(undefined)`, isEmpty(undefined)) // true
console.log(`✅ isEmpty(null)`, isEmpty(null)) // true

console.log(`\n🔷 Object`)
console.log(`✅ isEmpty({})`, isEmpty({})) // true
console.log(`✅ isEmpty({a:1})`, isEmpty({ a: 1 })) // false

console.log(`\n🔷 Array`)
console.log(`✅ isEmpty([])`, isEmpty([])) // true
console.log(`✅ isEmpty([1])`, isEmpty([1])) // false

console.log(`\n🔷 String/Char`)
console.log(`✅ isEmpty("")`, isEmpty('')) // true
console.log(`✅ isEmpty("a")`, isEmpty('a')) // false

console.log(`\n🔷 Function`)
console.log(
  `✅ isEmpty(function(){})`,
  isEmpty(function () {})
) // false

console.log(`\n🔷 Symbol`)
console.log(`✅ isEmpty(Symbol(""))`, isEmpty(Symbol(''))) // false

... and below is the result:

image

As you can see, my expectations as indicated by the // comments in the console statements do differ from yours significantly.

I looked at your tests and it seems the test would suggest what I received. For instance, take your tests from your test suite for the first two console statements above:

    test('returns true for number greater than 0', () => {
      const input = 22
      const result = _.isEmpty(input)
      assert.isTrue(result)
    })
    test('returns true for number 0', () => {
      const input = 0
      const result = _.isEmpty(input)
      assert.isTrue(result)
    })

The logic is suggesting that both digits test return true. Is it an error?

Another example pertaining to arrays...

console.log(\n🔷 Array)
console.log(✅ isEmpty([]), isEmpty([])) // true
console.log(✅ isEmpty([1]), isEmpty([1])) // false

🔷 Array
✅ isEmpty([]) true
✅ isEmpty([1]) true

      const input = [1, 2, 3]
      const result = _.isEmpty(input)
      assert.isFalse(result)
    })
    test('returns true for empty array', () => {
      const input = []
      const result = _.isEmpty(input)
      assert.isTrue(result)
    })

Again the test for isEmpty(array) is not in keeping with what I am expecting or the test assertion - an array with no elements returns true and an array with elements returns false (as your test) and what I expect. But, as you see above true is being returned by both.

Cheers

feature request: `Array.removeOrAppend`

A common use case with implementing toggle filters in a UI is to add the active filters to an array. Let's say we want to filter a list of items by color, and we can have multiple color filters active at the same time.

type Filter = 'red' | 'blue' | 'green'

const activeFilters: Filter[] = ['red', 'blue']

Now when the user deselects the red filter, we want to remove it from the array of activeFilters. We could do that using Array.filter. And when the user selects the red filter again, it should be added back to the list. Because the order of the filters does not matter, we can append. The implementation for this could look like this (imagine setActiveFilters to trigger a state update like in React):

const handleFilterToggle = (filter: Filter) => {
  // deselect the filter if it is active
  if(activeFilters.includes(filter)) {
    setActiveFilters(activeFilters.filter(f => f !== filter))
  } else {
    // append the filter to activeFilters
    setActiveFilters([...activeFilters, filter])
  }
}

It would be quite useful to have this implementation as a dedicated function that could be called something like toggleItem or removeOrAppend. The proposed function signature could look like this:

removeOrAppend(list, item, compareFn?: (item) => any)

where list is the given array, item is the element to remove or append and compareFn is a an optional function that can be passed in to determine an item's identity like in unique.

Example usage:

const activeFilters: Filter[] = ['red', 'blue']

const removedRed = removeOrAppend(activeFilters, 'red') // ['blue']
const addedGreen = removeOrAppend(activeFilters, 'green') // ['blue', 'green']

I would love this to be a function as it is quite a common use case. Please let me know your opinion on this :)

examples of use cases

Hi, when reading through the array of functionality I see interesting functions that I have trouble see how one would ever use. This is not a criticism, more a lack of imagination on my part. If you added to the docs a section for each like "This is useful when doing such and such task" e.g cluster. I imagine this may be used for paging or some such thing. I also imagine that at least most of these functions are included because you personally had a use case or could see a use case for them. If I knew those use cases then I would be inspired perhaps to use these functions when they came up.
Just a thought.
Thanks,
Raif

Potential perf issue

Why the destructuring assignment is needed here (and on some other places):
https://github.com/rayepps/radash/blob/master/src/object.ts#L34

/**
 * Map over all the keys of an object to return
 * a new object
 */
export const mapKeys = <
  TValue,
  TKey extends string | number | symbol,
  TNewKey extends string | number | symbol
>(
  obj: Record<TKey, TValue>,
  mapFunc: (key: TKey, value: TValue) => TNewKey
): Record<TNewKey, TValue> => {
  return Object.keys(obj).reduce(
    (acc, key) => {
      acc[mapFunc(key as TKey, obj[key])] = obj[key];
      return acc;
    },
    {} as Record<TNewKey, TValue>
  )
}

might be more performant. Or, maybe this one:

export const mapKeys = <
  TValue,
  TKey extends string | number | symbol,
  TNewKey extends string | number | symbol
>(
  obj: Record<TKey, TValue>,
  mapFunc: (key: TKey, value: TValue) => TNewKey
): Record<TNewKey, TValue> => {
  const acc = {} as Record<TNewKey, TValue>;
  const fn = ([key, value]) => {
    acc[mapFunc(key as TKey, value)] = value;
  };
  Object.entries(obj).forEach(fn);
  return acc;
}

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.