Skip to content

About

Personal helpers and convenience functions for TypeScript

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

typescript-extra

Personal helpers and convenience functions for TypeScript.

Links:

Installation

# one of:
npm install @carlwr/typescript-extra
pnpm dlx jsr add @carlwr/typescript-extra

# run checks and tests:
npm run qa

Exports

Identifiers live on the package root or on a subpath, e.g.:

import { memoized } from '@carlwr/typescript-extra'
import { andThen } from '@carlwr/typescript-extra/maybe-async'

Node-only identifiers (not browser/bundler-safe) live under the /node subpath, e.g.:

import { rm_rf } from '@carlwr/typescript-extra/node'

Authoring

This README.md file, and any JSDoc documentation, is written entirely by me, Carl, a human developer. Agents are not allowed to touch the prose of these.

I-the-human hand-wrote almost all code, at least up to and including v0.12.2. For later versions, agentic tools may be used as a development aid; with myself as the reviewer and ultimate decision-maker. I am and will remain the sole author of this README.md file and any JSDoc.

 


API

allUnique

function allUnique<T extends Primitive>(xs: T[]): boolean

whether the elements of xs are unique, in the === sense

O(n) (if the hash tables behave, and they should for primitives) (constants likely not too great)

assertNever

function assertNever(_: never): never

Marker for exhaustiveness checks in discriminated unions.

cached

function cached<T>(f: () => T): () => T

Cached, lazy evaluation.

If f throws, the result is not cached: the next call will evaluate f again.

This function can be thought of as the synchronous analogue to the promise-oriented memoized. Note though that the error-handling differs.

example:

const getCfg = cached(() => readFileSync('config.json', 'utf8'))
let c0 = getCfg()  // reads the file, returns result
let c1 = getCfg()  // returns cached result

cachedUnary

function cachedUnary<K, V>(f: (k: K) => V): (k: K) => V

Cached, lazy evaluation of a single-argument function.

The cache is keyed by argument identity (SameValueZero, i.e. === except for on NaN).

If f throws, the result is not cached for that argument: the next call with the same argument will evaluate f again.

example:

const getText = cachedUnary((path: string) => readFileSync(path, 'utf8'))
getText('a.txt')  // reads a.txt, returns its content
getText('a.txt')  // returns cached a.txt content
getText('B.txt')  // reads B.txt, returns its content

drain

function drain<T>(xs: [T, ...T[]]): () => T

Stateful iterator yielding the elements of xs.

When the last element is reached, calls will continue to return that element indefinitely.

example:

const next = drain([1, 2, 3])
next()  // => 1
next()  // => 2
next()  // => 3
next()  // => 3
next()  // => 3 (returns 3 forever)

escapeRegExp

function escapeRegExp(s: string): string

escape regex meta-characters in s so the result matches s literally when used as a RegExp pattern

example:

new RegExp(escapeRegExp('a.b'))  // matches "a.b" literally, not "a<any>b"

extract

function extract<T>(root: unknown, pred: (k: string, v: unknown) => T | undefined): T[]

walk root recursively while collecting all Ts for which pred returns a defined result

example:

const root = {
  A: 1,
  b: 0,
  c: {
    99: [{A:2}, {e:"zero"}, null],
    A: 3
  }
}
function pred(k: string, v: any): number|undefined {
  if (k === 'A' && typeof v === 'number') { return v }
  return undefined
}
const result = extract<number>(root, pred)
// result === [1, 2, 3]

flatmapNonEmpty

function flatmapNonEmpty<T, U>(xs: readonly [T, T], fn: (x: T, i: number, arr: readonly [T, T]) => [U, ...U[]]): [U, ...U[]]

Flatmap over a non-empty array with a function returning a non-empty arrays. Return the flattened result as a non-empty array.

The callback receives (x, i, arr) — same shape as Array.prototype.flatMap, with arr narrowed to non-empty.

getMatch

function getMatch(re: RegExp, str: string): string

return the match of the regex, or throw if no match

hasAtleastTwo

function hasAtleastTwo<T>(xs: readonly T[]): xs is [T, T, ...T[]]

hasKey

function hasKey<T, K extends PropertyKey>(value: T, key: K): value is T & { [P in PropertyKey]: P extends keyof T ? Exclude<T[P], undefined> : unknown }

whether the value is an object that has the key

in the true branch, the type of the passed argument is narrowed to include the knowledge that the key is present, without destroying any other knowledge about the type prior to the call

if K is a known key of T, the narrowed property type also has undefined excluded (the in check has proved the key is set)

isDefined

function isDefined<T>(x: T | null | undefined): x is NonNullable<T>

isEmpty

function isEmpty<T>(xs: readonly T[]): xs is []

isNonEmpty

