Skip to content

Commit

Permalink
v2.0.0 release
Browse files Browse the repository at this point in the history
  • Loading branch information
Ray Epps committed Jul 14, 2022
1 parent ff0236b commit 1748037
Show file tree
Hide file tree
Showing 21 changed files with 983 additions and 460 deletions.
27 changes: 27 additions & 0 deletions LICENSE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
Copyright (c) 2022, radash
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
69 changes: 58 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,69 @@
# Radash
Its like Ramda or Lodash because its just a library of simple, small, utility functions. Its not like Ramda or Lodash because you can open it up and easily read and understand all of them.

:loud_sound: `/raw-dash/`

<div align="center">
<p align="center">
<img src="./banner.png" alt="radash" width="100%" style="border-radius:4px" />
</p>
</div>
<div>
<h3 align="center">
Functional utility library - modern, simple, typed, powerful
<br />
<h4 align="center">
<a href="https://radash-docs.vercel.app" target="_blank">
Full Documentation
</a>
</h4>
</h3>
</div>

## Install
```
yarn add radash
```
or
```
npm install radash
```
Types are included, if your into that.

## Usage
A very brief kitchen sink. See the full documentation [here](https://radash-docs.vercel.app).

```ts
import _ from 'radash'
import * as _ from 'radash'

const list = [1, 2, 3, 4, 5, 6]
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'
}]

const oddsSquared = _.select(list, x = x*x, x => x % 2)
const { odds, evens } = _.group(list, x => x % 2 ? 'odds' : 'evens' )
```
_.max(gods, g => g.rank) // => ra
_.sum(gods, g => g.rank) // => 268
_.fork(gods, g => g.culture === 'norse') // => [[loki], [ra, zues]]
_.sort(gods, g => g.rank) // => [ra, zues, 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, zues, 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)
})
```
Binary file added banner.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "radash",
"version": "1.14.1",
"version": "2.0.0",
"description": "A collection of useful and helpful functions",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down
64 changes: 38 additions & 26 deletions src/array.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { iter } from './curry'
import { random } from './number'

/**
Expand Down Expand Up @@ -37,14 +36,14 @@ export const sum = <T extends number | object>(array: T[], fn?: (item: T) => num
/**
* Get the first item in an array or a default value
*/
export const first = <T>(array: T[], defaultValue: T | null | undefined = null) => {
export const first = <T>(array: T[], defaultValue: T | null | undefined = undefined) => {
return array?.length > 0 ? array[0] : defaultValue
}

/**
* Get the last item in an array or a default value
*/
export const last = <T>(array: T[], defaultValue: T | null | undefined = null) => {
export const last = <T>(array: T[], defaultValue: T | null | undefined = undefined) => {
return array?.length > 0 ? array[array.length - 1] : defaultValue
}

Expand Down Expand Up @@ -84,7 +83,7 @@ export const replace = <T>(list: T[], newItem: T, match: (item: T, idx: number)
* Convert an array to a dictionary by mapping each item
* into a dictionary key & value
*/
export const dict = <T, Key extends string | number | symbol, Value>(array: T[], getKey: (item: T) => Key, getValue: (item: T) => Value): Record<Key, Value> => {
export const objectify = <T, Key extends string | number | symbol, Value>(array: T[], getKey: (item: T) => Key, getValue: (item: T) => Value): Record<Key, Value> => {
return array.reduce((acc, item) => ({
...acc,
[getKey(item)]: getValue(item)
Expand Down Expand Up @@ -153,7 +152,6 @@ export const shuffle = <T>(array: T[]): T[] => {
* null if the list is empty
*/
export const draw = <T>(array: T[]): T | null => {
const min = 0
const max = array.length
if (max === 0) {
return null
Expand All @@ -166,13 +164,13 @@ export const draw = <T>(array: T[]): T | null => {
* Creates a generator that will produce an iteration through
* the range of number as requested.
*
* @example for (const i of _.range(3, 3*3, 3))
* @example for (const i of _.range(3, 3*3, 3)) { console.log(i) }
*/
export const range = (start: number, end: number, step: number = 1): number[] => {
const itemCount = (end - start) / step
return iter(itemCount, (list, idx) => {
return [...list, ((idx * step) + start)]
}, [start])
export function * range (start: number, end: number, step: number = 1): Generator<number> {
const count = (end - start) / step
for (let idx = start; idx <= count; idx++) {
yield ((idx * step) + start)
}
}

/**
Expand All @@ -189,13 +187,13 @@ export const flat = <T>(lists: T[][]): T[] => {
* Given two arrays, returns true if any
* elements intersect
*/
export const intersects = <T> (listA: T[], listB: T[]): boolean => {
export const intersects = <T, K extends string | number | symbol> (listA: T[], listB: T[], identity?: (t: T) => K): boolean => {
if (!listA || !listB) return false
// Iterate the shorter list
const [root, other] = listA.length > listB.length
? [listB, listA]
: [listA, listB]
return root.some(value => other.includes(value))
const ident = identity ?? ((x: T) => x as unknown as K)
const dictB = listB.reduce((acc, item) => ({
...acc, [ident(item)]: true
}), {} as Record<string | number | symbol, true>)
return listA.some(value => dictB[ident(value)])
}

/**
Expand All @@ -219,7 +217,7 @@ export const fork = <T> (list: T[], condition: (item: T) => boolean): [T[], T[]]
* and replace items matched by the matcher func in the
* first place.
*/
export const zip = <T> (root: T[], others: T[], matcher: (item: T) => any) => {
export const merge = <T> (root: T[], others: T[], matcher: (item: T) => any) => {
if (!others && !root) return []
if (!others) return root
if (!root) return []
Expand Down Expand Up @@ -249,13 +247,27 @@ export const replaceOrAppend = <T> (list: T[], newItem: T, match: (a: T, idx: nu
return [...list, newItem]
}

export const sift = <T> (list: T[]) => {
return list?.filter(x => !!x) ?? []
}

/**
* Remove an item from an array given a matching function
* Like a reduce but does not require an array.
* Only need a number and will iterate the function
* as many times as specified.
*
* NOTE: This is NOT zero indexed. If you pass count=5
* you will get 1, 2, 3, 4, 5 iteration in the callback
* function
*/
export const remove = <T> (list: T[], match: (a: T) => boolean) => {
if (!list) return []
return list.reduce((acc, item) => {
if (match(item)) return acc
else return [...acc, item]
}, [] as T[])
}
export const iterate = <T>(
count: number,
func: (currentValue: T, iteration: number) => T,
initValue: T
) => {
let value = initValue
for (let i = 1; i <= count; i++) {
value = func(value, i)
}
return value
}
127 changes: 108 additions & 19 deletions src/async.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@

export const asyncReduce = <T, K> (array: T[]) => async (
export const reduce = async <T, K> (
array: T[],
asyncReducer: (acc: K, item: T) => Promise<K>,
initValue?: K
): Promise<K> => {
Expand All @@ -15,17 +16,8 @@ export const asyncReduce = <T, K> (array: T[]) => async (
return value
}

// Javascript not handling types correctly on the
// original reduce func because of the func => func => result
export const asyncReduceV2 = async <T, K> (
export const map = async <T, K> (
array: T[],
asyncReducer: (acc: K, item: T) => Promise<K>,
initValue?: K
): Promise<K> => {
return await asyncReduce(array)(asyncReducer, initValue) as K
}

export const asyncMap = <T, K> (array: T[]) => async (
asyncMapFunc: (item: T) => Promise<K>
): Promise<K[]> => {
let result = []
Expand All @@ -36,11 +28,108 @@ export const asyncMap = <T, K> (array: T[]) => async (
return result
}

// Javascript not handling types correctly on the
// original map func because of the func => func => result
export const asyncMapV2 = async <T, K> (
array: T[],
asyncMapFunc: (item: T) => Promise<K>
): Promise<K[]> => {
return await asyncMap(array)(asyncMapFunc) as K[]
}
/**
* Useful when for script like things where cleanup
* should be done on fail or sucess no matter.
*
* You can call defer many times to register many
* defered functions that will all be called when
* the function exits in any state.
*
*
* Ex.
* ```
* const main = _.defered(async (defer) => {
*
* fs.writeFile(`${deployment.id}.logs`)
* defer(() => {
* fs.remove(`${deployment.id}.logs`)
* })
*
* s3.download(...)
* defer(() => {
* fs.remove(...)
* })
*
* api.deployments.updateStatus('in_progress')
* defer((err) => {
* api.deployments.updateStatus(err ? 'failed' : 'success')
* })
*
* })
* ```
*/
export const defer = async <
TResponse
>(
func: (register: (fn: (error?: any) => void) => void) => Promise<TResponse>
): Promise<TResponse> => {
let funcs: Function[] = []
try {
const result = await func(funcs.push.bind(funcs))
for (const f of funcs) f()
return result as TResponse
} catch (err) {
for (const f of funcs) f(err)
throw err
}
}

/**
* Simple retry
*
* Ex.
* ```
* await _.async.retry(async (exit) => {
* const { error, result } = await api.users.list()
* if (error.reason === 'UNAUTHORIZED') {
* exit('Not Authenticated)
* }
* return result
* })()
*
* const listUsers = _.async.retry(async (exit, args) => {
* const { result } = await api.users.list(args)
* return result
* })
* await listUsers({ page: 2, limit: 200 })
* ```
*/
export const retry = async <
TResponse
> (
func: (exit: (err: any) => void) => Promise<TResponse>,
retries: number = 3,
delay: number | null = 100
): Promise<TResponse> => {
for (let i = 1; i <= retries; i++) {
try {
return await func((err: any) => {
throw { _exited: err }
})
} catch (err) {
if (err._exited) throw err._exited
if (i === retries) throw err
}
if (delay) {
await sleep(delay)
}
}
}

export const sleep = (milliseconds: number) => {
return new Promise(res => setTimeout(res, milliseconds))
}

type ArgumentsType<T> = T extends (...args: infer U) => any ? U : never
type UnwrapPromisify<T> = T extends Promise<infer U> ? U : T

export const tryit = <TFunction extends (...args: any) => any>(func: TFunction) => {
return async (...args: ArgumentsType<TFunction>): Promise<[Error, UnwrapPromisify<ReturnType<TFunction>>]> => {
try {
return [null, await func(...(args as any))]
} catch (err) {
return [err as any, null]
}
}
}
Loading

0 comments on commit 1748037

Please sign in to comment.