Giter Club home page Giter Club logo

froebel's Introduction

Froebel - a strictly typed TypeScript utility library.

This is my (WIP) personal collection of TypeScript helper functions and utilities that I use across different projects. Think an opinionated version of lodash, but with first-class types.

If you have an idea for a utility that might make a good addition to this collection, please open an issue and suggest its inclusion.

Runs in Deno, Node.js, and the Browser. Get it from deno.land or npm.

Installation

Using npm

npm install froebel

and — assuming a module-compatible system like webpack — import as:

import { someUtility } from 'froebel';
// you can also import the utility you need directly:
import memoize from 'froebel/memoize';

Using Deno

import { someUtility } from "https://deno.land/x/[email protected]/mod.ts";
// or import just the utility you need:
import memoize from "https://deno.land/x/[email protected]/memoize.ts"

Available Utilities

Each category also has a file exporting only the utilities in that category, so if you want to only import utilities from one category, you could import them as

import { throttle, debounce } from "froebel/function";

A few utils are exported from multiple categories but will only be listed here once. For example isPromise is exported from both the promise and the predicate category.

Table of Contents

Function

ident

(value: T) => T

source | tests

Identity function.

Import

/* Node: */  import ident from "froebel/ident";
/* Deno: */  import ident from "https://deno.land/x/[email protected]/ident.ts";

noop

() => void

source | tests

Import

/* Node: */  import noop from "froebel/noop";
/* Deno: */  import noop from "https://deno.land/x/[email protected]/noop.ts";

partial

(fun: T, ...argsLeft: PL) => (...argsRight: PR) => ReturnType<T>

source | tests

Partially apply a function.

Import

/* Node: */  import partial from "froebel/partial";
/* Deno: */  import partial from "https://deno.land/x/[email protected]/partial.ts";

Example

const divide = (dividend: number, divisor: number) => dividend / divisor

// (divisor: number) => number
const oneOver = partial(divide, 1)

// prints: 0.25
console.log(oneOver(4))

forward

(fun: T, ...argsRight: PR) => (...argsLeft: PL) => ReturnType<T>

source | tests

Given a function and its nth..last arguments, return a function accepting arguments 0..n-1.

Import

/* Node: */  import forward from "froebel/forward";
/* Deno: */  import forward from "https://deno.land/x/[email protected]/forward.ts";

Examples

const divide = (dividend: number, divisor: number) => dividend / divisor

// (dividend: number) => number
const divideBy2 = forward(divide, 2)

// prints: 0.5
console.log(divideBy2(1))
const fetchUrl = async (protocol: string, domain: string, path: string) =>
  await fetch(`${protocol}://${domain}/${path}`)

const fetchRepo = forward(fetchUrl, 'github.com', 'MathisBullinger/froebel')

const viaHTTPS = await fetchRepo('https')

unary

(fun: T) => Unary<T>

source | tests

Turns fun into a unary function (a function that only accepts one argument).

Note: fun must accept at least one argument and must not require more than one argument.

Import

/* Node: */  import unary from "froebel/unary";
/* Deno: */  import unary from "https://deno.land/x/[email protected]/unary.ts";

Example

['1', '2', '3'].map(unary(parseInt))  // -> [1, 2, 3]

callAll

(funs: F[], ...args: P) => ReturnTypes<F>

source | tests

Take a list of functions that accept the same parameters and call them all with the provided arguments.

Import

/* Node: */  import callAll from "froebel/callAll";
/* Deno: */  import callAll from "https://deno.land/x/[email protected]/callAll.ts";

Example

const mult = (a: number, b: number) => a * b
const div  = (a: number, b: number) => a / b

// prints: [8, 2]
console.log( callAll([mult, div], 4, 2) )

pipe

(...funs: T) => PipedFun<T>

source | tests

Given a list of functions returns a function that will execute the given functions one after another, always passing the result of the previous function as an argument to the next function.

If one of the given functions returns a promise, the promise will be resolved before being passed to the next function.

Import

/* Node: */  import pipe from "froebel/pipe";
/* Deno: */  import pipe from "https://deno.land/x/[email protected]/pipe.ts";

Example

const join = (...chars: string[]) => chars.join('')
pipe(join, parseInt)('1', '2', '3')  // -> 123

const square = (n: number) => n ** 2

// this is equivalent to: square(square(square(2)))
pipe(square, square, square)(2)  // -> 256

// also works with promises:
fetchNumber :: async () => Promise<number>
pipe(fetchNumber, n => n.toString())  // async () => Promise<string>

applyPipe

(arg: Parameters<T[0]>[0], ...funs: T) => CheckPipe<T, CarryReturn<ReturnTypes<T>, Parameters<T[0]>>, false>

source | tests

Like pipe but takes an argument as its first parameter and invokes the pipe with it.

Note: unlike in pipe, the first function of the pipe must take exactly one argument.

see pipe

Import

/* Node: */  import { applyPipe } from "froebel/pipe";
/* Deno: */  import { applyPipe } from "https://deno.land/x/[email protected]/pipe.ts";

Example

applyPipe(2, double, square, half)  // -> 8

bundle

(...funs: λ<T>[]) => (...args: T) => Promise<void>

source | tests

Given a list of functions that accept the same parameters, returns a function that takes these parameters and invokes all of the given functions.

The returned function returns a promise that resolves once all functions returned/resolved and rejects if any of the functions throws/rejects - but only after all returned promises have been settled.

Import

/* Node: */  import bundle from "froebel/bundle";
/* Deno: */  import bundle from "https://deno.land/x/[email protected]/bundle.ts";

bundleSync

(...funs: λ<T>[]) => (...args: T) => void

source | tests

Same as bundle, but return synchronously.

If any of the functions throws an error synchronously, none of the functions after it will be invoked and the error will propagate.

Import

/* Node: */  import { bundleSync } from "froebel/bundle";
/* Deno: */  import { bundleSync } from "https://deno.land/x/[email protected]/bundle.ts";

nullishChain

(...funs: [] | [FF, ...FR[]]) => (...args: Parameters<FF>) => ReturnType<FF> | ReturnType<FR[number]>

source | tests

Given a list of functions that accept the same parameters, returns a function that given these arguments returns the result of the first function whose result is not nullish.

This is equivalent to chaining together invocations of the passed in functions with the given arguments with nullish coalescing (??) operators.

Import

/* Node: */  import { nullishChain } from "froebel/nullishChain";
/* Deno: */  import { nullishChain } from "https://deno.land/x/[email protected]/nullishChain.ts";

Example

const isAdult   = (age: number) => { if (n >= 18) return 'adult' }
const isToddler = (age: number) => { if (n <= 3) return 'toddler' }

const ageGroup = nullishChain(isAdult, isToddler, () => 'child')

// this is functionally equivalent to:
const ageGroup = age => isAdult(age) ?? isToddler(age) ?? 'child'

ageGroup(1)  // prints: 'toddler'
ageGroup(10) // prints: 'child'
ageGroup(50) // prints: 'adult'

asyncNullishChain

(...funs: [] | [FF, ...FR[]]) => (...args: Parameters<FF>) => Promise<PromType<ReturnType<FF>> | PromType<ReturnType<FR[number]>>>

source | tests

Same as nullishChain but accept asynchronous functions too.

Import

/* Node: */  import { asyncNullishChain } from "froebel/nullishChain";
/* Deno: */  import { asyncNullishChain } from "https://deno.land/x/[email protected]/nullishChain.ts";

Example

const readFromCache = (id: string): Resource => { if (id in cache) return cache[id] }
const readFromFile  = (id: string): Resource => { if (fileExists(id)) return readFile(id) }
const fetchFromNet  = async (id: string): Promise<Resource> => await fetch(`someURL/${id}`)

// async (id: string) => Promise<Resource>
const getResource = asyncNullishChain(readFromCache, readFromFile, fetchFromNet)

throttle

(fun: T, ms: number, opts?: {leading: boolean, trailing: boolean}) => λ<Parameters<T>, void> & {[cancel]: () => void}

source | tests

Create a throttled function that invokes fun at most every ms milliseconds.

fun is invoked with the last arguments passed to the throttled function.

Calling [throttle.cancel]() on the throttled function will cancel the currently scheduled invocation.

Import

/* Node: */  import throttle from "froebel/throttle";
/* Deno: */  import throttle from "https://deno.land/x/[email protected]/throttle.ts";

debounce

(fun: T, ms: number) => λ<Parameters<T>, void> & {[cancel]: () => void}

source | tests

Creates a debounced function that delays invoking fun until ms milliseconds have passed since the last invocation of the debounced function.

fun is invoked with the last arguments passed to the debounced function.

Calling [debounce.cancel]() on the debounced function will cancel the currently scheduled invocation.

Import

/* Node: */  import debounce from "froebel/debounce";
/* Deno: */  import debounce from "https://deno.land/x/[email protected]/debounce.ts";

memoize

(fun: T, opt: {limit: number, weak: W, key: (...args: Parameters<T>) => K}) => T & {cache: W extends false ? Map<K, ReturnType<T>> : Cache<K, ReturnType<T>>}

source | tests

Returns a copy of fun that remembers its result for any given arguments and only invokes fun for unknown arguments.

The cache key is computed using the key function. The default key function simply stringifies the arguments.

If limit is specified, only the limit-last entries are kept in cache.

The function's cache is available at memoized.cache.

If opt.weak is true, non-primitive cache keys are stored in a WeakMap. This behavior might for example be useful if you want to memoize some calculation including a DOM Node without holding on to a reference of that node. Using weak keys prohibits setting a limit.

Import

/* Node: */  import memoize from "froebel/memoize";
/* Deno: */  import memoize from "https://deno.land/x/[email protected]/memoize.ts";

Examples

const expensiveCalculation = (a: number, b: number) => {
  console.log(`calculate ${a} + ${b}`)
  return a + b
}
const calc = memoize(expensiveCalculation)

console.log( calc(1, 2) )
// calculate 1 + 2
// 3
console.log( calc(20, 5) )
// calculate 20 + 5
// 25
console.log( calc(20, 5) )
// 25
console.log( calc(1, 2) )
// 3

calc.cache.clear()
console.log( calc(1, 2) )
// calculate 1 + 2
// 3
const logIfDifferent = memoize(
  (msg: string) => console.log(msg),
  {
    limit: 1,
    key: msg => msg
  }
)

logIfDifferent('a')
logIfDifferent('a')
logIfDifferent('b')
logIfDifferent('a')

// a
// b
// a

limitInvocations

(fun: T, limit: number, ...funs: ExcS<T>) => T

source | tests

Returns a version of the function fun that can only be invoked limit times. An optional except function will be called with the same parameters on any additional invocations.

If fun returns anything but void (or Promise<void>), supplying an except function is mandatory.

The except function must have the same return type as fun, or — if fun returns a promise — it may return the type that the promise resolves to synchronously.

The except function may also throw instead of returning a value.

Import

/* Node: */  import { limitInvocations } from "froebel/invoke";
/* Deno: */  import { limitInvocations } from "https://deno.land/x/[email protected]/invoke.ts";

once

(fun: T, ...funs: ExcS<T>) => T

source | tests

Special case of limitInvocations. fun can only be invoked once.

see limitInvocations

Import

/* Node: */  import { once } from "froebel/invoke";
/* Deno: */  import { once } from "https://deno.land/x/[email protected]/invoke.ts";

List

atWrap

(arr: T[], i: number) => T

source | tests

Access list at i % length. Negative indexes start indexing the last element as [-1] and wrap around to the back.

Import

/* Node: */  import atWrap from "froebel/atWrap";
/* Deno: */  import atWrap from "https://deno.land/x/[email protected]/atWrap.ts";

zip

(...lists: T) => Zip<T>

source | tests

Takes multiple lists and returns a list of tuples containing the value in each list at the current index. If the lists are of different lengths, the returned list of tuples has the length of the shortest passed in list.

Import

/* Node: */  import zip from "froebel/zip";
/* Deno: */  import zip from "https://deno.land/x/[email protected]/zip.ts";

Example

const pairs = zip([1,2,3], ['a','b','c'])
console.log(pairs) // prints: [[1,'a'], [2,'b'], [3,'c']]

zipWith

(zipper: (...args: {[I in string | number | symbol]: U}) => U, ...lists: T) => U[]

source | tests

Same as zip but also takes a zipper function, that is called for each index with the element at current index in each list as arguments. The result of zipper is the element at current index in the list returned from zipWith.

Import

/* Node: */  import { zipWith } from "froebel/zip";
/* Deno: */  import { zipWith } from "https://deno.land/x/[email protected]/zip.ts";

Example

const sums = zipWith((a,b) => a+b, [1,2,3], [4,5,6])
console.log(sums) // prints: [5,7,9]

unzip

(...zipped: T[][]) => Unzip<T>

source | tests

Reverse of zip. Takes a list of tuples and deconstructs them into an array (of length of the tuples length) of lists each containing all the elements in all tuples at the lists index.

Import

/* Node: */  import unzip from "froebel/unzip";
/* Deno: */  import unzip from "https://deno.land/x/[email protected]/unzip.ts";

Example

const [nums, chars] = unzip([1,'a'], [2,'b'], [3,'c'])
console.log(nums)  // prints: [1, 2, 3]
console.log(chars) // prints: ['a','b','c']

unzipWith

(zipped: T[][], ...unzippers: U) => {[I in string | number | symbol]: ReturnType<U[I]>}

source | tests

Same as unzip but accepts an unzipper function for each tuple index. The unzipper's return value is used as the value in the list at that index returned from unzipWith.

The unzipper takes the current element as its first argument, an accumulator as second argument (initially undefined) and its return value is the accumulator passed into the next invocation.

Import

/* Node: */  import { unzipWith } from "froebel/unzip";
/* Deno: */  import { unzipWith } from "https://deno.land/x/[email protected]/unzip.ts";

Example

const [nums, str] = unzipWith(
  [ [1,'a'], [2,'b'], [3,'c'] ],
  (n, acc: number[] = []) => [...acc, n],
  (c, str = '') => str + c
)

console.log(nums) // prints: [1, 2, 3]
console.log(str)  // prints: 'abc'

batch

(list: T[], batchSize: number) => T[][]

source | tests

Takes a list and returns it in multiple smaller lists of the size batchSize. The last batch may be smaller than batchSize depending on if list size is divisible by batchSize.

Import

/* Node: */  import batch from "froebel/batch";
/* Deno: */  import batch from "https://deno.land/x/[email protected]/batch.ts";

Example

batch([1,2,3,4,5], 2)  // -> [ [1,2], [3,4], [5] ]

partition

(list: T[], predicate: (el: T) => el is S) => [S[], Exclude<T, S>[]]

source | tests

Takes a list and returns a pair of lists containing: the elements that match the predicate and those that don't, respectively.

Think of it as filter, but the elements that don't pass the filter aren't discarded but returned in a separate list instead.

Import

/* Node: */  import partition from "froebel/partition";
/* Deno: */  import partition from "https://deno.land/x/[email protected]/partition.ts";

Example

const [strings, numbers] = partition(
  ['a', 'b', 1, 'c', 2, 3],
  (el): el is string => typeof el === 'string'
)
// strings: ["a", "b", "c"]
// numbers: [1, 2, 3]

shuffle

(list: T[]) => T[]

source | tests

Shuffles list using the Fisher-Yates shuffle algorithm. The original list is not modified and the shuffled list is returned.

Import

/* Node: */  import shuffle from "froebel/shuffle";
/* Deno: */  import shuffle from "https://deno.land/x/[email protected]/shuffle.ts";

shuffleInPlace

(list: unknown[]) => void

source | tests

Same as shuffle but shuffles list in place.

Import

/* Node: */  import { shuffleInPlace } from "froebel/shuffle";
/* Deno: */  import { shuffleInPlace } from "https://deno.land/x/[email protected]/shuffle.ts";

take

(n: number, list: Iterable<T>) => T[]

source | tests

Takes n elements from the iterable list and returns them as an array.

Import

/* Node: */  import { take } from "froebel/list";
/* Deno: */  import { take } from "https://deno.land/x/[email protected]/list.ts";

Example

take(5, repeat(1, 2))  // -> [1, 2, 1, 2, 1]
take(3, [1, 2, 3, 4])  // -> [1, 2, 3]
take(3, [1, 2])        // -> [1, 2]

range

source | tests

Creates a range between two values.

see numberRange and alphaRange

Import

/* Node: */  import range from "froebel/range";
/* Deno: */  import range from "https://deno.land/x/[email protected]/range.ts";

numberRange

(start: number, end: number, step: number) => number[]

source | tests

Constructs a numeric between start and end inclusively.

Import

/* Node: */  import { numberRange } from "froebel/range";
/* Deno: */  import { numberRange } from "https://deno.land/x/[email protected]/range.ts";

Example

range(2, 6)      // -> [2, 3, 4, 5, 6]
range(8, 9, .3)  // -> [8, 8.3, 8.6, 8.9]
range(3, -2)     // -> [3, 2, 1, 0, -1, -2]

alphaRange

(start: string, end: string) => string[]

source | tests

Constructs a range between characters.

Import

/* Node: */  import { alphaRange } from "froebel/range";
/* Deno: */  import { alphaRange } from "https://deno.land/x/[email protected]/range.ts";

Example

range('a', 'd')  // -> ['a', 'b', 'c', 'd']
range('Z', 'W')  // -> ['Z', 'Y', 'X', 'W']

Iterable

repeat

(...sequence: [T, ...T[]]) => Generator<T>

source | tests

Returns a generator that repeats sequence.

Import

/* Node: */  import repeat from "froebel/repeat";
/* Deno: */  import repeat from "https://deno.land/x/[email protected]/repeat.ts";

Example

// prints: 1, 2, 3, 1, 2, 3, ...
for (const n of repeat(1, 2, 3))
  console.log(n)

take

(n: number, list: Iterable<T>) => Generator<T>

source | tests

Takes n elements from the iterable list and returns them as a generator.

Import

/* Node: */  import { take } from "froebel/iterable";
/* Deno: */  import { take } from "https://deno.land/x/[email protected]/iterable.ts";

Example

[...take(5, repeat(1, 2))]  // -> [1, 2, 1, 2, 1]
[...take(3, [1, 2, 3, 4])]  // -> [1, 2, 3]
[...take(3, [1, 2])]        // -> [1, 2]

Object

pick

(obj: T, ...keys: K[]) => Pick<T, K>

source | tests

From obj, create a new object that only includes keys.

Import

/* Node: */  import pick from "froebel/pick";
/* Deno: */  import pick from "https://deno.land/x/[email protected]/pick.ts";

Example

pick({ a: 1, b: 2, c: 3 }, 'a', 'c') // { a: 1, c: 3 }

omit

(obj: T, ...keys: K[]) => Omit<T, K>

source | tests

From obj, create a new object that does not include keys.

Import

/* Node: */  import omit from "froebel/omit";
/* Deno: */  import omit from "https://deno.land/x/[email protected]/omit.ts";

Example

omit({ a: 1, b: 2, c: 3 }, 'a', 'c') // { b: 2 }

map

(data: Map<IK, IV>, callback: (key: IK, value: IV) => [OK, OV]) => Map<OK, OV>

source | tests

Map over data. data can be a regular object, a Map, a Set, or an array.

Import

/* Node: */  import map from "froebel/map";
/* Deno: */  import map from "https://deno.land/x/[email protected]/map.ts";

Examples

// -> { a: 1, b: 2 }
map({ a: '1', b: '2' }, (key, value) => [key, parseInt(value)])
// -> Map([ [2, 1], [4, 3] ])
map(new Map([ [1, 2], [3, 4] ]), (key, value) => [key + 1, value - 1])

Path

select

(obj: T, ...path: P) => PickPath<T, P>

source | tests

Returns the value in obj at path. If the given path does not exist, the symbol none is returned.

Import

/* Node: */  import select from "froebel/select";
/* Deno: */  import select from "https://deno.land/x/[email protected]/select.ts";

Example

// -> 'something'
select(
  { a: { deeply: [{ nested: { object: 'something' } }] } },
  'a', 'deeply', 0, 'nested', 'object'
)

Equality

oneOf

(value: T, ...cmps: TT) => value is TT[number]

source | tests

Checks if v is one of cmps.

Import

/* Node: */  import oneOf from "froebel/oneOf";
/* Deno: */  import oneOf from "https://deno.land/x/[email protected]/oneOf.ts";

equal

(a: unknown, b: unknown) => boolean

source | tests

Checks if a and b are structurally equal using the following algorithm:

  • primitives are compared by value
  • functions are compared by reference
  • objects (including arrays) are checked to have the same properties and their values are compared recursively using the same algorithm

Import

/* Node: */  import equal from "froebel/equal";
/* Deno: */  import equal from "https://deno.land/x/[email protected]/equal.ts";

clone

(value: T) => T

source | tests

Returns a copied version of value.

If value is primitive, returns value. Otherwise, properties of value are copied recursively. Only value's own enumerable properties are cloned. Arrays are cloned by mapping over their elements.

If a path in value references itself or a parent path, then in the resulting object that path will also reference the path it referenced in the original object (but now in the resuling object instead of the original).

Import

/* Node: */  import clone from "froebel/clone";
/* Deno: */  import clone from "https://deno.land/x/[email protected]/clone.ts";

merge

(a: A, b: B) => Merge<A, B>

source | tests

Recursively merges A and B. If a property in A and B is of a different type (i.e. it's not an array, Set, Map, or plain object in both, the value from B will be used in the result).

If there are self-references in the cloned values, array / Set items, or Map keys or values, they will also be self-referencing in the result.

Import

/* Node: */  import merge from "froebel/merge";
/* Deno: */  import merge from "https://deno.land/x/[email protected]/merge.ts";

Promise

promisify

(withCallback: T, resultIndex?: N, errorIndex: null | number) => Promisified<T, N>

source | tests

Turns a function accepting a callback into a function returning a promise. You can specify in which parameter (if any) the callback expects to receive a result and in which it expects an error. Pass null to resultIndex or errorIndex if no result or errors are passed to the callback. By default the first argument passed to the callback is interpreted as result and none of the arguments as error (if the function accepting the callback throws or rejects, that will still result in the promisified function rejecting).

The callbackFirst property allows passing additional parameters after the callback and callbackLast will pass additional parameters before the callback.

Import

/* Node: */  import promisify from "froebel/promisify";
/* Deno: */  import promisify from "https://deno.land/x/[email protected]/promisify.ts";

Examples

const notify = (cb: (msg: string) => void) => { msg('something') }
const waitForMessage = promisify(notify)
await waitForMessage()  // -> 'something'

// here result is passed at index 1 and errors at index 0.
const callbackAPI = (cb: (error?: Error, data?: unknown) => void) => {}
const asyncAPI = promisify(callbackAPI, 1, 0)
const sleep = promisify(setTimeout).callbackFirst
await sleep(200)
const fs = require('node:fs');
const stat = promisify(fs.stat, 1, 0).callbackLast

try {
  const stats = await stat('.');
  console.log(`This directory is owned by ${stats.uid}`);
} catch (err) {
  console.error(err)
}

createQueue

() => Queue

source | tests

Creates a queue function that accepts a function as it's only parameter. When queue is invoked, the passed in function is executed after the last function passed to queue has finished executing. The queue function returns the result of the passed in function asynchronously.

Reading queue.done is true if no functions are currently executing / scheduled and otherwise a promise that resolves once the last function has stopped executing and no futher functions are queued.

Import

/* Node: */  import createQueue from "froebel/queue";
/* Deno: */  import createQueue from "https://deno.land/x/[email protected]/queue.ts";

Example

const queue = createQueue()

queue(async () => {
  console.log('start a')
  await delay()
  return 'end a'
}).then(console.log)

queue(async () => {
  console.log('start b')
  await delay()
  return 'end b'
}).then(console.log)

queue(async () => {
  console.log('start c')
  await delay()
  return 'end c'
}).then(console.log)

await queue.done

// start a
// end a
// start b
// end b
// start c
// end c

isPromise

(value: unknown) => value is Promise<T>

source | tests

Checks if value looks like a promise.

Import

/* Node: */  import isPromise from "froebel/isPromise";
/* Deno: */  import isPromise from "https://deno.land/x/[email protected]/isPromise.ts";

isNotPromise

(value: T) => value is Exclude<T, Promise<any>>

source | tests

Checks if value is not a promise.

Import

/* Node: */  import { isNotPromise } from "froebel/isPromise";
/* Deno: */  import { isNotPromise } from "https://deno.land/x/[email protected]/isPromise.ts";

Example

(value: number | Promise<unknown>) => {
  if (isNotPromise(value)) return value / 2
}

Predicate

truthy

(value: T) => value is PickTruthy<T>

source | tests

Checks if value is truthy. Literal types are narrowed accordingly.

Import

/* Node: */  import { truthy } from "froebel/truthy";
/* Deno: */  import { truthy } from "https://deno.land/x/[email protected]/truthy.ts";

falsy

(value: T) => value is PickFalsy<T>

source | tests

Checks if value is falsy. Literal types are narrowed accordingly.

Import

/* Node: */  import { falsy } from "froebel/truthy";
/* Deno: */  import { falsy } from "https://deno.land/x/[email protected]/truthy.ts";

nullish

(value: T) => value is Nullish<T>

source | tests

Checks if value is nullish. Literal types are narrowed accordingly.

Import

/* Node: */  import { nullish } from "froebel/nullish";
/* Deno: */  import { nullish } from "https://deno.land/x/[email protected]/nullish.ts";

notNullish

(value: null | T) => value is T

source | tests

Checks if value is not nullish. Literal types are narrowed accordingly.

Import

/* Node: */  import { notNullish } from "froebel/nullish";
/* Deno: */  import { notNullish } from "https://deno.land/x/[email protected]/nullish.ts";

Example

const nums = (...values: (number | undefined)[]): number[] => values.filter(notNullish)

isFulfilled

(result: PromiseSettledResult<T>) => result is PromiseFulfilledResult<T>

source | tests

Checks if result (returned from Promise.allSettled) is fulfilled.

Import

/* Node: */  import { isFulfilled } from "froebel/settled";
/* Deno: */  import { isFulfilled } from "https://deno.land/x/[email protected]/settled.ts";

isRejected

(result: PromiseSettledResult<unknown>) => result is PromiseRejectedResult

source | tests

Checks if result (returned from Promise.allSettled) is rejected.

Import

/* Node: */  import { isRejected } from "froebel/settled";
/* Deno: */  import { isRejected } from "https://deno.land/x/[email protected]/settled.ts";

String

prefix

(prefix: T0, str: T1, caseMod?: C) => `${string}`

source | tests

Returns str prefixed with prefix. Optionally, allows prefxing in camel case, i.e. prefix('foo', 'bar', 'camel') => 'fooBar', or snake case, i.e. prefix('foo', 'bar', 'snake') => 'foo_bar'.

The result is strictly typed, so prefix('foo', 'bar') will return the type 'foobar', not just a generic string.

Import

/* Node: */  import prefix from "froebel/prefix";
/* Deno: */  import prefix from "https://deno.land/x/[email protected]/prefix.ts";

suffix

(str: T1, suffix: T0, caseMod?: C) => `${string}`

source | tests

Returns str suffixed with suffix. Same case and type behavior as prefix.

Import

/* Node: */  import suffix from "froebel/suffix";
/* Deno: */  import suffix from "https://deno.land/x/[email protected]/suffix.ts";

surround

(str: A, surrounding: B) => B extends "" ? A : Surround<A, B>

source | tests

Surrounds the str with surrounding. surrounding must have an even length.

Import

/* Node: */  import { surround } from "froebel/surround";
/* Deno: */  import { surround } from "https://deno.land/x/[email protected]/surround.ts";

Example

surround("foo", "()")      // "(foo)"
surround("foo", "({[]})")  // "({[foo]})"

capitalize

(str: T) => Capitalize

source | tests

Upper-case first letter of string.

Import

/* Node: */  import { capitalize } from "froebel/case";
/* Deno: */  import { capitalize } from "https://deno.land/x/[email protected]/case.ts";

uncapitalize

(str: T) => Uncapitalize

source | tests

Lower-case first letter of string

Import

/* Node: */  import { uncapitalize } from "froebel/case";
/* Deno: */  import { uncapitalize } from "https://deno.land/x/[email protected]/case.ts";

upper

(str: T) => Uppercase

source | tests

Strictly typed String.toUpperCase().

Import

/* Node: */  import { upper } from "froebel/case";
/* Deno: */  import { upper } from "https://deno.land/x/[email protected]/case.ts";

lower

(str: T) => Lowercase

source | tests

Strictly typed String.toLowerCase().

Import

/* Node: */  import { lower } from "froebel/case";
/* Deno: */  import { lower } from "https://deno.land/x/[email protected]/case.ts";

snake

(str: T) => DelimitedCase<T, "_">

source | tests

Transforms a variable name to snake case.

Note: The rules for transforming anything to snake case are somewhat vague. So use this only for very simple names where the resulting value is absolutely unambiguous. For more examples of how names are transformed, have a look at the test cases.

Import

/* Node: */  import { snake } from "froebel/case";
/* Deno: */  import { snake } from "https://deno.land/x/[email protected]/case.ts";

Example

snake('fooBar') // 'foo_bar'

kebab

(str: T) => DelimitedCase<T, "-">

source | tests

Transforms a variable name to kebab case.

Note: The rules for transforming anything to kebab case are somewhat vague. So use this only for very simple names where the resulting value is absolutely unambiguous. For more examples of how names are transformed, have a look at the test cases.

Import

/* Node: */  import { kebab } from "froebel/case";
/* Deno: */  import { kebab } from "https://deno.land/x/[email protected]/case.ts";

Example

kebab('fooBar') // 'foo-bar'

camel

(str: T) => CamelCase<T>

source | tests

Transforms a variable name to camel case.

Note: The rules for transforming anything to camel case are somewhat vague. So use this only for very simple names where the resulting value is absolutely unambiguous. For more examples of how names are transformed, have a look at the test cases.

Import

/* Node: */  import { camel } from "froebel/case";
/* Deno: */  import { camel } from "https://deno.land/x/[email protected]/case.ts";

Example

camel('foo_bar') // 'fooBar'

pascal

(str: T) => Capitalize

source | tests

Transforms a variable name to pascal case.

Note: The rules for transforming anything to pascal case are somewhat vague. So use this only for very simple names where the resulting value is absolutely unambiguous. For more examples of how names are transformed, have a look at the test cases.

Import

/* Node: */  import { pascal } from "froebel/case";
/* Deno: */  import { pascal } from "https://deno.land/x/[email protected]/case.ts";

Example

pascal('foo_bar') // 'FooBar'

screamingSnake

(str: T) => Uppercase

source | tests

Transforms a variable name to screaming snake case.

see snake

Import

/* Node: */  import { screamingSnake } from "froebel/case";
/* Deno: */  import { screamingSnake } from "https://deno.land/x/[email protected]/case.ts";

Example

screamingSnake('fooBar') // 'FOO_BAR'

transformCase

(str: T, targetCase: C) => DelimitedCase<T, "_">

source | tests

Transform a variable name to targetCase

see snake, kebab, camel, pascal, and screamingSnake

Import

/* Node: */  import { transformCase } from "froebel/case";
/* Deno: */  import { transformCase } from "https://deno.land/x/[email protected]/case.ts";

Math

clamp

(min: number, num: number, max: number) => number

source | tests

Clamp num between min and max inclusively.

Import

/* Node: */  import clamp from "froebel/clamp";
/* Deno: */  import clamp from "https://deno.land/x/[email protected]/clamp.ts";

Data Structures

BiMap

class BiMap<L, R>(data?: Map<L, R> | [L, R][], aliasLeft?: AL, aliasRight?: AR)

source | tests

Bidirectional map. Maps two sets of keys in a one-to-one relation.

Both sides are accessible (at .left & .right, or at their respective alias if one was provided in the constructor) with an interface similar to that of the built-in Map and the same iteration behavior.

Import

/* Node: */  import BiMap from "froebel/bimap";
/* Deno: */  import BiMap from "https://deno.land/x/[email protected]/bimap.ts";

Examples

const nums = BiMap.from({ one: 1, two: 2 })

// different ways of iterating over the entries
[...nums.left]                 // [['one',1], ['two',2]]
[...nums.right]                // [[1,'one'], [2,'two']]
[...nums.left.keys()]          // ['one', 'two']
[...nums.left.values()]        // [1, 2]
[...nums.right.keys()]         // [1, 2]
[...nums.right.values()]       // ['one', 'two']
[...nums]                      // [['one',1], ['two',2]]
[...nums.right.entries()]      // [[1,'one'], [2,'two']]
Object.fromEntries(nums.right) // { '1': 'one', '2': 'two' }

// setting a value
nums.left.three = 3
// when accessing a property using bracket notation (i.e. nums.right[4]),
// JavaScript coerces the key to a string, so keys that aren't strings or
// symbols must be accessed using the same access methods known from Map.
nums.right.set(4, 'four')

// remapping values
nums.left.tres = 3          // {one: 1, two: 2, tres: 3, four: 4}
nums.right.set(4, 'cuatro') // {one: 1, two: 2, tres: 3, cuatro: 4}

// deleting
delete nums.left.tres    // {one: 1, two: 2, cuatro: 4}
nums.right.delete(4)     // {one: 1, two: 2}

// reversing the map
const num2Name = nums.reverse()
console.log([...num2Name.left])                 // [[1,'one'], [2,'two']]
console.log(Object.fromEntries(num2Name.right)) // {one: 1, two: 2}

// other methods known from built-in Map
nums.size               // 2
nums.[left|right].size  // 2
nums.clear() // equivalent to nums.[left|right].clear()
console.log(nums.size)  // 0
// giving aliases to both sides
const dictionary = new BiMap(
  [
    ['hello', 'hallo'],
    ['bye', 'tschüss'],
  ],
  'en',
  'de'
)

dictionary.de.get('hallo') // 'hello'
dictionary.en.get('bye')   // 'tschüss'

delete dictionary.de.hallo
console.log(Object.fromEntries(dictionary.en)) // { bye: 'tschüss' }

// you can also use the BiMap.alias method:
BiMap.alias('en', 'de')<string, string>()
BiMap.alias('en', 'de')([['hello', 'hallo']])
BiMap.alias('en', 'de')(new Map<string, string>())
BiMap.alias('en', 'de')({ hello: 'hallo' })
BiMap.alias('en', 'de')(new Set(['hello']), new Set(['hallo']))

// the same arguments can be used with BiMap.from, e.g.:
BiMap.from(new Set<number>(), new Set<number>())

SortedArray

class SortedArray<T>(compare: Cmp<T>, ...value: T[])

source | tests

Sorted array. Behaves much like a regular array but its elements remain sorted using the compare function supplied in the constructor.

Contains most of the methods defined on regular JavaScript arrays as long as they don't modify the array's content in place.

New elements are added using the add(...values) method.

Elements can still be accessed using bracket notation as in plain JavaScript arrays but can't be assigned to using bracket notation (as that could change the element's sort position).

Elements can be removed using the delete(...indices) method, which returns an array containing the deleted values. Deleting an element using delete sorted[index] will also work, but results in a TypeScript error because element access is marked readonly.

Array methods that pass a reference of the array to a callback (e.g. map, reduce, find) will pass a reference to the SortedArray instance instead.

The filter and slice methods will return SortedArray instances instead of plain arrays.

Import

/* Node: */  import SortedArray from "froebel/sortedArray";
/* Deno: */  import SortedArray from "https://deno.land/x/[email protected]/sortedArray.ts";

SortedMap

class SortedMap<K, V>(compare: Cmp<K, V>, entries?: null | [K, V][])

source | tests

Behaves like a regular JavaScript Map, but its iteration order is dependant on the compare function supplied in the constructor.

Note: The item's sort position is only computed automatically on insertion. If you update one of the values that the compare function depends on, you must call the update(key) method afterwards to ensure the map stays sorted.

Import

/* Node: */  import SortedMap from "froebel/sortedMap";
/* Deno: */  import SortedMap from "https://deno.land/x/[email protected]/sortedMap.ts";

froebel's People

Contributors

dependabot[bot] avatar honkinggoose avatar mathisbullinger avatar mgred 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

froebel's Issues

React Native support

throttle breaks the default React Native build (0.70.2).

