An open API service indexing awesome lists of open source software.

https://github.com/lyonbot/yon-utils

Some utils that I repeated too many times. DRY!
https://github.com/lyonbot/yon-utils

Last synced: 5 months ago
JSON representation

Some utils that I repeated too many times. DRY!

Awesome Lists containing this project

README

          

# yon-utils

Some utils and remix that I repeated in many projects.

This package includes some light-weight alternatives to packages like:

our | is alternative to / remix of
------- | -----------------
[elt](#fn-elt) / [clsx](#fn-clsx) | clsx, classnames, h, hyperscript
[maybeAsync](#fn-maybeAsync) / [makePromise](#fn-makePromise) / [PromiseEx](#fn-PromiseEx) | imperative-promise, bluebird
[stringHash](#fn-stringHash) | cyrb53, murmurhash ...
<some lodash-like functions> | lodash

There are also some interesting original utils like [shallowEqual](#fn-shallowEqual) / [newFunction](#fn-newFunction) / [toArray](#fn-toArray) / [getVariableName](#fn-getVariableName) etc. Feel free to explore!

## QuickStart

[Play in CodeSandbox](https://codesandbox.io/s/yon-utils-playground-xwh4qt)

All modules are shipped as ES modules and tree-shakable.

- via package manager

`npm install yon-utils`

- via import within ``

`import { elt } from "https://unpkg.com/yon-utils"`

<!-- auto generate begin -->

## ToC

| category | exports |
| --- | ------- |
| dom | **methods**: [clsx](#clsxargs) / [elt](#elttagname-attrs-children) |
| execute | **methods**: [newFunction](#newfunctionargumentnames-functionbody-options) / [noop](#noop) / [retry](#retryfn-opts) |
| flow | **methods**: [fnQueue](#fnqueue) / [makeAsyncIterator](#makeasynciterator) / [makeEffect](#makeeffectfn-isequal) / [withDefer](#withdeferfn) / [withAsyncDefer](#withasyncdeferfn) / [createWorkerHandler](#createworkerhandlermethods) / [createWorkerDispatcher](#createworkerdispatcherpostmessage) |
| interaction | **methods**: [writeClipboard](#writeclipboardtext) / [readClipboard](#readclipboardtimeout) / [modKey](#modkeyev) / [startMouseMove](#startmousemove-initialevent-onmove-onend-) <br /> **vars**: [IS_MAC](#is_mac) / [MOD_KEY](#mod_key) / [MOD_KEY_LABEL](#mod_key_label) <br /> **types**: [KeyboardEventLike](#interface-keyboardeventlike) / [MouseMoveInfo](#interface-mousemoveinfo) / [MouseMoveInitOptions](#interface-mousemoveinitoptions) |
| manager | **methods**: [getSearchMatcher](#getsearchmatcherkeyword) <br /> **classes**: [ModuleLoader](#new-moduleloadertsource) / [CircularDependencyError](#new-circulardependencyerrorquery-querystack) <br /> **types**: [ModuleLoaderCache](#type-moduleloadercachet) / [ModuleLoaderSource](#interface-moduleloadersourcet) |
| math | **methods**: [isInsideRect](#isinsiderectx-y-rect) / [isRectEqual](#isrectequalrect1-rect2-epsilon) / [getRectIntersection](#getrectintersectionrects) / [getRectUnion](#getrectunionrects) / [approx](#approxa-b-epsilon) / [clamp](#clampn-min-max) / [lerp](#lerpmin-max-t) / [easeOut](#easeoutt) / [randomNum](#randomnummin-max) <br /> **types**: [RectLike](#type-rectlike) / [RectLikeXYWH](#interface-rectlikexywh) / [RectLikeLTWH](#interface-rectlikeltwh) |
| promise | **methods**: [delay](#delaymilliseconds) / [debouncePromise](#debouncepromisefn) / [maybeAsync](#maybeasyncinput) / [makePromise](#makepromise) <br /> **classes**: [PromiseEx](#new-promiseextexecutor) / [PromisePendingError](#new-promisependingerrorcause) / [SwappablePromise](#new-swappablepromiset) <br /> **types**: [ImperativePromiseEx](#type-imperativepromiseext) / [SwappablePromiseExecutor](#interface-swappablepromiseexecutor) |
| string | **methods**: [stringHash](#stringhashstr) / [getVariableName](#getvariablenamebasicname-existingvariables) / [bracket](#brackettext1-text2-brackets) / [randomStr](#randomstrsize-dict) |
| value | **methods**: [shallowEqual](#shallowequalobja-objb-depth) / [toArray](#toarrayvalue) / [find](#finditerator-predicate) / [reduce](#reduceiterator-initial-reducer) / [head](#headiterator) / [contains](#containscollection-item) / [forEach](#foreachobjorarray-iter) / [isNil](#isnilobj) / [isObject](#isobjectobj) / [isThenable](#isthenablesth) <br /> **types**: [OneOrMany](#type-oneormanyt) / [Predicate](#type-predicatet) / [CollectionOf](#type-collectionoft) / [IterItem](#type-iteritemt) / [MaybePromise](#type-maybepromiset) / [Falsy](#type-falsy) / [Nil](#type-nil) / [Fn](#type-fnret-args) |

## ๐Ÿงฉ `dom/clsx`

### `clsx(...args)`

- **...args**: `any[]`

- *returns*: `string`

construct className strings conditionally.

can be an alternative to `classnames()`. modified from [lukeed/clsx](https://github.com/lukeed/clsx). to integrate with Tailwind VSCode, [read this](https://github.com/lukeed/clsx#tailwind-support)

<br/>

## ๐Ÿงฉ `dom/elt`

### `elt(tagName, attrs, ...children)`

- **tagName**: `string` โ€” for example `"div"` or `"button.my-btn"`

- **attrs**: `any` โ€” attribute values to be set. beware:
- `onClick` and a `function` value, will be handled by `addEventListener()`
- `!onClick` or `onClick.capture` will make it capture
- `style` value could be a string or object
- `class` value could be a string, object or array, and will be process by `clsx()`
- `className` is alias of `class`

- **...children**: `any[]` โ€” can be strings, numbers, nodes. other types or nils will be omitted.

- *returns*: `HTMLElement`

Make `document.createElement` easier

```js
var button = elt(
'button.myButton', // tagName, optionally support .className and #id
{
title: "a magic button",
class: { isPrimary: xxx.xxx }, // className will be processed by clsx
onclick: () => alert('hi')
},
'Click Me!'
)
```

This function can be used as a [jsxFactory](https://www.typescriptlang.org/tsconfig#jsxFactory), aka [JSX pragma](https://www.gatsbyjs.com/blog/2019-08-02-what-is-jsx-pragma/).
You can add <code>/** &#64;jsx elt *&#47;</code> into your code, then TypeScript / Babel will use `elt` to process JSX expressions:

> /** &#64;jsx elt *&#47;
>
> var button = &lt;button class="myButton" onclick={...}>Click Me&lt;/button></code></pre>

<br/>

## ๐Ÿงฉ `execute/function`

### `newFunction(argumentNames, functionBody, options?)`

- **argumentNames**: `NameArray<ARGS>` โ€” a `string[]` of argument names

- **functionBody**: `string` โ€” the function body

- **options?**: `{ async? }`

- **async?**: `boolean | undefined` โ€” set to `true` if the code contains `await`, the new function will be an async function

- *returns*: `Fn<RESULT, ARGS>`

like `new Function` but with more reasonable options and api

<br/>

### `noop()`

- *returns*: `void`

The nothing-to-do function

<br/>

## ๐Ÿงฉ `execute/retry`

### `retry(fn, opts?)`

- **fn**: `() => T | Nil | Promise<T | Nil>`

- **opts?**: `{ duration?, interval?, signal? }`

- **duration?**: `number | undefined`

- **interval?**: `number | undefined`

- **signal?**: `AbortSignal | undefined`

- *returns*: `Promise<T>`

a simple retry function for Promise-returning functions.

if `fn` resolves with `null | undefined` or throw Error, it will retry.

no exponential backoff, just retry periodically till timeout or success.

<br/>

## ๐Ÿงฉ `flow/fnQueue`

### `fnQueue()`

- *returns*: `{ tap, tapSilent, call, queue }`

- **tap**: `AddCallbacks<Args>` โ€” add one or more functions.

- **tapSilent**: `AddCallbacks<Args>` โ€” add functions, and will silently ignore their errors

- **call**: `(...args: Args) => void` โ€” run functions. if fnQueue is async, returns Promise

- **queue**: `{ silent?: boolean | undefined; fn: Fn<any, Args>; }[]` โ€” the queued functions

Store a list of functions, and execute them in order.

- **Use case**: ๐Ÿงน disposer (clean resources) / โšก event emitter / ๐Ÿชข tapable-like middleware
- **Defaults**: sync, FIFO, errors will abort

Use decorators or options, to customize a fnQueue:

- `fnQueue.async()` to create async queue -- the `call()` will return a Promise instead.
- `fnQueue.filo()` to create FILO queue.
- `fnQueue.onetime()` to clear the queue after each call.
- `fnQueue({ error: 'ignore' })` to ignore errors.

Options can be combined, like `fnQueue.async.onetime()` -- see example below.

#### Example

```js
// create an async fnQueue with options ...
const disposer = fnQueue.async.onetime({ error: 'ignore' });

try {
const srcFile = await openFile(path1);
disposer.tap(() => srcFile.close());

const dstFile = await openFile(path2);
disposer.tap(() => dstFile.close());

await copyData(srcFile, dstFile);
} finally {
await disposer.call();
}
```

- **async**: `Factory<Promise<void>>`

- **async**: `Factory<Promise<void>>`

- **filo**: `Factory<Promise<void>>` โ€” change execution order to FILO (first-in, last-out, like a stack)

- **onetime**: `Factory<Promise<void>>` โ€” after each call, clear the queue

- **filo**: `Factory<void>` โ€” change execution order to FILO (first-in, last-out, like a stack)

- **async**: `Factory<Promise<void>>`

- **filo**: `Factory<void>` โ€” change execution order to FILO (first-in, last-out, like a stack)

- **onetime**: `Factory<void>` โ€” after each call, clear the queue

- **onetime**: `Factory<void>` โ€” after each call, clear the queue

- **async**: `Factory<Promise<void>>`

- **filo**: `Factory<void>` โ€” change execution order to FILO (first-in, last-out, like a stack)

- **onetime**: `Factory<void>` โ€” after each call, clear the queue

<br/>

## ๐Ÿงฉ `flow/makeAsyncIterator`

### `makeAsyncIterator()`

- *returns*: `{ write(value: T): void; end(error?: any): void; } & AsyncIterableIterator<T>`

- **write**: `(value: T) => void`

- **end**: `(error?: any) => void`

Help you convert a callback-style stream into an async iterator. Also works on "observable" value like RxJS.

You can think of this as a simplified `new Readable({ ... })` without headache.

#### Example

```js
const iterator = makeAsyncIterator();

socket.on('data', value => iterator.write(value));
socket.on('end', () => iterator.end());
socket.on('error', (err) => iterator.end(err));

for await (const line of iterator) {
console.log(line);
}
```

<br/>

## ๐Ÿงฉ `flow/makeEffect`

### `makeEffect(fn, isEqual?)`

- **fn**: `(input: T, previous: T | undefined) => void | (() => void)`

- **isEqual?**: `(x: T, y: T) => boolean`

- *returns*: `{ cleanup, value }`

- **cleanup**: `() => void` โ€” invoke last cleanup function, and reset `value` to undefined

- **value?**: `T | undefined` โ€” get last received value, or `undefined` if effect was clean up

Wrap `fn` and create an unary function. The actual `fn()` executes only when the argument changes.

Meanwhile, your `fn` may return a cleanup function, which will be invoked before new `fn()` calls
-- just like React's `useEffect`

The new unary function also provide `cleanup()` method to forcedly do the cleanup, which will also clean the memory of last input.

#### Example

```js
const sayHi = makeEffect((name) => {
console.log(`Hello, ${name}`);
return () => {
console.log(`Goodbye, ${name}`);
}
});

sayHi('Alice'); // output: Hello, Alice
sayHi('Alice'); // no output
sayHi('Bob'); // output: Goodbye, Alice Hello, Bob
sayHi.cleanup(); // output: Goodbye, Bob
sayHi.cleanup(); // no output
```

<br/>

## ๐Ÿงฉ `flow/withDefer`

### `withDefer(fn)`

- **fn**: `(defer: AddCallbacks<[]>) => Ret`

- *returns*: `Ret`

Get rid of `try catch finally` hells!
Use `defer(callback)` to clean up resources, and they will run in `finally` stage.

Works on both sync and async procedures.

For sync functions:

```js
// sync
const result = withDefer((defer) => {
const file = openFileSync('xxx')
defer(() => closeFileSync(file)) // <- register callback

const parser = createParser()
defer(() => parser.dispose()) // <- register callback

return parser.parse(file.readSync())
})
```

For async functions, use `withAsyncDefer`

```js
// async
const result = await withAsyncDefer(async (defer) => {
const file = await openFile('xxx')
defer(async () => await closeFile(file)) // <- defer function can be async now!

const parser = createParser()
defer(() => parser.dispose()) // <-

return parser.parse(await file.read())
})
```

**Error handling**

If one callback throws, rest callbacks still work. And you get the last error thrown.

To suppress a callback's throwing, use `defer.silent(callback)`

```js
defer.silent(() => closeFile(file)) // will never throws
```

#### Remark

Refer to [TypeScript using syntax](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-2.html#using-declarations-and-explicit-resource-management),
[TC39 Explicit Resource Management](https://github.com/tc39/proposal-explicit-resource-management) and GoLang's `defer` keyword.

<br/>

### `withAsyncDefer(fn)`

- **fn**: `(defer: AddCallbacks<[]>) => Ret`

- *returns*: `Ret`

Same as **withDefer**, but this returns a Promise, and supports async callbacks.

<br/>

## ๐Ÿงฉ `flow/worker-rpc`

### `createWorkerHandler(methods)`

- **methods**: `T` โ€” Object containing method implementations that can be called remotely

- *returns*: `(payload: RpcPayload) => void` โ€” A handler function that processes incoming RPC payloads

Creates a handler function for processing RPC requests in a Worker.

#### Example

```js
// In your worker file:
import { createWorkerHandler } from './worker-rpc';

const handler = createWorkerHandler({
async getData(id) {
// implementation
return result;
}
});

self.onmessage = (e) => {
if (e.data?.type === 'myCall') handler(e.data.payload);
}
```

<br/>

### `createWorkerDispatcher(postMessage)`

- **postMessage**: `(payload: RpcPayload, transferable: Transferable[]) => void` โ€” Function that sends messages to the worker (typically worker.postMessage)

- *returns*: `T` โ€” A proxy object where each property access creates a function that calls the corresponding method in the worker

Creates a proxy object that dispatches method calls to a Worker via RPC.

#### Example

```js
// In your main thread:
import { createWorkerDispatcher } from './worker-rpc';

const worker = new Worker('path/to/worker.js');
const api = createWorkerDispatcher<MyWorkerAPI>((payload, transferable) =>
worker.postMessage({
type: 'myCall',
payload,
}, transferable)
);

// Now you can call worker methods as if they were local:
// No need to worry about initiating, it automatically wait for the worker to be ready
const result = await api.getData(123);
```

<br/>

## ๐Ÿงฉ `interaction/clipboard`

### `writeClipboard(text)`

- **text**: `string`

- *returns*: `Promise<void>`

write text to clipboard, with support for insecure context and legacy browser!

note: if you are in HTTPS and modern browser, you can directly use `navigator.clipboard.writeText()` instead.

<br/>

### `readClipboard(timeout?)`

- **timeout?**: `number` โ€” default 1500

- *returns*: `Promise<string>`

read clipboard text.

if user rejects or hesitates about the permission for too long,
this will throw an Error.

<br/>

## ๐Ÿงฉ `interaction/keyboard`

### `modKey(ev)`

- **ev**: `KeyboardEventLike`

- *returns*: `number`

get Modifier Key status from a Event

#### Remark

1. use `modKey.Mod` to indicate if the key is `โŒ˜`(Cmd) on Mac, or `Ctrl` on Windows/Linux
2. use `|` (or operator) to combine modifier keys. see example below.

#### Example

```js
if (modKey(ev) === (modKey.Mod | modKey.Shift) && ev.code === 'KeyW') {
// Ctrl/Cmd + Shift + W, depends on the OS
}
```

- **None**: `0`

- **Ctrl**: `1`

- **Cmd**: `2`

- **Shift**: `4`

- **Alt**: `8`

- **Mod**: `1 | 2` โ€” equals to `Ctrl` or `Cmd`, depending on the OS

<br/>

### `IS_MAC`

```ts
boolean
```

<br/>

### `MOD_KEY`

the proper way to get modKey status from KeyboardEvent

```ts
"metaKey" | "ctrlKey"
```

<br/>

### `MOD_KEY_LABEL`

the proper label of modKey. eg: `โŒ˜` for Mac, `Ctrl` for Windows/Linux

```ts
"โŒ˜" | "Ctrl"
```

<br/>

### `interface KeyboardEventLike`

- **ctrlKey?**: `boolean | undefined`

- **metaKey?**: `boolean | undefined`

- **shiftKey?**: `boolean | undefined`

- **altKey?**: `boolean | undefined`

<br/>

## ๐Ÿงฉ `interaction/mouseMove`

### `startMouseMove({ initialEvent, onMove, onEnd })`

- **__0**: `MouseMoveInitOptions`

- *returns*: `Promise<MouseMoveInfo>` โ€” a Promise with final position when user releases button

Powerful tool to implement any drag / resize action easily!

While dragging, it tracks the cursor's movement and reports to `onMove(...)`.
And when user releases the button, it will call `onEnd(...)`.

1. the element needs `touch-action: none` style to prevent scrolling on mobile

2. use within `pointerdown` event

3. `event.preventDefault()` is necessary too

#### Example

```js
button.style.touchAction = 'none' // CSS touch-action: none
button.addEventListener('pointerdown', event => {
event.preventDefault();
startMouseMove({
initialEvent: event,
onMove({ deltaX, deltaY }) { ... },
onEnd({ deltaX, deltaY }) { ... },
});
});
```

<br/>

### `interface MouseMoveInfo`

- **clientX**: `number`

- **clientY**: `number`

- **deltaX**: `number`

- **deltaY**: `number`

- **duration**: `number` โ€” in milliseconds, since `startMouseMove` called

- **event**: `MouseEvent | PointerEvent`

- **pointerId**: `number | false`

- **cancelled?**: `boolean | undefined`

<br/>

### `interface MouseMoveInitOptions`

- **initialEvent**: `MouseEvent | PointerEvent`

- **onMove?**: `((data: MouseMoveInfo) => void) | undefined`

- **onEnd?**: `((data: MouseMoveInfo) => void) | undefined`

<br/>

## ๐Ÿงฉ `manager/moduleLoader`

### `new ModuleLoader<T>(source)`

- **source**: `ModuleLoaderSource<T>`

All-in-one ModuleLoader, support both sync and async mode, can handle circular dependency problem.

### Example in Sync

```js
const loader = new ModuleLoader({
// sync example
resolve(query, { load }) {
if (query === 'father') return 'John'
if (query === 'mother') return 'Mary'

// simple alias: just `return load('xxx')`
if (query === 'mom') return load('mother')

// load dependency
// - `load('xxx').value` for sync, don't forget .value
// - `await load('xxx')` for async
if (query === 'family') return `${load('father').value} and ${load('mother').value}`

// always return something as fallback
return 'bad query'
}
})

console.log(loader.load('family').value) // don't forget .value
```

### Example in Async

```js
const loader = new ModuleLoader({
// async example
async resolve(query, { load }) {
if (query === 'father') return 'John'
if (query === 'mother') return 'Mary'

// simple alias: just `return load('xxx')`
if (query === 'mom') return load('mother')

// load dependency
// - `await load('xxx')` for async
// - no need `.value` in async mode
if (query === 'family') return `${await load('father')} and ${await load('mother')}`

// always return something as fallback
return 'bad query'
}
})

console.log(await loader.load('family')) // no need `.value` with `await`
```

<details><summary>
<em>๐Ÿ“– show members of <code>ModuleLoader</code> &raquo;</em>
</summary>

#### `ModuleLoader # cache`

- *type*: `ModuleLoaderCache<{ dependencies?: string[] | undefined; promise: PromiseEx<T>; }>`

#### `ModuleLoader # load(query)`

- **query**: `string`

- *returns*: `PromiseEx<T>`

fetch a module

#### `ModuleLoader # getDependencies(query, deep?)`

- **query**: `string`

- **deep?**: `boolean`

- *returns*: `PromiseEx<string[]>`

get all direct dependencies of a module.

note: to get reliable result, this will completely load the module and deep dependencies.

</details>

<br/>

### `new CircularDependencyError(query, queryStack)`

- **query**: `string`

- **queryStack**: `string[]`

The circular dependency Error that `ModuleLoader` might throw.

<details><summary>
<em>๐Ÿ“– show members of <code>CircularDependencyError</code> &raquo;</em>
</summary>

#### `CircularDependencyError # query`

- *type*: `string`

the module that trying to be loaded.

#### `CircularDependencyError # queryStack`

- *type*: `string[]`

the stack to traceback the loading progress.

#### `CircularDependencyError # name`

- *type*: `string`

always `'CircularDependencyError'`

</details>

<br/>

### `type ModuleLoaderCache<T>`

used by ModuleLoader

- **get**: `(query: string) => T | undefined`

- **set**: `(query: string, value: T) => any`

- **delete**: `(query: string) => any`

- **clear**: `() => void`

<br/>

### `interface ModuleLoaderSource<T>`

used by ModuleLoader

- **resolve**: `(query: string, ctx: { load(target: string): PromiseEx<T>; noCache<T>(value: T): T; }) => MaybePromise<T>` โ€” You must implement a loader function. It parse `query` and returns the module content.

1. It could be synchronous or asynchronous, depends on your scenario.
2. You can use `load()` from `ctx` to load dependencies. Example: `await load("common")` or `load("common").value`
3. All queries are cached by default. To bypass it, use `ctx.noCache`. Example: `return noCache("404: not found")`

- **cache?**: `ModuleLoaderCache<any> | undefined`

<br/>

## ๐Ÿงฉ `manager/simpleSearch`

### `getSearchMatcher(keyword)`

- **keyword**: `string`

- *returns*: `{ test, filter, filterEx }`

- **test**: `(record: any) => number` โ€” test one record and tell if it matches.

the `record` could be a string, array and object(only values will be tested).

will return `0` for not matched, `1` for fuzzy matched, `> 1` for partially accurately matched

- **filter**: `FilterFunction` โ€” filter a list / collection, and get the sorted search result.

returns a similarity-sorted array of matched values.

also see `filterEx` if want more information

- **filterEx**: `FilterExFunction` โ€” filter a list / collection, and get the sorted search result with extra information.

returns a similarity-sorted array of `{ value, score, index, key }`.

also see `filter` if you just want the values.

Simple utility to start searching

#### Example

```js
// note: items can be object / array / array of objects ...
const items = ['Alice', 'Lichee', 'Bob'];

const result = getSearchMatcher('lic').filter(items);
// -> ['Lichee', 'Alice']
```

<br/>

## ๐Ÿงฉ `math/geometry`

### `isInsideRect(x, y, rect)`

- **x**: `number` โ€” The x-coordinate of the point.

- **y**: `number` โ€” The y-coordinate of the point.

- **rect**: `RectLike` โ€” The rectangle to check against.

- *returns*: `boolean`

Determines whether a point (x, y) is inside a rectangle.

<br/>

### `isRectEqual(rect1, rect2, epsilon?)`

- **rect1?**: `Nil | RectLike` โ€” The first rectangle to compare.

- **rect2?**: `Nil | RectLike` โ€” The second rectangle to compare.

- **epsilon?**: `number | undefined` โ€” The maximum difference allowed between the values of the rectangles' properties.

- *returns*: `boolean`

Determines whether two rectangles are equal.

<br/>

### `getRectIntersection(...rects)`

- **...rects**: `(Nil | RectLike)[]` โ€” The rectangles

- *returns*: `RectLike` โ€” The intersection rectangle. Can be passed to `DOMRect.fromRect(.)`

Calculates the intersection of rectangles.

<br/>

### `getRectUnion(...rects)`

- **...rects**: `(Nil | RectLike)[]` โ€” The rectangles

- *returns*: `RectLike` โ€” The union rectangle. Can be passed to `DOMRect.fromRect(.)`

Calculates the union (out bounding box) of rectangles.

<br/>

### `type RectLike`

a interface that fits DOMRect and many other situation

```ts
export type RectLike = RectLikeXYWH | RectLikeLTWH;
```

<br/>

### `interface RectLikeXYWH`

- **x**: `number`

- **y**: `number`

- **width**: `number`

- **height**: `number`

<br/>

### `interface RectLikeLTWH`

- **left**: `number`

- **top**: `number`

- **width**: `number`

- **height**: `number`

<br/>

## ๐Ÿงฉ `math/number`

### `approx(a, b, epsilon?)`

- **a**: `number`

- **b**: `number`

- **epsilon?**: `number` โ€” The maximum difference allowed between the two numbers. Defaults to 0.001.

- *returns*: `boolean`

Determines if two numbers are approximately equal within a given epsilon.

<br/>

### `clamp(n, min, max)`

- **n**: `number`

- **min**: `number`

- **max**: `number`

- *returns*: `number`

Clamp a number between min and max

<br/>

### `lerp(min, max, t)`

- **min**: `number`

- **max**: `number`

- **t**: `number` โ€” The interpolation value clamped between 0 and 1

- *returns*: `number`

Linearly interpolate

#### Example

```js
const value = lerp(0, 2, 0.5) // value will be 1
```

<br/>

### `easeOut(t)`

- **t**: `number`

- *returns*: `number`

simple ease-out function, useful for animation, tween, and fake progress bar

#### Example

```js
// a fake progress bar never reach 100%
const sinceTime = Date.now()
const progress = 0.95 * easeOut((Date.now() - sinceTime) / 5000)
```

<br/>

### `randomNum(min, max)`

- **min**: `number`

- **max**: `number`

- *returns*: `number`

Get a random number from [min, max)

<br/>

## ๐Ÿงฉ `promise/misc`

### `delay(milliseconds)`

- **milliseconds**: `number`

- *returns*: `Promise<void>`

<br/>

### `debouncePromise(fn)`

- **fn**: `() => Promise<T>` โ€” The function to be debounced.

- *returns*: `{ (): Promise<T>; clear(): void; }` โ€” The debounced function.

- **clear**: `() => void` โ€” make next call always return new Promise

Creates a debounced version of a function that returns a promise.

The returned function will ensure that only one Promise is created and executed at a time,
even if the debounced function is called multiple times before last Promise gets finished.

All _suppressed_ calls will get the last started Promise.

<br/>

## ๐Ÿงฉ `promise/promise`

### `maybeAsync(input)`

- **input**: `T | Promise<T> | (() => T | Promise<T>)` โ€” your sync/async function to run, or just a value

- *returns*: `PromiseEx<Awaited<T>>` โ€” a crafted Promise that exposes `{ status, value, reason }`, whose `status` could be `"pending" | "fulfilled" | "rejected"`

Run the function, return a crafted Promise that exposes `status`, `value` and `reason`

If `input` is sync function, its result will be stored in `promise.value` and `promise.status` will immediately be set as "fulfilled"

Useful when you are not sure whether `fn` is async or not.

<br/>

### `makePromise()`

- *returns*: `ImperativePromiseEx<T>`

Create an imperative Promise.

Returns a Promise with these 2 methods exposed, so you can control its behavior:

- `.resolve(result)`
- `.reject(error)`

Besides, the returned Promise will expose these useful properties
so you can get its status easily:

- `.wait([timeout])` โ€” wait for result, if timeout set and exceeded, a `PromisePendingError` will be thrown
- `.status` โ€” could be `"pending" | "fulfilled" | "rejected"`
- `.result` and `.reason`
- `.value` โ€” fail-safe get result (or cause an Error from rejection, or cause a `PromisePendingError` if still pending)

#### Example

```js
const handler = makePromise();

doSomeRequest(..., result => handler.resolve(result));

// wait with timeout
const result = await handler.wait(1000);

// or just await
const result = await handler;
```

<br/>

### `new PromiseEx<T>(executor)`

- **executor**: `(resolve: (value: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void`

a crafted Promise that exposes `{ status, value, reason }`

Note: please use `maybeAsync()` or `PromiseEx.resolve()` to create a PromiseEx

<details><summary>
<em>๐Ÿ“– show members of <code>PromiseEx</code> &raquo;</em>
</summary>

#### `PromiseEx # status`

- *type*: `"pending" | "fulfilled" | "rejected"`

#### `PromiseEx # reason`

- *type*: `any`

if rejected, get the reason.

#### `PromiseEx # result`

- *type*: `T | undefined`

get result, or nothing if not fulfilled.

note: you might need `.value` which follows **fail-fast mentality**

#### `PromiseEx # loading`

- *type*: `boolean`

equivalent to `.status === "pending"`

#### `PromiseEx # isPending()`

- *returns*: `boolean`

#### `PromiseEx # isFulfilled()`

- *returns*: `boolean`

#### `PromiseEx # isRejected()`

- *returns*: `boolean`

#### `PromiseEx # value`

- *type*: `T | undefined`

**fail-fast mentality**, safely get the result.

- if pending, throw `new PromisePendingError(this)`
- if rejected, throw `.reason`
- if fulfilled, get `.result`

#### `PromiseEx # wait(timeout?)`

- **timeout?**: `number | undefined`

- *returns*: `Promise<T>`

wait for resolved / rejected.

optionally can set a timeout in milliseconds. if timeout, a `PromisePendingError` will be thrown

#### `PromiseEx # thenImmediately(onfulfilled?, onrejected?)`

- **onfulfilled?**: `Nil | ((value: T) => TResult1 | PromiseLike<TResult1>)`

- **onrejected?**: `Nil | ((reason: any) => TResult2 | PromiseLike<TResult2>)`

- *returns*: `PromiseEx<TResult1 | TResult2>`

Like `then()` but immediately invoke callbacks, if this PromiseEx
is already resolved / rejected.

</details>

- **resolve**: `{ (): PromiseEx<void>; <T>(input: T): PromiseEx<Awaited<T>>; }` โ€” Creates a new resolved promise.




Creates a new resolved promise for the provided value.

- **reject**: `<T = never>(reason?: any) => PromiseEx<T>` โ€” Creates a new rejected promise for the provided reason.

<br/>

### `new PromisePendingError(cause)`

- **cause**: `Promise<any>`

Could be thrown from `.value` and `.wait(timeout)` of PromiseEx

<details><summary>
<em>๐Ÿ“– show members of <code>PromisePendingError</code> &raquo;</em>
</summary>

#### `PromisePendingError # cause`

- *type*: `Promise<any>`

</details>

<br/>

### `type ImperativePromiseEx<T>`

```ts
export type ImperativePromiseEx<T> = PromiseEx<Awaited<T>> & {
resolve(result: T | PromiseLike<T>): void
reject(reason?: any): void
}
```

<br/>

## ๐Ÿงฉ `promise/swappablePromise`

### `interface SwappablePromiseExecutor`

- **signal**: `AbortSignal` โ€” triggered when current async process is replaced by another `run()`

- **isCurrent**: `() => boolean` โ€” whether current process is the newest pending Promise. will become `false` when resolved / rejected / "replaced by other Promise"

- **throwIfNotCurrent**: `() => void` โ€” throw if current process is not the newest pending Promise

<br/>

### `new SwappablePromise<T>()`

A swappable promise that allows dynamically changing the underlying promise being waited upon.

Useful in avoiding race conditions for UI updates.

This "SwappablePromise" will wait for the newest promise to resolve.
You can use `run(fn)` or `swap(promise)` to change the underlying promise.

example use cases:

- only display latest request's result, for multiple requests
- allow multiple submits, and old requests have "rollback" mechanism (use `run()` to get context and the signal)

<details><summary>
<em>๐Ÿ“– show members of <code>SwappablePromise</code> &raquo;</em>
</summary>

#### `SwappablePromise # wait(timeout?)`

- **timeout?**: `number | undefined`

- *returns*: `Promise<T>`

wait for the underlying Promise resolved. the waiting target may be swapped, before resolved or rejected.

if no underlying Promise yet, will keep waiting.

#### `SwappablePromise # run(executor)`

- **executor**: `(ctx: SwappablePromiseExecutor) => T | Promise<T>`

- *returns*: `void`

swap to a new async process, and pivot `wait()` & `then()` to wait for it

#### `SwappablePromise # swap(promise)`

- **promise**: `T | Promise<T>`

- *returns*: `void`

swap to a new Promise. just alias of `run(() => promise)`

#### `SwappablePromise # reset()`

- *returns*: `void`

reset to "pending" status

if someone is waiting, it will keep waiting, NOT aborted.

#### `SwappablePromise # hasTarget()`

- *returns*: `boolean`

whether a underlying Promise is set and pending

#### `SwappablePromise # isPending()`

- *returns*: `boolean`

whether is pending

#### `SwappablePromise # isFulfilled()`

- *returns*: `boolean`

whether is fulfilled

#### `SwappablePromise # isRejected()`

- *returns*: `boolean`

whether is rejected

#### `SwappablePromise # then(onfulfilled?, onrejected?)`

- **onfulfilled?**: `((value: T) => TResult1 | PromiseLike<TResult1>) | null | undefined`

- **onrejected?**: `((reason: any) => TResult2 | PromiseLike<TResult2>) | null | undefined`

- *returns*: `Promise<TResult1 | TResult2>`

wait for the underlying promise to resolve. the waiting target may be swapped, before resolved or rejected.

if no underlying Promise yet, will keep waiting.

</details>

<br/>

## ๐Ÿงฉ `string/string`

### `stringHash(str)`

- **str**: `string`

- *returns*: `number`

Quickly compute string hash with [cyrb53 algorithm](https://github.com/bryc/code/blob/master/jshash/experimental/cyrb53.js)

<br/>

### `getVariableName(basicName, existingVariables?)`

- **basicName**: `string`

- **existingVariables?**: `CollectionOf<string> | undefined`

- *returns*: `string`

input anything weird, get a valid variable name

optionally, you can give a `existingVariables` to avoid conflicting -- the new name might have a numeric suffix

#### Example

```js
getVariableName('foo-bar') // -> "fooBar"
getVariableName('123abc') // -> "_123abc"
getVariableName('') // -> "foobar"
getVariableName('name', ['name', 'age']) // -> "name2"
```

<br/>

### `bracket(text1, text2, brackets?)`

- **text1?**: `string | number | null | undefined`

- **text2?**: `string | number | null | undefined`

- **brackets?**: `string | [string, string] | undefined` โ€” defaults to `[" (", ")"]`

- *returns*: `string`

Add bracket (parenthesis) to text

- `bracket("c_name", "Column Name")` => `"c_name (Column Name)"`
- `bracket("Column Name", "c_name")` => `"Column Name (c_name)"`

If one parameter is empty, it returns the other one:

- `bracket("c_name", null)` => `"c_name"`
- `bracket(null, "c_name")` => `"c_name"`

<br/>

### `randomStr(size?, dict?)`

- **size?**: `number`

- **dict?**: `string`

- *returns*: `string`

Generate a random string

<br/>

## ๐Ÿงฉ `value/compare`

### `shallowEqual(objA, objB, depth?)`

- **objA**: `any`

- **objB**: `any`

- **depth?**: `number` โ€” defaults to 1

- *returns*: `boolean`

<br/>

## ๐Ÿงฉ `value/iterable`

### `toArray(value)`

- **value?**: `OneOrMany<T>`

- *returns*: `NonNullable<T>[]`

Input anything, always return an array.

- If the input is a single value that is not an array, wrap it as a new array.
- If the input is already an array, it returns a shallow copy.
- If the input is an iterator, it is equivalent to using `Array.from()` to process it.

Finally before returning, all `null` and `undefined` will be omitted

<br/>

### `find(iterator, predicate)`

- **iterator?**: `Nil | Iterable<T>`

- **predicate**: `Predicate<T>`

- *returns*: `T | undefined`

Like `Array#find`, but the input could be a Iterator (for example, from generator, `Set` or `Map`)

<br/>

### `reduce(iterator, initial, reducer)`

- **iterator?**: `Nil | Iterable<T>`

- **initial**: `U`

- **reducer**: `(agg: U, item: T, index: number) => U`

- *returns*: `U`

Like `Array#reduce`, but the input could be a Iterator (for example, from generator, `Set` or `Map`)

<br/>

### `head(iterator)`

- **iterator?**: `Nil | Iterable<T>`

- *returns*: `T | undefined`

Take the first result from a Iterator

<br/>

### `contains(collection, item)`

- **collection?**: `Nil | CollectionOf<T>`

- **item**: `T`

- *returns*: `boolean`

input an array / Set / Map / WeakSet / WeakMap / object etc, check if it contains the `item`

<br/>

### `forEach(objOrArray, iter)`

- **objOrArray**: `U`

- **iter**: `(value: U[keyof U], key: keyof U, whole: U) => void`

- *returns*: `void`

a simple forEach iterator that support both `Array | Set | Map | Object | Iterable` as the input

when met plain object, it works like `forIn` of lodash.

<br/>

### `type OneOrMany<T>`

```ts
export type OneOrMany<T> = Iterable<T | Nil> | T | Nil
```

<br/>

### `type Predicate<T>`

```ts
export type Predicate<T> = (value: T, index: number) => boolean;
```

<br/>

### `type CollectionOf<T>`

```ts
export type CollectionOf<T> =
| ReadonlyArray<T>
| Set<T> | Map<T, any>
| (T extends object ? WeakMap<T, any> | WeakSet<T> : never)
| (T extends string ? Record<T, any> : never)
```

<br/>

### `type IterItem<T>`

```ts
export type IterItem<T> = T extends Iterable<infer U> ? U : never;
```

<br/>

## ๐Ÿงฉ `value/types`

### `isNil(obj)`

- **obj**: `any`

- *returns*: `boolean`

Tell if `obj` is null or undefined

<br/>

### `isObject(obj)`

- **obj**: `any`

- *returns*: `false | "array" | "object"`

Tell if `obj` is Array, Object or other(`false`)

<br/>

### `isThenable(sth)`

- **sth**: `any`

- *returns*: `boolean`

<br/>

### `type MaybePromise<T>`

```ts
export type MaybePromise<T> = Promise<T> | T
```

<br/>

### `type Falsy`

TypeScript type presents all falsy value, including Nil, false, 0, ""

```ts
export type Falsy = null | undefined | false | 0 | "";
```

<br/>

### `type Nil`

TypeScript type presents null or undefined

```ts
export type Nil = null | undefined;
```

<br/>

### `type Fn<RET, ARGS>`

TypeScript type presents any function

```ts
export type Fn<RET = any, ARGS extends any[] = any[]> = (...args: ARGS) => RET;
```

<br/>

<!-- auto generate end -->