froebel v0.23.2
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 froebeland — 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/froebel@v0.23.2/mod.ts";
// or import just the utility you need:
import memoize from "https://deno.land/x/froebel@v0.23.2/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
functionlistiterableobjectpathequalitypromisepredicatestringmathdata structures
Function
ident
(value: T) => TIdentity function.
Import
/* Node: */ import ident from "froebel/ident";
/* Deno: */ import ident from "https://deno.land/x/froebel@v0.23.2/ident.ts";noop
() => voidImport
/* Node: */ import noop from "froebel/noop";
/* Deno: */ import noop from "https://deno.land/x/froebel@v0.23.2/noop.ts";partial
(fun: T, ...argsLeft: PL) => (...argsRight: PR) => ReturnType<T>Partially apply a function.
Import
/* Node: */ import partial from "froebel/partial";
/* Deno: */ import partial from "https://deno.land/x/froebel@v0.23.2/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>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/froebel@v0.23.2/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>Turns
funinto a unary function (a function that only accepts one argument).Note:
funmust 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/froebel@v0.23.2/unary.ts";Example
['1', '2', '3'].map(unary(parseInt)) // -> [1, 2, 3]callAll
(funs: F[], ...args: P) => ReturnTypes<F>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/froebel@v0.23.2/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>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/froebel@v0.23.2/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>Like
pipebut 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/froebel@v0.23.2/pipe.ts";Example
applyPipe(2, double, square, half) // -> 8bundle
(...funs: λ<T>[]) => (...args: T) => Promise<void>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/froebel@v0.23.2/bundle.ts";bundleSync
(...funs: λ<T>[]) => (...args: T) => voidSame 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/froebel@v0.23.2/bundle.ts";nullishChain
(...funs: [] | [FF, ...FR[]]) => (...args: Parameters<FF>) => ReturnType<FF> | ReturnType<FR[number]>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/froebel@v0.23.2/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]>>>Same as nullishChain but accept asynchronous functions too.
Import
/* Node: */ import { asyncNullishChain } from "froebel/nullishChain";
/* Deno: */ import { asyncNullishChain } from "https://deno.land/x/froebel@v0.23.2/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}Create a throttled function that invokes
funat most everymsmilliseconds.
funis 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/froebel@v0.23.2/throttle.ts";debounce
(fun: T, ms: number) => λ<Parameters<T>, void> & {[cancel]: () => void}Creates a debounced function that delays invoking
fununtilmsmilliseconds have passed since the last invocation of the debounced function.
funis 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/froebel@v0.23.2/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>>}Returns a copy of
funthat remembers its result for any given arguments and only invokesfunfor unknown arguments.The cache key is computed using the
keyfunction. The defaultkeyfunction simply stringifies the arguments.If
limitis specified, only thelimit-last entries are kept in cache.The function's cache is available at
memoized.cache.If
opt.weakistrue, 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 alimit.
Import
/* Node: */ import memoize from "froebel/memoize";
/* Deno: */ import memoize from "https://deno.land/x/froebel@v0.23.2/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
// 3const logIfDifferent = memoize(
(msg: string) => console.log(msg),
{
limit: 1,
key: msg => msg
}
)
logIfDifferent('a')
logIfDifferent('a')
logIfDifferent('b')
logIfDifferent('a')
// a
// b
// alimitInvocations
(fun: T, limit: number, ...funs: ExcS<T>) => TReturns a version of the function
funthat can only be invokedlimittimes. An optionalexceptfunction will be called with the same parameters on any additional invocations.If
funreturns anything butvoid(orPromise<void>), supplying anexceptfunction is mandatory.The
exceptfunction must have the same return type asfun, or — iffunreturns a promise — it may return the type that the promise resolves to synchronously.The
exceptfunction may also throw instead of returning a value.
Import
/* Node: */ import { limitInvocations } from "froebel/invoke";
/* Deno: */ import { limitInvocations } from "https://deno.land/x/froebel@v0.23.2/invoke.ts";once
(fun: T, ...funs: ExcS<T>) => TSpecial case of limitInvocations.
funcan only be invoked once.see limitInvocations
Import
/* Node: */ import { once } from "froebel/invoke";
/* Deno: */ import { once } from "https://deno.land/x/froebel@v0.23.2/invoke.ts";List
atWrap
(arr: T[], i: number) => TAccess 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/froebel@v0.23.2/atWrap.ts";zip
(...lists: T) => Zip<T>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/froebel@v0.23.2/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[]Same as zip but also takes a
zipperfunction, that is called for each index with the element at current index in each list as arguments. The result ofzipperis the element at current index in the list returned fromzipWith.
Import
/* Node: */ import { zipWith } from "froebel/zip";
/* Deno: */ import { zipWith } from "https://deno.land/x/froebel@v0.23.2/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>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/froebel@v0.23.2/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]>}Same as unzip but accepts an
unzipperfunction for each tuple index. Theunzipper's return value is used as the value in the list at that index returned fromunzipWith.The
unzippertakes the current element as its first argument, an accumulator as second argument (initiallyundefined) 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/froebel@v0.23.2/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[][]Takes a
listand returns it in multiple smaller lists of the sizebatchSize. The last batch may be smaller thanbatchSizedepending on iflistsize is divisible bybatchSize.
Import
/* Node: */ import batch from "froebel/batch";
/* Deno: */ import batch from "https://deno.land/x/froebel@v0.23.2/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>[]]Takes a
listand returns a pair of lists containing: the elements that match thepredicateand 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/froebel@v0.23.2/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[]Shuffles
listusing the Fisher-Yates shuffle algorithm. The originallistis not modified and the shuffled list is returned.
Import
/* Node: */ import shuffle from "froebel/shuffle";
/* Deno: */ import shuffle from "https://deno.land/x/froebel@v0.23.2/shuffle.ts";shuffleInPlace
(list: unknown[]) => voidSame as shuffle but shuffles
listin place.
Import
/* Node: */ import { shuffleInPlace } from "froebel/shuffle";
/* Deno: */ import { shuffleInPlace } from "https://deno.land/x/froebel@v0.23.2/shuffle.ts";take
(n: number, list: Iterable<T>) => T[]Takes
nelements from the iterablelistand returns them as an array.
Import
/* Node: */ import { take } from "froebel/list";
/* Deno: */ import { take } from "https://deno.land/x/froebel@v0.23.2/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
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/froebel@v0.23.2/range.ts";numberRange
(start: number, end: number, step: number) => number[]Constructs a numeric between
startandendinclusively.
Import
/* Node: */ import { numberRange } from "froebel/range";
/* Deno: */ import { numberRange } from "https://deno.land/x/froebel@v0.23.2/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[]Constructs a range between characters.
Import
/* Node: */ import { alphaRange } from "froebel/range";
/* Deno: */ import { alphaRange } from "https://deno.land/x/froebel@v0.23.2/range.ts";Example
range('a', 'd') // -> ['a', 'b', 'c', 'd']
range('Z', 'W') // -> ['Z', 'Y', 'X', 'W']Iterable
repeat
(...sequence: [T, ...T[]]) => Generator<T>Returns a generator that repeats
sequence.
Import
/* Node: */ import repeat from "froebel/repeat";
/* Deno: */ import repeat from "https://deno.land/x/froebel@v0.23.2/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>Takes
nelements from the iterablelistand returns them as a generator.
Import
/* Node: */ import { take } from "froebel/iterable";
/* Deno: */ import { take } from "https://deno.land/x/froebel@v0.23.2/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>From
obj, create a new object that only includeskeys.
Import
/* Node: */ import pick from "froebel/pick";
/* Deno: */ import pick from "https://deno.land/x/froebel@v0.23.2/pick.ts";Example
pick({ a: 1, b: 2, c: 3 }, 'a', 'c') // { a: 1, c: 3 }omit
(obj: T, ...keys: K[]) => Omit<T, K>From
obj, create a new object that does not includekeys.
Import
/* Node: */ import omit from "froebel/omit";
/* Deno: */ import omit from "https://deno.land/x/froebel@v0.23.2/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>Map over
data.datacan be a regular object, aMap, aSet, or an array.
Import
/* Node: */ import map from "froebel/map";
/* Deno: */ import map from "https://deno.land/x/froebel@v0.23.2/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>Returns the value in
objatpath. If the given path does not exist, the symbolnoneis returned.
Import
/* Node: */ import select from "froebel/select";
/* Deno: */ import select from "https://deno.land/x/froebel@v0.23.2/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]Checks if
vis one ofcmps.
Import
/* Node: */ import oneOf from "froebel/oneOf";
/* Deno: */ import oneOf from "https://deno.land/x/froebel@v0.23.2/oneOf.ts";equal
(a: unknown, b: unknown) => booleanChecks if
aandbare 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/froebel@v0.23.2/equal.ts";clone
(value: T) => TReturns a copied version of
value.If
valueis primitive, returnsvalue. Otherwise, properties ofvalueare copied recursively. Onlyvalue's own enumerable properties are cloned. Arrays are cloned by mapping over their elements.If a path in
valuereferences 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/froebel@v0.23.2/clone.ts";merge
(a: A, b: B) => Merge<A, B>Recursively merges
AandB. If a property inAandBis of a different type (i.e. it's not an array, Set, Map, or plain object in both, the value fromBwill 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/froebel@v0.23.2/merge.ts";Promise
promisify
(withCallback: T, resultIndex?: N, errorIndex: null | number) => Promisified<T, N>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
nulltoresultIndexorerrorIndexif 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
callbackFirstproperty allows passing additional parameters after the callback andcallbackLastwill pass additional parameters before the callback.
Import
/* Node: */ import promisify from "froebel/promisify";
/* Deno: */ import promisify from "https://deno.land/x/froebel@v0.23.2/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
() => QueueCreates a
queuefunction that accepts a function as it's only parameter. Whenqueueis invoked, the passed in function is executed after the last function passed toqueuehas finished executing. Thequeuefunction returns the result of the passed in function asynchronously.Reading
queue.doneistrueif 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/froebel@v0.23.2/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 cisPromise
(value: unknown) => value is Promise<T>Checks if
valuelooks like a promise.
Import
/* Node: */ import isPromise from "froebel/isPromise";
/* Deno: */ import isPromise from "https://deno.land/x/froebel@v0.23.2/isPromise.ts";isNotPromise
(value: T) => value is Exclude<T, Promise<any>>Checks if
valueis not a promise.
Import
/* Node: */ import { isNotPromise } from "froebel/isPromise";
/* Deno: */ import { isNotPromise } from "https://deno.land/x/froebel@v0.23.2/isPromise.ts";Example
(value: number | Promise<unknown>) => {
if (isNotPromise(value)) return value / 2
}Predicate
truthy
(value: T) => value is PickTruthy<T>Checks if
valueis truthy. Literal types are narrowed accordingly.
Import
/* Node: */ import { truthy } from "froebel/truthy";
/* Deno: */ import { truthy } from "https://deno.land/x/froebel@v0.23.2/truthy.ts";falsy
(value: T) => value is PickFalsy<T>Checks if
valueis falsy. Literal types are narrowed accordingly.
Import
/* Node: */ import { falsy } from "froebel/truthy";
/* Deno: */ import { falsy } from "https://deno.land/x/froebel@v0.23.2/truthy.ts";nullish
(value: T) => value is Nullish<T>Checks if
valueis nullish. Literal types are narrowed accordingly.
Import
/* Node: */ import { nullish } from "froebel/nullish";
/* Deno: */ import { nullish } from "https://deno.land/x/froebel@v0.23.2/nullish.ts";notNullish
(value: null | T) => value is TChecks if
valueis not nullish. Literal types are narrowed accordingly.
Import
/* Node: */ import { notNullish } from "froebel/nullish";
/* Deno: */ import { notNullish } from "https://deno.land/x/froebel@v0.23.2/nullish.ts";Example
const nums = (...values: (number | undefined)[]): number[] => values.filter(notNullish)isFulfilled
(result: PromiseSettledResult<T>) => result is PromiseFulfilledResult<T>Checks if
result(returned fromPromise.allSettled) is fulfilled.
Import
/* Node: */ import { isFulfilled } from "froebel/settled";
/* Deno: */ import { isFulfilled } from "https://deno.land/x/froebel@v0.23.2/settled.ts";isRejected
(result: PromiseSettledResult<unknown>) => result is PromiseRejectedResultChecks if
result(returned fromPromise.allSettled) is rejected.
Import
/* Node: */ import { isRejected } from "froebel/settled";
/* Deno: */ import { isRejected } from "https://deno.land/x/froebel@v0.23.2/settled.ts";String
prefix
(prefix: T0, str: T1, caseMod?: C) => `${string}`Returns
strprefixed withprefix. 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 genericstring.
Import
/* Node: */ import prefix from "froebel/prefix";
/* Deno: */ import prefix from "https://deno.land/x/froebel@v0.23.2/prefix.ts";suffix
(str: T1, suffix: T0, caseMod?: C) => `${string}`Returns
strsuffixed withsuffix. Same case and type behavior as prefix.
Import
/* Node: */ import suffix from "froebel/suffix";
/* Deno: */ import suffix from "https://deno.land/x/froebel@v0.23.2/suffix.ts";surround
(str: A, surrounding: B) => B extends "" ? A : Surround<A, B>Surrounds the
strwithsurrounding.surroundingmust have an even length.
Import
/* Node: */ import { surround } from "froebel/surround";
/* Deno: */ import { surround } from "https://deno.land/x/froebel@v0.23.2/surround.ts";Example
surround("foo", "()") // "(foo)"
surround("foo", "({[]})") // "({[foo]})"capitalize
(str: T) => CapitalizeUpper-case first letter of string.
Import
/* Node: */ import { capitalize } from "froebel/case";
/* Deno: */ import { capitalize } from "https://deno.land/x/froebel@v0.23.2/case.ts";uncapitalize
(str: T) => UncapitalizeLower-case first letter of string
Import
/* Node: */ import { uncapitalize } from "froebel/case";
/* Deno: */ import { uncapitalize } from "https://deno.land/x/froebel@v0.23.2/case.ts";upper
(str: T) => UppercaseStrictly typed
String.toUpperCase().
Import
/* Node: */ import { upper } from "froebel/case";
/* Deno: */ import { upper } from "https://deno.land/x/froebel@v0.23.2/case.ts";lower
(str: T) => LowercaseStrictly typed
String.toLowerCase().
Import
/* Node: */ import { lower } from "froebel/case";
/* Deno: */ import { lower } from "https://deno.land/x/froebel@v0.23.2/case.ts";snake
(str: T) => DelimitedCase<T, "_">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/froebel@v0.23.2/case.ts";Example
snake('fooBar') // 'foo_bar'kebab
(str: T) => DelimitedCase<T, "-">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/froebel@v0.23.2/case.ts";Example
kebab('fooBar') // 'foo-bar'camel
(str: T) => CamelCase<T>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/froebel@v0.23.2/case.ts";Example
camel('foo_bar') // 'fooBar'pascal
(str: T) => CapitalizeTransforms 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/froebel@v0.23.2/case.ts";Example
pascal('foo_bar') // 'FooBar'screamingSnake
(str: T) => UppercaseTransforms a variable name to screaming snake case.
see snake
Import
/* Node: */ import { screamingSnake } from "froebel/case";
/* Deno: */ import { screamingSnake } from "https://deno.land/x/froebel@v0.23.2/case.ts";Example
screamingSnake('fooBar') // 'FOO_BAR'transformCase
(str: T, targetCase: C) => DelimitedCase<T, "_">Transform a variable name to
targetCasesee snake, kebab, camel, pascal, and screamingSnake
Import
/* Node: */ import { transformCase } from "froebel/case";
/* Deno: */ import { transformCase } from "https://deno.land/x/froebel@v0.23.2/case.ts";Math
clamp
(min: number, num: number, max: number) => numberClamp
numbetweenminandmaxinclusively.
Import
/* Node: */ import clamp from "froebel/clamp";
/* Deno: */ import clamp from "https://deno.land/x/froebel@v0.23.2/clamp.ts";Data Structures
BiMap
class BiMap<L, R>(data?: Map<L, R> | [L, R][], aliasLeft?: AL, aliasRight?: AR)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/froebel@v0.23.2/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[])Sorted array. Behaves much like a regular array but its elements remain sorted using the
comparefunction 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 usingdelete 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
filterandslicemethods will return SortedArray instances instead of plain arrays.
Import
/* Node: */ import SortedArray from "froebel/sortedArray";
/* Deno: */ import SortedArray from "https://deno.land/x/froebel@v0.23.2/sortedArray.ts";SortedMap
class SortedMap<K, V>(compare: Cmp<K, V>, entries?: null | [K, V][])Behaves like a regular JavaScript
Map, but its iteration order is dependant on thecomparefunction 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
comparefunction depends on, you must call theupdate(key)method afterwards to ensure the map stays sorted.
Import
/* Node: */ import SortedMap from "froebel/sortedMap";
/* Deno: */ import SortedMap from "https://deno.land/x/froebel@v0.23.2/sortedMap.ts";