performance isn't available in React Native, and neither is perf_hooks.

I have not yet investigated the conventional alternative.

Fails to resolve types with "moduleResolution": "bundler" in tsconfig

Could not find a declaration file for module 'froebel/forward'. '/Users/daniel/testproject/node_modules/froebel/forward.mjs' implicitly has an 'any' type.
  There are types at '/Users/daniel/testproject/node_modules/froebel/forward.d.ts', but this result could not be resolved when respecting package.json "exports". The 'froebel' library may need to update its package.json or typings.

Adding a types property to every export in package.json solves it:

{
  "name": "froebel",
  "version": "0.23.2",
  "description": "TypeScript utility library",
  "main": "index.js",
  "types": "index.d.ts",
  "exports": {
    ".": {
      "require": "./index.js",
      "import": "./index.mjs",
      "types": "./index.d.ts"
    },
    "./atWrap": {
      "require": "./atWrap.js",
      "import": "./atWrap.mjs",
      "types": "./atWrap.d.ts"
    },
    "./batch": {
      "require": "./batch.js",
      "import": "./batch.mjs",
      "types": "./batch.d.ts"
    },
    "./bimap": {
      "require": "./bimap.js",
      "import": "./bimap.mjs",
      "types": "./bimap.d.ts"
    },
    "./bundle": {
      "require": "./bundle.js",
      "import": "./bundle.mjs",
      "types": "./bundle.d.ts"
    },
    "./callAll": {
      "require": "./callAll.js",
      "import": "./callAll.mjs",
      "types": "./callAll.d.ts"
    },
    "./case": {
      "require": "./case.js",
      "import": "./case.mjs",
      "types": "./case.d.ts"
    },
    "./clamp": {
      "require": "./clamp.js",
      "import": "./clamp.mjs",
      "types": "./clamp.d.ts"
    },
    "./clone": {
      "require": "./clone.js",
      "import": "./clone.mjs",
      "types": "./clone.d.ts"
    },
    "./debounce": {
      "require": "./debounce.js",
      "import": "./debounce.mjs",
      "types": "./debounce.d.ts"
    },
    "./ds": {
      "require": "./ds.js",
      "import": "./ds.mjs",
      "types": "./ds.d.ts"
    },
    "./equal": {
      "require": "./equal.js",
      "import": "./equal.mjs",
      "types": "./equal.d.ts"
    },
    "./equality": {
      "require": "./equality.js",
      "import": "./equality.mjs",
      "types": "./equality.d.ts"
    },
    "./error": {
      "require": "./error.js",
      "import": "./error.mjs",
      "types": "./error.d.ts"
    },
    "./except": {
      "require": "./except.js",
      "import": "./except.mjs",
      "types": "./except.d.ts"
    },
    "./forward": {
      "require": "./forward.js",
      "import": "./forward.mjs",
      "types": "./forward.d.ts"
    },
    "./function": {
      "require": "./function.js",
      "import": "./function.mjs",
      "types": "./function.d.ts"
    },
    "./ident": {
      "require": "./ident.js",
      "import": "./ident.mjs",
      "types": "./ident.d.ts"
    },
    "./index": {
      "require": "./index.js",
      "import": "./index.mjs",
      "types": "./index.d.ts"
    },
    "./invoke": {
      "require": "./invoke.js",
      "import": "./invoke.mjs",
      "types": "./invoke.d.ts"
    },
    "./isPromise": {
      "require": "./isPromise.js",
      "import": "./isPromise.mjs",
      "types": "./isPromise.d.ts"
    },
    "./iterable": {
      "require": "./iterable.js",
      "import": "./iterable.mjs",
      "types": "./iterable.d.ts"
    },
    "./list": {
      "require": "./list.js",
      "import": "./list.mjs",
      "types": "./list.d.ts"
    },
    "./map": {
      "require": "./map.js",
      "import": "./map.mjs",
      "types": "./map.d.ts"
    },
    "./math": {
      "require": "./math.js",
      "import": "./math.mjs",
      "types": "./math.d.ts"
    },
    "./memoize": {
      "require": "./memoize.js",
      "import": "./memoize.mjs",
      "types": "./memoize.d.ts"
    },
    "./merge": {
      "require": "./merge.js",
      "import": "./merge.mjs",
      "types": "./merge.d.ts"
    },
    "./noop": {
      "require": "./noop.js",
      "import": "./noop.mjs",
      "types": "./noop.d.ts"
    },
    "./nullish": {
      "require": "./nullish.js",
      "import": "./nullish.mjs",
      "types": "./nullish.d.ts"
    },
    "./nullishChain": {
      "require": "./nullishChain.js",
      "import": "./nullishChain.mjs",
      "types": "./nullishChain.d.ts"
    },
    "./object": {
      "require": "./object.js",
      "import": "./object.mjs",
      "types": "./object.d.ts"
    },
    "./omit": {
      "require": "./omit.js",
      "import": "./omit.mjs",
      "types": "./omit.d.ts"
    },
    "./oneOf": {
      "require": "./oneOf.js",
      "import": "./oneOf.mjs",
      "types": "./oneOf.d.ts"
    },
    "./partial": {
      "require": "./partial.js",
      "import": "./partial.mjs",
      "types": "./partial.d.ts"
    },
    "./partition": {
      "require": "./partition.js",
      "import": "./partition.mjs",
      "types": "./partition.d.ts"
    },
    "./path": {
      "require": "./path.js",
      "import": "./path.mjs",
      "types": "./path.d.ts"
    },
    "./pick": {
      "require": "./pick.js",
      "import": "./pick.mjs",
      "types": "./pick.d.ts"
    },
    "./pipe": {
      "require": "./pipe.js",
      "import": "./pipe.mjs",
      "types": "./pipe.d.ts"
    },
    "./predicate": {
      "require": "./predicate.js",
      "import": "./predicate.mjs",
      "types": "./predicate.d.ts"
    },
    "./prefix": {
      "require": "./prefix.js",
      "import": "./prefix.mjs",
      "types": "./prefix.d.ts"
    },
    "./promise": {
      "require": "./promise.js",
      "import": "./promise.mjs",
      "types": "./promise.d.ts"
    },
    "./promisify": {
      "require": "./promisify.js",
      "import": "./promisify.mjs",
      "types": "./promisify.d.ts"
    },
    "./queue": {
      "require": "./queue.js",
      "import": "./queue.mjs",
      "types": "./queue.d.ts"
    },
    "./range": {
      "require": "./range.js",
      "import": "./range.mjs",
      "types": "./range.d.ts"
    },
    "./repeat": {
      "require": "./repeat.js",
      "import": "./repeat.mjs",
      "types": "./repeat.d.ts"
    },
    "./select": {
      "require": "./select.js",
      "import": "./select.mjs",
      "types": "./select.d.ts"
    },
    "./settled": {
      "require": "./settled.js",
      "import": "./settled.mjs",
      "types": "./settled.d.ts"
    },
    "./shuffle": {
      "require": "./shuffle.js",
      "import": "./shuffle.mjs",
      "types": "./shuffle.d.ts"
    },
    "./sortedArray": {
      "require": "./sortedArray.js",
      "import": "./sortedArray.mjs",
      "types": "./sortedArray.d.ts"
    },
    "./sortedMap": {
      "require": "./sortedMap.js",
      "import": "./sortedMap.mjs",
      "types": "./sortedMap.d.ts"
    },
    "./string": {
      "require": "./string.js",
      "import": "./string.mjs",
      "types": "./string.d.ts"
    },
    "./suffix": {
      "require": "./suffix.js",
      "import": "./suffix.mjs",
      "types": "./suffix.d.ts"
    },
    "./surround": {
      "require": "./surround.js",
      "import": "./surround.mjs",
      "types": "./surround.d.ts"
    },
    "./take": {
      "require": "./take.js",
      "import": "./take.mjs",
      "types": "./take.d.ts"
    },
    "./throttle": {
      "require": "./throttle.js",
      "import": "./throttle.mjs",
      "types": "./throttle.d.ts"
    },
    "./truthy": {
      "require": "./truthy.js",
      "import": "./truthy.mjs",
      "types": "./truthy.d.ts"
    },
    "./types": {
      "require": "./types.js",
      "import": "./types.mjs",
      "types": "./types.d.ts"
    },
    "./unary": {
      "require": "./unary.js",
      "import": "./unary.mjs",
      "types": "./unary.d.ts"
    },
    "./unzip": {
      "require": "./unzip.js",
      "import": "./unzip.mjs",
      "types": "./unzip.d.ts"
    },
    "./zip": {
      "require": "./zip.js",
      "import": "./zip.mjs",
      "types": "./zip.d.ts"
    }
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/MathisBullinger/froebel.git"
  },
  "author": "Mathis Bullinger",
  "license": "ISC",
  "bugs": {
    "url": "https://github.com/MathisBullinger/froebel/issues"
  },
  "homepage": "https://github.com/MathisBullinger/froebel#readme",
  "devDependencies": {
    "@babel/cli": "^7.18.10",
    "@babel/core": "^7.18.10",
    "@babel/plugin-transform-typescript": "^7.18.12",
    "@babel/preset-env": "^7.18.10",
    "@types/node": "^17.0.35",
    "typedoc": "^0.22.17",
    "typescript": "^4.7.3"
  }
}

