npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@trevthedev/toolbelt

v0.0.70

Published

miscellaneous functions and utilities

Downloads

64

Readme

Toolbelt

A set of useful utilities and tools.

Table Of Contents

Chain Overview

A fast, simple, typed way to chain together asynchronous functions - with the output of each function acting as the input to the subsequent function.

If an error is returned by a function in the chain, that effectively ends any further processing of the chain.

An awaited chain returns an AwaitedChainController - which can be used to communicate with the currently executing function, including for example implementing a cancel ability.

Example Basic Usage

import { chain } from '...'
import type { Resolver } from '....'

// function to generate dummy asynchronous functions
const addChar =
  <T extends string, C extends string>(c: C) =>
  (x: T, resolver: Resolver<(result: `${C}:${T}`) => void>) =>
    resolver(`${c}:${x}` as `${C}:${T}`)

// adds three asynchronous functions to the chain
const fooChain = chain(
  addChar<'start', 'A'>('A'),
  addChar<'A:start', 'B'>('B'),
  addChar<'B:A:start', 'C'>('C'),
)
// adds a further three asynchronous functions to the chain
const fooBarChain = fooChain(
  addChar<'C:B:A:start', 'A'>('A'),
  addChar<'A:C:B:A:start', 'B'>('B'),
  addChar<'B:A:C:B:A:start', 'C'>('C'),
)
// awaits chain of asynchronous functions
fooBarChain.await('start' as const, (result) => {
  expect(result).toEqual('C:B:A:C:B:A:start')
  done(undefined)
})

chain

chain( ...asyncFns: [AsyncFn, ...AsyncFn[]] ) => ChainNode

AsyncFn

Functions passed to the chain must have the following form:

;(
  input: any, // input provided to the async function
  resolver: Resolver, // a Resolver object is provided to the function - see below
) => any // ResultResolverController

Resolver

The Resolver is a function object passed to the AsyncFn and it's used to return either a result or an error:

{
  (result: any):any
  result: (result: any)=>any
  error: (error: any)=>any
}

A simple way to type a Resolver is using the provided type Resolver<ResultFn, ErrorFn> - note ErrorFn is optional e.g.:

import type { Resolver } from '....'
const asyncFn = (
  x: string,
  resolver: Resolver<(output: string) => void, (error: Error) => void>,
) => {
  resolver('...') // or
  resolver.result('...') // or
  resolver.error(new Error('...')) // or
}

ChainNode

Adding AsyncFns to the chain, creates a new ChainNode. ChainNodes can have other AsyncFns added, and can be awaited, or trap upstream errors.

{
  // function to add additional AsyncFns to the chain
  ( asyncFns: [AsyncFn, ...AsyncFn[]] ): ChainNode

  // Symbol('Chain Node')
  type: chainNodeType

  // captures all downstream errors and prevents error bubbling to
  // for example an errorCb in await.
  // all errors effectively halt the execution of the rest
  // of the chain.
  onError(callback: (finalError: any)=>any): ChainNode

  // awaits a chain
  await(
    input: any,
    resultCb: (finalResult: any)=>any,
    errorCb?: (finalError: any)=>any,
  ): AwaitedChainController

  // converts the chain into a AsyncFn, which can then
  // be spliced, or added into other chains.
  readonly asyncFn: (input:any, resolver: Resolver)=>any
}

AwaitedChainController

An object that contains the ResultResolverController of the current asynchronously executing AsyncFn. ResultResolverControllers are mostly not used and set to void. However they can be used to provide a mechanism to communicate with the currently executing AsyncFn - for example one could implement cancel or currentStatus functionality.

{
  controller: any
}

EnhancedChain Overview

EnhancedChain, is built on top of Chain and provides additional functionality including:

  1. can be converted into a promise, via the .input(startInput:any): Promise<any> method
  2. synchronous functions can be added to the chain via .sync(...syncFunctions: (input:any)=>any[]): EnhancedChainNode

And the following options can be specified:

  1. thrownErrorToErrorCb - any errors thrown are routed to the errorCb. default is true
  2. enforceSingleResolution - a resolver may be called once - any subsequent calls throw an error. default is true
  3. forceAsync - all ValidAsync calls can be wrapped in setImmediate | nextTick | queueMicrotask - the default is none
  4. resolveReturnedPromises any promises returned via a resolver are resolved, before being passed on. default is true - note typing for this not yet properly implement!
  5. callbacks - chain execution can be tracked via a range of callbacks/

Example Usage

import { enhancedChain } from '...'
import type { Resolver } from '...'

const eChainy = enhancedChain(
  {
    callbacks: { afterChainResolved: () => console.log('chain resolved') },
  },
  (x: 'start', resolve: Resolver<(arg: 'A') => void>) => {
    console.log(x) // 'start'
    resolve('A')
  },
).sync(
  (x: 'A') => 'B' as const,
  (x: 'B') => new Promise((resolve) => resolve('C')),
)

const eChainyMore = eChainy((x: 'C', resolve: Resolver<(arg: 'done') => void>) => {
  console.log(x) // 'C'
  resolve('done' as const)
})

eChainy.input('start').then((result) => {
  console.log(result) // 'C'
})

eChainyMore.await('start', (result) => {
  console.log(result) // 'done'
  done(undefined)
})

enhancedChain

enhancedChain( options: EnhanceChainOptions, ...asyncFns: AsyncFn[] ) => EnhancedChainNode

EnhanceChainOptions

{
    thrownErrorToErrorCb?: boolean;
    enforceSingleResolution?: boolean;
    forceAsync?: 'setImmediate' | 'nextTick' | 'queueMicrotask' | 'none';
    resolveReturnedPromises?: boolean;
    callbacks?: {
      beforeChainStart?: () => void
      beforeChainResult?: () => void
      afterChainResult?: () => void
      beforeChainError?: () => void
      afterChainError?: () => void

      beforeChainResolved?: () => void
      afterChainResolved?: () => void

      beforeNodeStart?: () => void
      beforeNodeResult?: () => void
      afterNodeResult?: () => void
      beforeNodeError?: () => void
      afterNodeError?: () => void
    };
}

EnhancedChainNode

expands ChainNode by addition of the following methods:

{
  // function to add additional synchronous functions to the chain
  sync( ...syncFns: [syncFn, ...syncFn[]] ): EnhancedChainNode

  // returns a Promise of the EnhancedChain
  input(inputArg: any): Promise(any)

  // Symbol('Enhanced Chain Node')
  type: enhancedChainNodeType

  ...see ChainNode
}

Compose Overview

Composes multiple functions, into a single function. Equivalent to (arg)=>fn3(fn2(fn1(arg)))

Example Usage

import { compose } from '...'
const fn = compose(
  (a: string) => `${a}:A`,
  (a: string) => `${a}:B`,
)
console.log(fn('start')) //`start:A:B`

ComposeWithError

Composes multiple function of the form (input)=>[error]|[undefined,result] into a single function, which stops executing if any function returns an error.

Example Usage

const fn = composeWithError(
  (a: 'start'): ResultError<'eA', 'A'> => [undefined, `A:${a}` as 'A'],
  (a: 'A'): ResultError<'eB', 'B'> => [undefined, `B:${a}` as 'B'],
)
const [error, result] = fn('start' as const)
expect(result).toEqual('B:A:start')
expect(error).toEqual(undefined)

Pipe

pipe(input,fn1,fn2) is equivalent to compose(fn1,fn2)(input)

Compositor Overview

An object to streamline function composition

Example Usage

import { compositor } from '...'

// chains functions together
const fn = compositor(
  (input: 'a') => `${input}:b` as 'a:b',
  (input: 'a:b') => `${input}:c` as 'a:b:c',
)
const fnFn = fn.call // makes a snapshot of chained functions
console.log(fnFn('a')) // 'a:b:c'

// chains are expandable
const fn2 = fn(
  (input: 'a:b:c') => `${input}:d` as 'a:b:c:d',
  (input: 'a:b:c:d') => `${input}:e` as 'a:b:c:d:e',
)
console.log(fn2.call('a')) // 'a:b:c:d:e'
console.log(fnFn('a')) // 'a:b:c'

// an empty compositor returns whatever it is called with
console.log(compositor().call('hello')) // hello

// effects are functions added to the composition, but
// which don't impact the composition calculation.
fn2.addEffects(
  () => console.log('hello'),
  () => console.log('world'),
)
// logs 'hello' 'world' and returns 'a:b:c:d:e'
console.log(fn2.call('a'))

// insertPlaceholder creates a placeholder for a function
// which can later be provided
// useful if the function changes between calls.
const setFn = fn2.insertPlaceholder(undefined as unknown as (arg: 'a:b:c:d:e') => 'a:b:c:d:e:f')
// logs 'hello' 'world' and returns 'a:b:c:d:e:f'
console.log(setFn((input: 'a:b:c:d:e') => `${input}:f` as 'a:b:c:d:e:f')('a'))

compositor

{
  // add functions to the compositor
  (...fns: [(input: any) => any, ...((input: any) => any)[]]): Compositor
  // creates and returns the composed function
  call(input:any): any
  /**
   * utility to add functions that take no input, and return no output
   * but to run them in sequence in the chain - useful for example to fire events,
   * or notifiers.
   */
  addEffects(...effects: [() => void, ...(() => void)[]]): Compositor
}

AsyncCoupler Overview

Enables the coupling of two async callbacks: incomingCallback and outgoingCallback - which methods can be renamed as require. The callbacks may be added in any sequence and are enqueued.

Once both callbacks have been added: outgoingCallback(incomingCallback) is called. Optionally, instead of FIFO, a manual index may be specified causing callbacks to be made in index order.

asyncCouplerWorkAround provides a DRY'er way to specify the typings.

Example Usage

// default asyncCoupler has `addOutgoingCallback` and `addIncomingCallback` methods
const coupler = asyncCoupler<(result: number) => void>()
coupler.addOutgoingCallback((incomingCb) => incomingCb(1))
coupler.addIncomingCallback(
  (result) => console.log(result), // 1
)

// renaming methods
const cCoupler = asyncCoupler<
  (input: number) => void,
  {
    outgoingCallbackName: 'addA'
    incomingCallbackName: 'addB'
  }
>({
  outgoingCallbackName: 'addA',
  incomingCallbackName: 'addB',
})
cCoupler.addA((incomingCb) => incomingCb(1))
cCoupler.addB((result) => {
  console.log(result) // 1
  done(undefined)
})

// a workaround to reduce typing
const cCouplerA = asyncCouplerWorkAround({
  outgoingCallbackName: 'addA',
  incomingCallbackName: 'addB',
} as const)<(input: number) => void>()
cCouplerA.addA((incomingCb) => incomingCb(1))
cCouplerA.addB((result) => {
  console.log(result) // 1
  done(undefined)
})

AsyncFnsInParallel

AsyncFnsInParallel executes asynchronous functions in parallel. Similar to, but simpler than Promise.alland more performant.

Asynchronous functions take the form:

;(input: any, resolver: AsyncFnResolver) => controller

Example Usage

import { asyncFnsInParallel } from '...'
import type { AsyncFnResolver } from '...'

const parallelFns = asyncFnsInParallel(
  (a: 1, resolver: AsyncFnResolver<(value: 1) => void>) =>
    setTimeout(() => resolver((a * 1) as 1), 100),
  (a: 1, resolver: AsyncFnResolver<(value: 2) => void>) =>
    setTimeout(() => resolver((a * 2) as 2), 100),
)
parallelFns.await(1, (results) => {
  console.log(results) // [1,2]
})

asyncFnsInParallel

asyncFnsInParallel( ...asyncFns: [AsyncFnAny, ...AsyncFnAny[]]) =>{
    await: (
      input: Input,
      resultCb: (resultArray: ResultsArray) => void,
      errorCb?: (errorArray: ErrorsArray, resultArray: ResultsArray) => void,
    )=>AsyncFnsInParallelController
    promise: (input: any)=>Promise<any>
}

AsyncFnsInParallelController

{
    state:  'awaited' | 'halted' | 'done' | 'error'
    // all controllers that are not undefined.
    controllers: Controllers[]
    resultQueue: [...results:any[]][]
    errorQueue: [...errorArgs:any[]][]
    controllerQueue: Controllers[]
    // halts the AsyncFnsInParallel and no resultCb or errorCb will be made
    // useful if one wants to cancel the `AsyncFnsInParallel`.
    halt():void
}

AsyncFnResolver

A simple way to type a Resolver is using the provided type AsyncFnResolver<ResultFn, ErrorFn> - note ErrorFn is optional e.g.:

import type { Resolver } from '....'
const AsyncFn = (
  x: string,
  resolver: AsyncFnResolver<(output: string) => void, (error: Error) => void>,
) => {
  resolver('...') // or
  resolver.result('...') // or
  resolver.error(new Error('...')) // or
}

Difference

Finds the set of all elements in the first array not contained in the second array (i.e. non duplicated items).

Note: typing is not battle tested and so unexpected edge cases may exist

Example Usage

const u1 = difference([1, 2, 3, 4] as const, [7, 6, 5, 4, 3] as const) //=> [1,2]
const u2 = difference([7, 6, 5, 4, 3] as const, [1, 2, 3, 4] as const) //=> [7,6,5]
const u3 = difference([7, 6, 5, 4, 3], [1, 2, 3, 4]) //=> [7,6,5] type: number[]

Intersection

Given two arrays, intersection returns a set composed of the elements common to both arrays.

Note: typing is not battle tested and so unexpected edge cases may exist

Example Usage

const u1 = intersection([1, 2, 3, 4] as const, [7, 6, 5, 4, 3] as const) //=> [3,4]
const u2 = intersection([7, 6, 5, 4, 3] as const, [1, 2, 3, 4] as const) //=> [3,4]
const u3 = intersection([7, 6, 5, 4, 3] as const, [1, 2, 3, 4, 'a'] as const) //=> [3,4]
const u4 = intersection([7, 6, 5, 4, 3] as const, [1, 2, 3, 4]) //=> [3,4] type: number[]
const u5 = intersection([7, 6, 5, 4, 3] as const, [1, 2, 3, 4, 'a']) //=> [3,4] type: (string | number)[]
const u6 = intersection([7, 6, 5, 4, 3], [1, 2, 3, 4, 'a']) //=> [3,4] type: (string | number)[]

EnhancedMap

Enhances javascript's map function

<V>(...iterable: readonly V[]) => {
    /**
     * adds an item and an optional `key` can be supplied,
     * otherwise insertion order is used.
     * @returns a function that removes the added item from the map.
     */
    add(item: V, key?: number): () => boolean;
    /**
     * adds an array of item to the map.
     * @returns a function that removes all of the added item from the map.
     */
    addItems(...items: V[]): () => void;
    /**
     *
     * @param basedOnInsertionOrder whether to shift
     *             based on insertion order, or key order
     * @returns V|undefined
     */
    shift(basedOnInsertionOrder?: boolean): V | undefined;
    /**
     * sets the item at `key`
     */
    set(key: number, value: V): any;
    /**
     * count of the total number of items added to the queue
     */
    readonly countOfItemsAdded: number;
    reduce<U>(callbackfn: (previousValue: U, currentValue: V, currentKey: number, index: number) => U, initialValue: U, reverseOrder?: boolean): U;
    map<U>(callbackfn: (value: V, key: number) => U, reverseOrder?: boolean): U[];
    clear(): void;
    delete(key: number): boolean;
    forEach(callbackfn: (value: V, key: number) => void, thisArg?: any): void;
    get(key: number): V | undefined;
    has(key: number): boolean;
    [Symbol.iterator](): IterableIterator<[number, V]>;
    readonly [Symbol.toStringTag]: string;
    readonly entries: IterableIterator<[number, V]>;
    readonly keys: IterableIterator<number>;
    readonly values: IterableIterator<V>;
    readonly size: number;
}

OutputPins

A function that creates an object which provides convenient ways to route an outcome.

const exampleResultErrorGenerator = outputPins<
  { result: [result: 'RESULT']; error: [error: Error]; cancel: [cancelReason: 'CANCEL'] },
  'result'
>('result', 'error', 'cancel')
type OutputError = OutputPinGetter<
  { result: [result: 'RESULT']; error: [error: Error]; cancel: [cancelReason: unknown] },
  'error'
>
type OutputCancel = OutputPinGetter<
  { result: [result: 'RESULT']; error: [error: Error]; cancel: [cancelReason: unknown] },
  'cancel'
>

type OutputResult = OutputPinGetter<
  { result: [result: 'RESULT']; error: [error: Error]; cancel: [cancelReason: unknown] },
  'result'
>
const fn = (error: boolean) => {
  const returnResult = exampleResultErrorGenerator()
  // eslint-disable-next-line no-constant-condition
  if (false) return returnResult.cancel('CANCEL')
  return error ? returnResult.error(new Error('error')) : returnResult('RESULT')
}
const results = fn(true)
if (results.isError()) throw (results as OutputError).error
if (results.isCancel()) throw (results as OutputCancel).cancel
console.log(results()) // 'RESULT'
console.log(results.isResult())
console.log((results as OutputResult).result)

resultNone

Inspired by the maybe monad, this function returns a function object, that can have either a result or a none set.

const fn = (error: boolean) => {
  const returnResult = resultNone<'RESULT', null>()
  return error ? returnResult.none(null) : returnResult('RESULT')
}
const results = fn(false)
if (results.isNone()) throw new Error('null')
console.log(results()) // 'RESULT'
console.log((results as ResultNone<'RESULT', null, 'result'>).result) // 'RESULT'

RresultError

Inspired by the either monad, means functions take the form (...args)=>[error,undefined]|[undefined,result]

type ResultError<E, R> = [error: E, result?: undefined] | [error: undefined, result: R]

toError

converts error into [error, undefined]

function toError<T>(error: T): [T, undefined]

toResult

converts result into [undefined, result]

function toResult<T>(result: T): [undefined, T]

isResult

tests if a ResultError is a result

function isResult<E, R>(output: ResultError<E, R>): output is [error: undefined, result: R]

isError

tests if a ResultError is a error

function isError<E, R>(output: ResultError<E, R>): output is [error: E, result: undefined]

resultErrorToResultNever

Unwraps a ResultError throwing any Error, or returning the Result

function resultErrorToResultNever(fn: (...args) => ResultError): (...args) => Result

resultNeverToResultError

Converts a function that may throw, to a ResultError, by try catching any errors.

function resultNeverToResultError<A extends unknown[], R>(
  fn: (...args: A) => R,
): (...args: A) => ResultError<unknown, R>

onlyExecuteOnResult

Takes as input a function fn which has one input returning a ResultError and returns a new function which takes one input of type ResultError and if that is an error then it returns the ResultError, and if it is a result it executes and returns fn with the result

Useful for chaining together functions which return ResultError

function onlyExecuteOnResult<E1, R1, E2, R2>(
  fn: (input: R1) => ResultError<E2, R2>,
): ([error, result]: ResultError<E1, R1>) => ResultError<E1 | E2, R2>

Other utilities

  • didError

    const fn = (error: boolean) => {
      const output = didError<Error>()
      return error ? output.error(new Error('ERROR')) : output()
    }
    const results = fn(true)
    if (results.isError()) console.log((results as DidError<Error, true>).errorValue()) // Error('ERROR')
    expect((results as DidError<Error, true>).errorValue()).toBeInstanceOf(Error)
  • wrapTryCatchInDidError

    const fn = wrapTryCatchInDidError((error: boolean) => {
      if (error) throw new Error('ERROR')
    })
    const results = fn(true)
    if (results.isError()) console.log((results as DidError<Error, true>).errorValue()) // Error('ERROR')
    expect((results as DidError<Error, true>).errorValue()).toBeInstanceOf(Error)
  • times

    console.log(times(50, (previousResult) => previousResult + 1, 10)) // 60
  • runFunctionsOnlyOnce

    let x = 1
    const once = runFunctionsOnlyOnce()
    const fn1 = once(() => {
      x += 1
      console.log(x)
    }, 'fn1')
    const fn2 = once(() => {
      x -= 1
      console.log(x)
    }, 'fn2')
    fn1()
    fn2() // throws cannot call 'fn2' after calling 'fn1'
  • validateFn

  • requireValue

    const fn = requireValue((x: any) => x)
    console.log(fn('a'))
    fn() // throws this function requires a value
  • callbackTee

  • reverseForEach

  • createUid

  • methodOnlyOnce

  • validObjects

  • capitaliseWords, capitalise

    const x = capitalise('cat') // 'Cat'
    const y = capitaliseWords('cat dog') // 'Cat Dog'
  • isObjectAndHasExecutableProperty

  • isGetter

  • isSetter

  • isValue

  • isFunction