@carlwr/typescript-extra
v0.11.0
Published
Personal helpers and convenience functions for TypeScript
Readme
typescript-extra
Personal helpers and convenience functions for TypeScript
Links:
- Github:
carlwr/typescript-extra - npm:
@carlwr/typescript-extra - JSR:
@carlwr/typescript-extra
Installation
# one of:
npm install @carlwr/typescript-extra
pnpm dlx jsr add @carlwr/typescript-extra
# run checks and tests:
npm run qaExports
Node-only identifiers live under the /node subpath, e.g.:
import { rm_rf } from '@carlwr/typescript-extra/node'Browser/bundler-safe identifiers (documented below) live on the package root, e.g.:
import { memoized } from '@carlwr/typescript-extra'API
allUnique
function allUnique<T>(xs: T[]): booleanwhether 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): neverMarker for exhaustiveness checks in discriminated unions.
cached
function cached<T>(f: () => T): () => TCached, 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 resultcachedUnary
function cachedUnary<K, V>(f: (k: K) => V): (k: K) => VCached, 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 contentdrain
function drain<T>(xs: [T, ...T[]]): () => TStateful 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): stringescape 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 returnes 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): stringreturn 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>(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).
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>(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): Treturn the ith element of xs, or throw if i is out of bounds
trim
function trim(str: string): stringthe .trim() method as a function
trim(str) <=> str.trim()
withoutFirstSubstring
function withoutFirstSubstring(first: string, str: string): stringremove a substring from the beginning of a string; throw if str does not start with first
example:
withoutFirstSubstring('e', 'ego') // => 'go'