Proposal: `mergeDeep`

Basically I'm not sure about lodash's _.merge.

The docs are extremely "wordy" around enumerable properties, giving me pause about the implementation, and the type defs I’ve seen in lodash (so far) seem overly complicated.

Related: sindresorhus/ts-extras#55

Clone (structuredClone) doesn't work with proxies

A lot of frameworks use Proxies for their reactivity, Vue included, but that makes it impossible to use structuredClone.

Vue team explicitely suggests to use clone functions, but this library clone function uses structured clone if available, which breaks thing when proxies are involved, unless of course you use browser which doesn't support structuredClone, then things work correctly.

Utility Request: Unary

It would be great to have a unary utility. Like this in plain JS but not sure how to implement it in TS:

const unary = (f) => {
  return f.length === 1 ? f : (args) => f(args)
}

That way we could do things like:

['1', '2', '3'].map(unary(parseInt))

Example this discussion/explanation about why the unary utility is useful.

My apologies if this already exists in here under a different name. This is a fantastic library! 👏

Future Request: castArray

It would be nice to have a castArray utility.

something like this

function castArray<T>(x: T | T[]): T[] {
  return x === undefined ? [] : Array.isArray(x) ? x : [x];
}

Does the `pipe` type need to be recursive?

I saw your post about type-checking a pipe function here and posted a reply:

Does it need to be recursive?

Here's the approach I came up with, before giving up:

playground

My approach was to just take one item off the start of the list, and one item off the end of the list - derive the input arguments and output return types from the two lists, and then see if they're the same.

If we can compute a set of InputTypes and OutputTypes and just check if those match, why do we need the whole recursive thing?

For better error reporting maybe? I guess with my approach, the entire array will be faulted - whereas with your approach, the error will point to the individual argument at fault?

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.