function isNonEmpty<T>(xs: readonly T[]): xs is [T, ...T[]]

isSingle

function isSingle<T>(xs: readonly T[]): xs is [T]

mapAsync

function mapAsync<T, U>(xs: readonly T[], f: (x: T) => Promise<U>): Promise<U[]>

Map an async function over xs.

mapFilterAsync

function mapFilterAsync<T, U>(xs: readonly T[], f: (x: T) => Promise<U | null | undefined>): Promise<U[]>

Map an async function over xs and keep only defined results.

mapNonEmpty

function mapNonEmpty<T, U>(xs: readonly [T, T], fn: (x: T, i: number, arr: readonly [T, T]) => U): [U, ...U[]]

Map over a non-empty array while preserving the type as non-empty.

The callback receives (x, i, arr) — same shape as Array.prototype.map, with arr narrowed to non-empty.

memoized

function memoized<T>(f: () => Promise<T>): () => Promise<T>

Cached, lazy, single-flight evaluation of a promise.

A rejected promise is cached as well, i.e. no re-attempts (rejections are assumed to be permanent).

memoizedRetry

function memoizedRetry<T>(f: () => Promise<T>): () => Promise<T>

Cached, lazy, single-flight evaluation of a promise.

A rejected promise is not cached: the next call will evaluate f again.

The retrying analogue of memoized.

nonEmpty

function nonEmpty<T>(head: T, ...rest: T[]): [T, ...T[]]

Construct a NonEmpty from a guaranteed head and zero or more tail elements.

Useful for declaring or building a [T,...T[]] without an explicit type annotation or cast.

example:

const xs = nonEmpty(1, 2, 3)  // typed [number, ...number[]]

partitionAsync

function partitionAsync<A, B extends boolean>(items: readonly A[], pred: (a: A) => Promise<B>): Promise<readonly [A[], A[]]>

Split items into those for which the async predicate resolves truthy and falsy.

safeIndex

function safeIndex<T>(xs: readonly [T, T], i: number): T

return the ith element of xs, or throw if i is out of bounds

trim

function trim(str: string): string

the .trim() method as a function

trim(str) <=> str.trim()

withoutFirstSubstring

function withoutFirstSubstring(first: string, str: string): string

remove a substring from the beginning of a string; throw if str does not start with first

example:

withoutFirstSubstring('e', 'ego') // => 'go'

API: /node

rm_rf

function rm_rf(path: string): Promise<void>

remove a file or directory recursively; ignore errors

API: /maybe-async

andFinally

function andFinally<R, C = void>(run: () => R, cleanup: () => C & NotThenable<C>): R extends PromiseLike<unknown> ? Promise<Awaited<R>> : R

Run the function run. Run the synchronous cleanup function once, regardless of whether run returned, threw or rejected.

If run returns a promise, cleanup is run when that promise has settled.

The return value of cleanup is always ignored.

What andFinally returns, and how it calls cleanup, is determined by whether run threw, and if it didn't, what it returned:

run -> andFinally calls cleanup
returned a non-promise -> returns or throws synchronously
threw -> throws synchronously
returned a promise -> returns a native promise asynchronously

The outcome of andFinally, and what error is attached as cause, if any, is determined by whether run threw/rejected and whether cleanup threw:

run cleanup -> outcome cause set to
succeeded returned -> run's value –
failed returned -> run's error –
succeeded threw -> cleanup's error –
failed threw -> cleanup's error run's error, if unset

If cleanup throws an error and run failed, cleanup's error object is mutated (can only matter if shared or reused).

In the documentation above, "promise" is used in the meaning of PromiseLike on the type level, and an object with a PromiseLike.then method at runtime. The two are typically the same but may disagree in rare cases.

andThen

function andThen<V, B>(value: V, f: (a: Awaited<V>) => B): V extends PromiseLike<unknown> ? Promise<Awaited<B>> : PromiseLike<unknown> extends V ? B | Promise<Awaited<B>> : B

Apply a function (f) to a value (value). value may or may not be a promise, and f may or may not return a promise.

If value is a promise, f receives the awaited value.

What andThen returns, and how it calls f, is determined by what value is:

value -> andThen calls f
non-promise -> returns what f returned synchronously
fulfilling promise -> returns a native promise asynchronously
rejecting promise -> returns a rejecting promise never

If f throws, then if value is

  • a non-promise, the error propagates
  • a promise, then the returned promise rejects

If value is a promise, a flattened (native) promise is returned.

If a .then getter of value throws, the returned promise will be a rejection.

In the documentation above, "promise" is used in the meaning of PromiseLike on the type level, and an object with a PromiseLike.then method at runtime. The two are typically the same but may disagree in rare cases.

About

Personal helpers and convenience functions for TypeScript

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages