{"id":42415265,"url":"https://github.com/lyonbot/yon-utils","last_synced_at":"2026-01-28T01:58:53.459Z","repository":{"id":65839295,"uuid":"601008558","full_name":"lyonbot/yon-utils","owner":"lyonbot","description":"Some utils that I repeated too many times. DRY!","archived":false,"fork":false,"pushed_at":"2025-07-24T19:56:38.000Z","size":203,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-09-11T08:48:24.525Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lyonbot.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-02-13T07:00:56.000Z","updated_at":"2025-07-24T19:56:42.000Z","dependencies_parsed_at":"2023-04-13T08:03:51.707Z","dependency_job_id":"50d4d80f-951f-4e0a-983e-e493a774d3c6","html_url":"https://github.com/lyonbot/yon-utils","commit_stats":{"total_commits":10,"total_committers":2,"mean_commits":5.0,"dds":"0.19999999999999996","last_synced_commit":"db514c6f33bfbae03fe5dcc7cf1170f07e1ca455"},"previous_names":[],"tags_count":20,"template":false,"template_full_name":null,"purl":"pkg:github/lyonbot/yon-utils","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lyonbot%2Fyon-utils","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lyonbot%2Fyon-utils/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lyonbot%2Fyon-utils/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lyonbot%2Fyon-utils/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lyonbot","download_url":"https://codeload.github.com/lyonbot/yon-utils/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lyonbot%2Fyon-utils/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28833234,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-27T23:29:49.665Z","status":"ssl_error","status_checked_at":"2026-01-27T23:25:58.379Z","response_time":168,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-01-28T01:58:53.404Z","updated_at":"2026-01-28T01:58:53.452Z","avatar_url":"https://github.com/lyonbot.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# yon-utils\n\nSome utils and remix that I repeated in many projects.\n\nThis package includes some light-weight alternatives to packages like:\n\nour | is alternative to / remix of\n------- | ----------------- \n[elt](#fn-elt) / [clsx](#fn-clsx) | clsx, classnames, h, hyperscript\n[maybeAsync](#fn-maybeAsync) / [makePromise](#fn-makePromise) / [PromiseEx](#fn-PromiseEx) | imperative-promise, bluebird\n[stringHash](#fn-stringHash) | cyrb53, murmurhash ...\n\u0026lt;some lodash-like functions\u003e | lodash\n\nThere are also some interesting original utils like [shallowEqual](#fn-shallowEqual) / [newFunction](#fn-newFunction) / [toArray](#fn-toArray) / [getVariableName](#fn-getVariableName) etc. Feel free to explore!\n\n## QuickStart\n\n[Play in CodeSandbox](https://codesandbox.io/s/yon-utils-playground-xwh4qt)\n\nAll modules are shipped as ES modules and tree-shakable.\n\n- via package manager\n\n  `npm install yon-utils`\n\n- via import within `\u003cscript type=\"module\"\u003e`\n\n  `import { elt } from \"https://unpkg.com/yon-utils\"`\n\n\u003c!-- auto generate begin --\u003e\n\n\n## ToC\n\n| category | exports |\n| --- | ------- |\n| dom | **methods**: [clsx](#clsxargs) / [elt](#elttagname-attrs-children) |\n| execute | **methods**: [newFunction](#newfunctionargumentnames-functionbody-options) / [noop](#noop) / [retry](#retryfn-opts) |\n| flow | **methods**: [fnQueue](#fnqueue) / [makeAsyncIterator](#makeasynciterator) / [makeEffect](#makeeffectfn-isequal) / [withDefer](#withdeferfn) / [withAsyncDefer](#withasyncdeferfn) / [createWorkerHandler](#createworkerhandlermethods) / [createWorkerDispatcher](#createworkerdispatcherpostmessage) |\n| interaction | **methods**: [writeClipboard](#writeclipboardtext) / [readClipboard](#readclipboardtimeout) / [modKey](#modkeyev) / [startMouseMove](#startmousemove-initialevent-onmove-onend-) \u003cbr /\u003e **vars**: [IS_MAC](#is_mac) / [MOD_KEY](#mod_key) / [MOD_KEY_LABEL](#mod_key_label) \u003cbr /\u003e **types**: [KeyboardEventLike](#interface-keyboardeventlike) / [MouseMoveInfo](#interface-mousemoveinfo) / [MouseMoveInitOptions](#interface-mousemoveinitoptions) |\n| manager | **methods**: [getSearchMatcher](#getsearchmatcherkeyword) \u003cbr /\u003e **classes**: [ModuleLoader](#new-moduleloadertsource) / [CircularDependencyError](#new-circulardependencyerrorquery-querystack) \u003cbr /\u003e **types**: [ModuleLoaderCache](#type-moduleloadercachet) / [ModuleLoaderSource](#interface-moduleloadersourcet) |\n| 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) \u003cbr /\u003e **types**: [RectLike](#type-rectlike) / [RectLikeXYWH](#interface-rectlikexywh) / [RectLikeLTWH](#interface-rectlikeltwh) |\n| promise | **methods**: [delay](#delaymilliseconds) / [debouncePromise](#debouncepromisefn) / [maybeAsync](#maybeasyncinput) / [makePromise](#makepromise) \u003cbr /\u003e **classes**: [PromiseEx](#new-promiseextexecutor) / [PromisePendingError](#new-promisependingerrorcause) / [SwappablePromise](#new-swappablepromiset) \u003cbr /\u003e **types**: [ImperativePromiseEx](#type-imperativepromiseext) / [SwappablePromiseExecutor](#interface-swappablepromiseexecutor) |\n| string | **methods**: [stringHash](#stringhashstr) / [getVariableName](#getvariablenamebasicname-existingvariables) / [bracket](#brackettext1-text2-brackets) / [randomStr](#randomstrsize-dict) |\n| 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) \u003cbr /\u003e **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) |\n\n## 🧩 `dom/clsx`\n\n### `clsx(...args)`\n\n- **...args**: `any[]`\n\n- *returns*: `string`\n\nconstruct className strings conditionally.\n\ncan 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)\n\n\n\u003cbr/\u003e\n\n## 🧩 `dom/elt`\n\n### `elt(tagName, attrs, ...children)`\n\n- **tagName**: `string` — for example `\"div\"` or `\"button.my-btn\"`\n\n- **attrs**: `any` — attribute values to be set. beware:\n  - `onClick` and a `function` value, will be handled by `addEventListener()`\n  - `!onClick` or `onClick.capture` will make it capture\n  - `style` value could be a string or object\n  - `class` value could be a string, object or array, and will be process by `clsx()`\n  - `className` is alias of `class`\n\n- **...children**: `any[]` — can be strings, numbers, nodes. other types or nils will be omitted.\n\n- *returns*: `HTMLElement`\n\nMake `document.createElement` easier\n\n```js\nvar button = elt(\n  'button.myButton',   // tagName, optionally support .className and #id\n  {\n    title: \"a magic button\",\n    class: { isPrimary: xxx.xxx }, // className will be processed by clsx\n    onclick: () =\u003e alert('hi')\n  }, \n  'Click Me!'\n)\n```\n\nThis 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/).\nYou can add \u003ccode\u003e/** \u0026#64;jsx elt *\u0026#47;\u003c/code\u003e into your code, then TypeScript / Babel will use `elt` to process JSX expressions:\n\n\u003e /** \u0026#64;jsx elt *\u0026#47;\n\u003e\n\u003e var button = \u0026lt;button class=\"myButton\" onclick={...}\u003eClick Me\u0026lt;/button\u003e\u003c/code\u003e\u003c/pre\u003e\n\n\n\u003cbr/\u003e\n\n## 🧩 `execute/function`\n\n### `newFunction(argumentNames, functionBody, options?)`\n\n- **argumentNames**: `NameArray\u003cARGS\u003e` — a `string[]` of argument names\n\n- **functionBody**: `string` — the function body\n\n- **options?**: `{ async? }`\n\n  - **async?**: `boolean | undefined` — set to `true` if the code contains `await`, the new function will be an async function\n\n- *returns*: `Fn\u003cRESULT, ARGS\u003e`\n\nlike `new Function` but with more reasonable options and api\n\n\n\u003cbr/\u003e\n\n### `noop()`\n\n- *returns*: `void`\n\nThe nothing-to-do function\n\n\n\u003cbr/\u003e\n\n## 🧩 `execute/retry`\n\n### `retry(fn, opts?)`\n\n- **fn**: `() =\u003e T | Nil | Promise\u003cT | Nil\u003e`\n\n- **opts?**: `{ duration?, interval?, signal? }`\n\n  - **duration?**: `number | undefined`\n  \n  - **interval?**: `number | undefined`\n  \n  - **signal?**: `AbortSignal | undefined`\n\n- *returns*: `Promise\u003cT\u003e`\n\na simple retry function for Promise-returning functions.\n\nif `fn` resolves with `null | undefined` or throw Error, it will retry.\n\nno exponential backoff, just retry periodically till timeout or success.\n\n\n\u003cbr/\u003e\n\n## 🧩 `flow/fnQueue`\n\n### `fnQueue()`\n\n- *returns*: `{ tap, tapSilent, call, queue }`\n\n  - **tap**: `AddCallbacks\u003cArgs\u003e` — add one or more functions.\n  \n  - **tapSilent**: `AddCallbacks\u003cArgs\u003e` — add functions, and will silently ignore their errors\n  \n  - **call**: `(...args: Args) =\u003e void` — run functions. if fnQueue is async, returns Promise\n  \n  - **queue**: `{ silent?: boolean | undefined; fn: Fn\u003cany, Args\u003e; }[]` — the queued functions\n\nStore a list of functions, and execute them in order.\n\n- **Use case**: 🧹 disposer (clean resources) / ⚡ event emitter / 🪢 tapable-like middleware\n- **Defaults**: sync, FIFO, errors will abort\n\nUse decorators or options, to customize a fnQueue:\n\n- `fnQueue.async()` to create async queue -- the `call()` will return a Promise instead.\n- `fnQueue.filo()` to create FILO queue.\n- `fnQueue.onetime()` to clear the queue after each call.\n- `fnQueue({ error: 'ignore' })` to ignore errors.\n\nOptions can be combined, like `fnQueue.async.onetime()` -- see example below.\n\n#### Example\n\n```js\n// create an async fnQueue with options ...\nconst disposer = fnQueue.async.onetime({ error: 'ignore' });\n\ntry {\n  const srcFile = await openFile(path1);\n  disposer.tap(() =\u003e srcFile.close());\n\n  const dstFile = await openFile(path2);\n  disposer.tap(() =\u003e dstFile.close());\n\n  await copyData(srcFile, dstFile);\n} finally {\n  await disposer.call();\n}\n```\n\n- **async**: `Factory\u003cPromise\u003cvoid\u003e\u003e`\n\n  - **async**: `Factory\u003cPromise\u003cvoid\u003e\u003e`\n  \n  - **filo**: `Factory\u003cPromise\u003cvoid\u003e\u003e` — change execution order to FILO (first-in, last-out, like a stack)\n  \n  - **onetime**: `Factory\u003cPromise\u003cvoid\u003e\u003e` — after each call, clear the queue\n\n- **filo**: `Factory\u003cvoid\u003e` — change execution order to FILO (first-in, last-out, like a stack)\n\n  - **async**: `Factory\u003cPromise\u003cvoid\u003e\u003e`\n  \n  - **filo**: `Factory\u003cvoid\u003e` — change execution order to FILO (first-in, last-out, like a stack)\n  \n  - **onetime**: `Factory\u003cvoid\u003e` — after each call, clear the queue\n\n- **onetime**: `Factory\u003cvoid\u003e` — after each call, clear the queue\n\n  - **async**: `Factory\u003cPromise\u003cvoid\u003e\u003e`\n  \n  - **filo**: `Factory\u003cvoid\u003e` — change execution order to FILO (first-in, last-out, like a stack)\n  \n  - **onetime**: `Factory\u003cvoid\u003e` — after each call, clear the queue\n\n\n\u003cbr/\u003e\n\n## 🧩 `flow/makeAsyncIterator`\n\n### `makeAsyncIterator()`\n\n- *returns*: `{ write(value: T): void; end(error?: any): void; } \u0026 AsyncIterableIterator\u003cT\u003e`\n\n  - **write**: `(value: T) =\u003e void`\n  \n  - **end**: `(error?: any) =\u003e void`\n\nHelp you convert a callback-style stream into an async iterator. Also works on \"observable\" value like RxJS.\n\nYou can think of this as a simplified `new Readable({ ... })` without headache.\n\n#### Example\n\n```js\nconst iterator = makeAsyncIterator();\n\nsocket.on('data', value =\u003e iterator.write(value));\nsocket.on('end', () =\u003e iterator.end());\nsocket.on('error', (err) =\u003e iterator.end(err));\n\nfor await (const line of iterator) {\n  console.log(line);\n}\n```\n\n\n\u003cbr/\u003e\n\n## 🧩 `flow/makeEffect`\n\n### `makeEffect(fn, isEqual?)`\n\n- **fn**: `(input: T, previous: T | undefined) =\u003e void | (() =\u003e void)`\n\n- **isEqual?**: `(x: T, y: T) =\u003e boolean`\n\n- *returns*: `{ cleanup, value }`\n\n  - **cleanup**: `() =\u003e void` — invoke last cleanup function, and reset `value` to undefined\n  \n  - **value?**: `T | undefined` — get last received value, or `undefined` if effect was clean up\n\nWrap `fn` and create an unary function. The actual `fn()` executes only when the argument changes.\n\nMeanwhile, your `fn` may return a cleanup function, which will be invoked before new `fn()` calls\n-- just like React's `useEffect`\n\nThe new unary function also provide `cleanup()` method to forcedly do the cleanup, which will also clean the memory of last input.\n\n#### Example\n\n```js\nconst sayHi = makeEffect((name) =\u003e {\n  console.log(`Hello, ${name}`);\n  return () =\u003e {\n    console.log(`Goodbye, ${name}`);\n  }\n});\n\nsayHi('Alice');  // output: Hello, Alice\nsayHi('Alice');  // no output\nsayHi('Bob');    // output: Goodbye, Alice   Hello, Bob\nsayHi.cleanup(); // output: Goodbye, Bob\nsayHi.cleanup(); // no output\n```\n\n\n\u003cbr/\u003e\n\n## 🧩 `flow/withDefer`\n\n### `withDefer(fn)`\n\n- **fn**: `(defer: AddCallbacks\u003c[]\u003e) =\u003e Ret`\n\n- *returns*: `Ret`\n\nGet rid of `try catch finally` hells!\nUse `defer(callback)` to clean up resources, and they will run in `finally` stage.\n\nWorks on both sync and async procedures.\n\nFor sync functions:\n\n```js\n// sync\nconst result = withDefer((defer) =\u003e {\n  const file = openFileSync('xxx')\n  defer(() =\u003e closeFileSync(file))  // \u003c- register callback\n\n  const parser = createParser()\n  defer(() =\u003e parser.dispose())  // \u003c- register callback\n\n  return parser.parse(file.readSync())\n})\n```\n\nFor async functions, use `withAsyncDefer`\n\n```js\n// async\nconst result = await withAsyncDefer(async (defer) =\u003e {\n  const file = await openFile('xxx')\n  defer(async () =\u003e await closeFile(file))  // \u003c- defer function can be async now!\n\n  const parser = createParser()\n  defer(() =\u003e parser.dispose())  // \u003c-\n\n  return parser.parse(await file.read())\n})\n```\n\n**Error handling**\n\nIf one callback throws, rest callbacks still work. And you get the last error thrown.\n\nTo suppress a callback's throwing, use `defer.silent(callback)`\n\n```js\ndefer.silent(() =\u003e closeFile(file))  // will never throws\n```\n\n#### Remark\n\nRefer to [TypeScript using syntax](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-2.html#using-declarations-and-explicit-resource-management),\n[TC39 Explicit Resource Management](https://github.com/tc39/proposal-explicit-resource-management) and GoLang's `defer` keyword.\n\n\n\u003cbr/\u003e\n\n### `withAsyncDefer(fn)`\n\n- **fn**: `(defer: AddCallbacks\u003c[]\u003e) =\u003e Ret`\n\n- *returns*: `Ret`\n\nSame as **withDefer**, but this returns a Promise, and supports async callbacks.\n\n\n\u003cbr/\u003e\n\n## 🧩 `flow/worker-rpc`\n\n### `createWorkerHandler(methods)`\n\n- **methods**: `T` — Object containing method implementations that can be called remotely\n\n- *returns*: `(payload: RpcPayload) =\u003e void` — A handler function that processes incoming RPC payloads\n\nCreates a handler function for processing RPC requests in a Worker.\n\n#### Example\n\n```js\n// In your worker file:\nimport { createWorkerHandler } from './worker-rpc';\n\nconst handler = createWorkerHandler({\n  async getData(id) {\n    // implementation\n    return result;\n  }\n});\n\nself.onmessage = (e) =\u003e { \n  if (e.data?.type === 'myCall') handler(e.data.payload);\n}\n```\n\n\n\u003cbr/\u003e\n\n### `createWorkerDispatcher(postMessage)`\n\n- **postMessage**: `(payload: RpcPayload, transferable: Transferable[]) =\u003e void` — Function that sends messages to the worker (typically worker.postMessage)\n\n- *returns*: `T` — A proxy object where each property access creates a function that calls the corresponding method in the worker\n\nCreates a proxy object that dispatches method calls to a Worker via RPC.\n\n#### Example\n\n```js\n// In your main thread:\nimport { createWorkerDispatcher } from './worker-rpc';\n\nconst worker = new Worker('path/to/worker.js');\nconst api = createWorkerDispatcher\u003cMyWorkerAPI\u003e((payload, transferable) =\u003e \n  worker.postMessage({\n    type: 'myCall',\n    payload,\n  }, transferable)\n);\n\n// Now you can call worker methods as if they were local:\n// No need to worry about initiating, it automatically wait for the worker to be ready\nconst result = await api.getData(123);\n```\n\n\n\u003cbr/\u003e\n\n## 🧩 `interaction/clipboard`\n\n### `writeClipboard(text)`\n\n- **text**: `string`\n\n- *returns*: `Promise\u003cvoid\u003e`\n\nwrite text to clipboard, with support for insecure context and legacy browser!\n\nnote: if you are in HTTPS and modern browser, you can directly use `navigator.clipboard.writeText()` instead.\n\n\n\u003cbr/\u003e\n\n### `readClipboard(timeout?)`\n\n- **timeout?**: `number` — default 1500\n\n- *returns*: `Promise\u003cstring\u003e`\n\nread clipboard text.\n\nif user rejects or hesitates about the permission for too long,\nthis will throw an Error.\n\n\n\u003cbr/\u003e\n\n## 🧩 `interaction/keyboard`\n\n### `modKey(ev)`\n\n- **ev**: `KeyboardEventLike`\n\n- *returns*: `number`\n\nget Modifier Key status from a Event\n\n#### Remark\n\n1. use `modKey.Mod` to indicate if the key is `⌘`(Cmd) on Mac, or `Ctrl` on Windows/Linux\n2. use `|` (or operator) to combine modifier keys. see example below.\n\n#### Example\n\n```js\nif (modKey(ev) === (modKey.Mod | modKey.Shift) \u0026\u0026 ev.code === 'KeyW') {\n  // Ctrl/Cmd + Shift + W, depends on the OS\n}\n```\n\n- **None**: `0`\n\n- **Ctrl**: `1`\n\n- **Cmd**: `2`\n\n- **Shift**: `4`\n\n- **Alt**: `8`\n\n- **Mod**: `1 | 2` — equals to `Ctrl` or `Cmd`, depending on the OS\n\n\n\u003cbr/\u003e\n\n### `IS_MAC`\n\n```ts\nboolean\n```\n\n\n\u003cbr/\u003e\n\n### `MOD_KEY`\n\nthe proper way to get modKey status from KeyboardEvent\n\n```ts\n\"metaKey\" | \"ctrlKey\"\n```\n\n\n\u003cbr/\u003e\n\n### `MOD_KEY_LABEL`\n\nthe proper label of modKey. eg: `⌘` for Mac, `Ctrl` for Windows/Linux\n\n```ts\n\"⌘\" | \"Ctrl\"\n```\n\n\n\u003cbr/\u003e\n\n### `interface KeyboardEventLike`\n\n- **ctrlKey?**: `boolean | undefined`\n\n- **metaKey?**: `boolean | undefined`\n\n- **shiftKey?**: `boolean | undefined`\n\n- **altKey?**: `boolean | undefined`\n\n\n\u003cbr/\u003e\n\n## 🧩 `interaction/mouseMove`\n\n### `startMouseMove({ initialEvent, onMove, onEnd })`\n\n- **__0**: `MouseMoveInitOptions`\n\n- *returns*: `Promise\u003cMouseMoveInfo\u003e` — a Promise with final position when user releases button\n\nPowerful tool to implement any drag / resize action easily!\n\nWhile dragging, it tracks the cursor's movement and reports to `onMove(...)`.\nAnd when user releases the button, it will call `onEnd(...)`.\n\n1. the element needs `touch-action: none` style to prevent scrolling on mobile\n\n2. use within `pointerdown` event\n\n3. `event.preventDefault()` is necessary too\n\n#### Example\n\n```js\nbutton.style.touchAction = 'none' // CSS touch-action: none\nbutton.addEventListener('pointerdown', event =\u003e {\n  event.preventDefault();\n  startMouseMove({\n    initialEvent: event,\n    onMove({ deltaX, deltaY }) { ... },\n    onEnd({ deltaX, deltaY }) { ... },\n  });\n});\n```\n\n\n\u003cbr/\u003e\n\n### `interface MouseMoveInfo`\n\n- **clientX**: `number`\n\n- **clientY**: `number`\n\n- **deltaX**: `number`\n\n- **deltaY**: `number`\n\n- **duration**: `number` — in milliseconds, since `startMouseMove` called\n\n- **event**: `MouseEvent | PointerEvent`\n\n- **pointerId**: `number | false`\n\n- **cancelled?**: `boolean | undefined`\n\n\n\u003cbr/\u003e\n\n### `interface MouseMoveInitOptions`\n\n- **initialEvent**: `MouseEvent | PointerEvent`\n\n- **onMove?**: `((data: MouseMoveInfo) =\u003e void) | undefined`\n\n- **onEnd?**: `((data: MouseMoveInfo) =\u003e void) | undefined`\n\n\n\u003cbr/\u003e\n\n## 🧩 `manager/moduleLoader`\n\n### `new ModuleLoader\u003cT\u003e(source)`\n\n- **source**: `ModuleLoaderSource\u003cT\u003e`\n\nAll-in-one ModuleLoader, support both sync and async mode, can handle circular dependency problem.\n\n### Example in Sync\n\n```js\nconst loader = new ModuleLoader({\n  // sync example\n  resolve(query, { load }) {\n    if (query === 'father') return 'John'\n    if (query === 'mother') return 'Mary'\n\n    // simple alias: just `return load('xxx')`\n    if (query === 'mom') return load('mother')\n\n    // load dependency\n    // - `load('xxx').value` for sync, don't forget .value\n    // - `await load('xxx')` for async\n    if (query === 'family') return `${load('father').value} and ${load('mother').value}`\n\n    // always return something as fallback\n    return 'bad query'\n  }\n})\n\nconsole.log(loader.load('family').value)  // don't forget .value\n```\n\n### Example in Async\n\n```js\nconst loader = new ModuleLoader({\n  // async example\n  async resolve(query, { load }) {\n    if (query === 'father') return 'John'\n    if (query === 'mother') return 'Mary'\n\n    // simple alias: just `return load('xxx')`\n    if (query === 'mom') return load('mother')\n\n    // load dependency\n    // - `await load('xxx')` for async\n    // - no need `.value` in async mode\n    if (query === 'family') return `${await load('father')} and ${await load('mother')}`\n\n    // always return something as fallback\n    return 'bad query'\n  }\n})\n\nconsole.log(await loader.load('family'))  // no need `.value` with `await`\n```\n\n\u003cdetails\u003e\u003csummary\u003e\n\u003cem\u003e📖 show members of \u003ccode\u003eModuleLoader\u003c/code\u003e \u0026raquo;\u003c/em\u003e\n\u003c/summary\u003e\n\n#### `ModuleLoader # cache`\n\n- *type*: `ModuleLoaderCache\u003c{ dependencies?: string[] | undefined; promise: PromiseEx\u003cT\u003e; }\u003e`\n\n\n\n#### `ModuleLoader # load(query)`\n\n- **query**: `string`\n\n- *returns*: `PromiseEx\u003cT\u003e`\n\nfetch a module\n\n\n\n#### `ModuleLoader # getDependencies(query, deep?)`\n\n- **query**: `string`\n\n- **deep?**: `boolean`\n\n- *returns*: `PromiseEx\u003cstring[]\u003e`\n\nget all direct dependencies of a module.\n\nnote: to get reliable result, this will completely load the module and deep dependencies.\n\n\n\n\u003c/details\u003e\n\n\n\u003cbr/\u003e\n\n### `new CircularDependencyError(query, queryStack)`\n\n- **query**: `string`\n\n- **queryStack**: `string[]`\n\nThe circular dependency Error that `ModuleLoader` might throw.\n\n\u003cdetails\u003e\u003csummary\u003e\n\u003cem\u003e📖 show members of \u003ccode\u003eCircularDependencyError\u003c/code\u003e \u0026raquo;\u003c/em\u003e\n\u003c/summary\u003e\n\n#### `CircularDependencyError # query`\n\n- *type*: `string`\n\nthe module that trying to be loaded.\n\n\n\n#### `CircularDependencyError # queryStack`\n\n- *type*: `string[]`\n\nthe stack to traceback the loading progress.\n\n\n\n#### `CircularDependencyError # name`\n\n- *type*: `string`\n\nalways `'CircularDependencyError'`\n\n\n\n\u003c/details\u003e\n\n\n\u003cbr/\u003e\n\n### `type ModuleLoaderCache\u003cT\u003e`\n\nused by ModuleLoader\n\n- **get**: `(query: string) =\u003e T | undefined`\n\n- **set**: `(query: string, value: T) =\u003e any`\n\n- **delete**: `(query: string) =\u003e any`\n\n- **clear**: `() =\u003e void`\n\n\n\u003cbr/\u003e\n\n### `interface ModuleLoaderSource\u003cT\u003e`\n\nused by ModuleLoader\n\n- **resolve**: `(query: string, ctx: { load(target: string): PromiseEx\u003cT\u003e; noCache\u003cT\u003e(value: T): T; }) =\u003e MaybePromise\u003cT\u003e` — You must implement a loader function. It parse `query` and returns the module content.\n  \n  1. It could be synchronous or asynchronous, depends on your scenario.\n  2. You can use `load()` from `ctx` to load dependencies. Example: `await load(\"common\")` or `load(\"common\").value`\n  3. All queries are cached by default. To bypass it, use `ctx.noCache`. Example: `return noCache(\"404: not found\")`\n\n- **cache?**: `ModuleLoaderCache\u003cany\u003e | undefined`\n\n\n\u003cbr/\u003e\n\n## 🧩 `manager/simpleSearch`\n\n### `getSearchMatcher(keyword)`\n\n- **keyword**: `string`\n\n- *returns*: `{ test, filter, filterEx }`\n\n  - **test**: `(record: any) =\u003e number` — test one record and tell if it matches.\n    \n    the `record` could be a string, array and object(only values will be tested).\n    \n    will return `0` for not matched, `1` for fuzzy matched, `\u003e 1` for partially accurately matched\n  \n  - **filter**: `FilterFunction` — filter a list / collection, and get the sorted search result.\n    \n    returns a similarity-sorted array of matched values.\n    \n    also see `filterEx` if want more information\n  \n  - **filterEx**: `FilterExFunction` — filter a list / collection, and get the sorted search result with extra information.\n    \n    returns a similarity-sorted array of `{ value, score, index, key }`.\n    \n    also see `filter` if you just want the values.\n\nSimple utility to start searching\n\n#### Example\n\n```js\n// note: items can be object / array / array of objects ...\nconst items = ['Alice', 'Lichee', 'Bob'];\n\nconst result = getSearchMatcher('lic').filter(items);\n// -\u003e ['Lichee', 'Alice']\n```\n\n\n\u003cbr/\u003e\n\n## 🧩 `math/geometry`\n\n### `isInsideRect(x, y, rect)`\n\n- **x**: `number` — The x-coordinate of the point.\n\n- **y**: `number` — The y-coordinate of the point.\n\n- **rect**: `RectLike` — The rectangle to check against.\n\n- *returns*: `boolean`\n\nDetermines whether a point (x, y) is inside a rectangle.\n\n\n\u003cbr/\u003e\n\n### `isRectEqual(rect1, rect2, epsilon?)`\n\n- **rect1?**: `Nil | RectLike` — The first rectangle to compare.\n\n- **rect2?**: `Nil | RectLike` — The second rectangle to compare.\n\n- **epsilon?**: `number | undefined` — The maximum difference allowed between the values of the rectangles' properties.\n\n- *returns*: `boolean`\n\nDetermines whether two rectangles are equal.\n\n\n\u003cbr/\u003e\n\n### `getRectIntersection(...rects)`\n\n- **...rects**: `(Nil | RectLike)[]` — The rectangles\n\n- *returns*: `RectLike` — The intersection rectangle. Can be passed to `DOMRect.fromRect(.)`\n\nCalculates the intersection of rectangles.\n\n\n\u003cbr/\u003e\n\n### `getRectUnion(...rects)`\n\n- **...rects**: `(Nil | RectLike)[]` — The rectangles\n\n- *returns*: `RectLike` — The union rectangle. Can be passed to `DOMRect.fromRect(.)`\n\nCalculates the union (out bounding box) of rectangles.\n\n\n\u003cbr/\u003e\n\n### `type RectLike`\n\na interface that fits DOMRect and many other situation\n\n```ts\nexport type RectLike = RectLikeXYWH | RectLikeLTWH;\n```\n\n\n\u003cbr/\u003e\n\n### `interface RectLikeXYWH`\n\n- **x**: `number`\n\n- **y**: `number`\n\n- **width**: `number`\n\n- **height**: `number`\n\n\n\u003cbr/\u003e\n\n### `interface RectLikeLTWH`\n\n- **left**: `number`\n\n- **top**: `number`\n\n- **width**: `number`\n\n- **height**: `number`\n\n\n\u003cbr/\u003e\n\n## 🧩 `math/number`\n\n### `approx(a, b, epsilon?)`\n\n- **a**: `number`\n\n- **b**: `number`\n\n- **epsilon?**: `number` — The maximum difference allowed between the two numbers. Defaults to 0.001.\n\n- *returns*: `boolean`\n\nDetermines if two numbers are approximately equal within a given epsilon.\n\n\n\u003cbr/\u003e\n\n### `clamp(n, min, max)`\n\n- **n**: `number`\n\n- **min**: `number`\n\n- **max**: `number`\n\n- *returns*: `number`\n\nClamp a number between min and max\n\n\n\u003cbr/\u003e\n\n### `lerp(min, max, t)`\n\n- **min**: `number`\n\n- **max**: `number`\n\n- **t**: `number` — The interpolation value clamped between 0 and 1\n\n- *returns*: `number`\n\nLinearly interpolate\n\n#### Example\n\n```js\nconst value = lerp(0, 2, 0.5) // value will be 1\n```\n\n\n\u003cbr/\u003e\n\n### `easeOut(t)`\n\n- **t**: `number`\n\n- *returns*: `number`\n\nsimple ease-out function, useful for animation, tween, and fake progress bar\n\n#### Example\n\n```js\n// a fake progress bar never reach 100%\nconst sinceTime = Date.now()\nconst progress = 0.95 * easeOut((Date.now() - sinceTime) / 5000)\n```\n\n\n\u003cbr/\u003e\n\n### `randomNum(min, max)`\n\n- **min**: `number`\n\n- **max**: `number`\n\n- *returns*: `number`\n\nGet a random number from [min, max)\n\n\n\u003cbr/\u003e\n\n## 🧩 `promise/misc`\n\n### `delay(milliseconds)`\n\n- **milliseconds**: `number`\n\n- *returns*: `Promise\u003cvoid\u003e`\n\n\n\u003cbr/\u003e\n\n### `debouncePromise(fn)`\n\n- **fn**: `() =\u003e Promise\u003cT\u003e` — The function to be debounced.\n\n- *returns*: `{ (): Promise\u003cT\u003e; clear(): void; }` — The debounced function.\n\n  - **clear**: `() =\u003e void` — make next call always return new Promise\n\nCreates a debounced version of a function that returns a promise.\n\nThe returned function will ensure that only one Promise is created and executed at a time,\neven if the debounced function is called multiple times before last Promise gets finished.\n\nAll _suppressed_ calls will get the last started Promise.\n\n\n\u003cbr/\u003e\n\n## 🧩 `promise/promise`\n\n### `maybeAsync(input)`\n\n- **input**: `T | Promise\u003cT\u003e | (() =\u003e T | Promise\u003cT\u003e)` — your sync/async function to run, or just a value\n\n- *returns*: `PromiseEx\u003cAwaited\u003cT\u003e\u003e` — a crafted Promise that exposes `{ status, value, reason }`, whose `status` could be `\"pending\" | \"fulfilled\" | \"rejected\"`\n\nRun the function, return a crafted Promise that exposes `status`, `value` and `reason`\n\nIf `input` is sync function, its result will be stored in `promise.value` and `promise.status` will immediately be set as \"fulfilled\"\n\nUseful when you are not sure whether `fn` is async or not.\n\n\n\u003cbr/\u003e\n\n### `makePromise()`\n\n- *returns*: `ImperativePromiseEx\u003cT\u003e`\n\nCreate an imperative Promise.\n\nReturns a Promise with these 2 methods exposed, so you can control its behavior:\n\n- `.resolve(result)`\n- `.reject(error)`\n\nBesides, the returned Promise will expose these useful properties\nso you can get its status easily:\n \n- `.wait([timeout])` — wait for result, if timeout set and exceeded, a `PromisePendingError` will be thrown\n- `.status` — could be `\"pending\" | \"fulfilled\" | \"rejected\"`\n- `.result` and `.reason`\n- `.value` — fail-safe get result (or cause an Error from rejection, or cause a `PromisePendingError` if still pending)\n\n#### Example\n\n```js\nconst handler = makePromise();\n\ndoSomeRequest(..., result =\u003e handler.resolve(result));\n\n// wait with timeout\nconst result = await handler.wait(1000);\n\n// or just await\nconst result = await handler;\n```\n\n\n\u003cbr/\u003e\n\n### `new PromiseEx\u003cT\u003e(executor)`\n\n- **executor**: `(resolve: (value: T | PromiseLike\u003cT\u003e) =\u003e void, reject: (reason?: any) =\u003e void) =\u003e void`\n\na crafted Promise that exposes `{ status, value, reason }`\n\nNote: please use `maybeAsync()` or `PromiseEx.resolve()` to create a PromiseEx\n\n\u003cdetails\u003e\u003csummary\u003e\n\u003cem\u003e📖 show members of \u003ccode\u003ePromiseEx\u003c/code\u003e \u0026raquo;\u003c/em\u003e\n\u003c/summary\u003e\n\n#### `PromiseEx # status`\n\n- *type*: `\"pending\" | \"fulfilled\" | \"rejected\"`\n\n\n\n#### `PromiseEx # reason`\n\n- *type*: `any`\n\nif rejected, get the reason.\n\n\n\n#### `PromiseEx # result`\n\n- *type*: `T | undefined`\n\nget result, or nothing if not fulfilled.\n\nnote: you might need `.value` which follows **fail-fast mentality**\n\n\n\n#### `PromiseEx # loading`\n\n- *type*: `boolean`\n\nequivalent to `.status === \"pending\"`\n\n\n\n#### `PromiseEx # isPending()`\n\n- *returns*: `boolean`\n\n\n\n#### `PromiseEx # isFulfilled()`\n\n- *returns*: `boolean`\n\n\n\n#### `PromiseEx # isRejected()`\n\n- *returns*: `boolean`\n\n\n\n#### `PromiseEx # value`\n\n- *type*: `T | undefined`\n\n**fail-fast mentality**, safely get the result.\n\n- if pending, throw `new PromisePendingError(this)`\n- if rejected, throw `.reason`\n- if fulfilled, get `.result`\n\n\n\n#### `PromiseEx # wait(timeout?)`\n\n- **timeout?**: `number | undefined`\n\n- *returns*: `Promise\u003cT\u003e`\n\nwait for resolved / rejected. \n\noptionally can set a timeout in milliseconds. if timeout, a `PromisePendingError` will be thrown\n\n\n\n#### `PromiseEx # thenImmediately(onfulfilled?, onrejected?)`\n\n- **onfulfilled?**: `Nil | ((value: T) =\u003e TResult1 | PromiseLike\u003cTResult1\u003e)`\n\n- **onrejected?**: `Nil | ((reason: any) =\u003e TResult2 | PromiseLike\u003cTResult2\u003e)`\n\n- *returns*: `PromiseEx\u003cTResult1 | TResult2\u003e`\n\nLike `then()` but immediately invoke callbacks, if this PromiseEx\nis already resolved / rejected.\n\n\n\n\u003c/details\u003e\n\n- **resolve**: `{ (): PromiseEx\u003cvoid\u003e; \u003cT\u003e(input: T): PromiseEx\u003cAwaited\u003cT\u003e\u003e; }` — Creates a new resolved promise.\n  \n  \n  \n  \n  Creates a new resolved promise for the provided value.\n\n- **reject**: `\u003cT = never\u003e(reason?: any) =\u003e PromiseEx\u003cT\u003e` — Creates a new rejected promise for the provided reason.\n\n\n\u003cbr/\u003e\n\n### `new PromisePendingError(cause)`\n\n- **cause**: `Promise\u003cany\u003e`\n\nCould be thrown from `.value` and `.wait(timeout)` of PromiseEx\n\n\u003cdetails\u003e\u003csummary\u003e\n\u003cem\u003e📖 show members of \u003ccode\u003ePromisePendingError\u003c/code\u003e \u0026raquo;\u003c/em\u003e\n\u003c/summary\u003e\n\n#### `PromisePendingError # cause`\n\n- *type*: `Promise\u003cany\u003e`\n\n\n\n\u003c/details\u003e\n\n\n\u003cbr/\u003e\n\n### `type ImperativePromiseEx\u003cT\u003e`\n\n```ts\nexport type ImperativePromiseEx\u003cT\u003e = PromiseEx\u003cAwaited\u003cT\u003e\u003e \u0026 {\n  resolve(result: T | PromiseLike\u003cT\u003e): void\n  reject(reason?: any): void\n}\n```\n\n\n\u003cbr/\u003e\n\n## 🧩 `promise/swappablePromise`\n\n### `interface SwappablePromiseExecutor`\n\n- **signal**: `AbortSignal` — triggered when current async process is replaced by another `run()`\n\n- **isCurrent**: `() =\u003e boolean` — whether current process is the newest pending Promise. will become `false` when resolved / rejected / \"replaced by other Promise\"\n\n- **throwIfNotCurrent**: `() =\u003e void` — throw if current process is not the newest pending Promise\n\n\n\u003cbr/\u003e\n\n### `new SwappablePromise\u003cT\u003e()`\n\nA swappable promise that allows dynamically changing the underlying promise being waited upon.\n\nUseful in avoiding race conditions for UI updates.\n\nThis \"SwappablePromise\" will wait for the newest promise to resolve.\nYou can use `run(fn)` or `swap(promise)` to change the underlying promise.\n\nexample use cases:\n\n- only display latest request's result, for multiple requests\n- allow multiple submits, and old requests have \"rollback\" mechanism (use `run()` to get context and the signal)\n\n\u003cdetails\u003e\u003csummary\u003e\n\u003cem\u003e📖 show members of \u003ccode\u003eSwappablePromise\u003c/code\u003e \u0026raquo;\u003c/em\u003e\n\u003c/summary\u003e\n\n#### `SwappablePromise # wait(timeout?)`\n\n- **timeout?**: `number | undefined`\n\n- *returns*: `Promise\u003cT\u003e`\n\nwait for the underlying Promise resolved. the waiting target may be swapped, before resolved or rejected.\n\nif no underlying Promise yet, will keep waiting.\n\n\n\n#### `SwappablePromise # run(executor)`\n\n- **executor**: `(ctx: SwappablePromiseExecutor) =\u003e T | Promise\u003cT\u003e`\n\n- *returns*: `void`\n\nswap to a new async process, and pivot `wait()` \u0026 `then()` to wait for it\n\n\n\n#### `SwappablePromise # swap(promise)`\n\n- **promise**: `T | Promise\u003cT\u003e`\n\n- *returns*: `void`\n\nswap to a new Promise. just alias of `run(() =\u003e promise)`\n\n\n\n#### `SwappablePromise # reset()`\n\n- *returns*: `void`\n\nreset to \"pending\" status \n\nif someone is waiting, it will keep waiting, NOT aborted.\n\n\n\n#### `SwappablePromise # hasTarget()`\n\n- *returns*: `boolean`\n\nwhether a underlying Promise is set and pending\n\n\n\n#### `SwappablePromise # isPending()`\n\n- *returns*: `boolean`\n\nwhether is pending\n\n\n\n#### `SwappablePromise # isFulfilled()`\n\n- *returns*: `boolean`\n\nwhether is fulfilled\n\n\n\n#### `SwappablePromise # isRejected()`\n\n- *returns*: `boolean`\n\nwhether is rejected\n\n\n\n#### `SwappablePromise # then(onfulfilled?, onrejected?)`\n\n- **onfulfilled?**: `((value: T) =\u003e TResult1 | PromiseLike\u003cTResult1\u003e) | null | undefined`\n\n- **onrejected?**: `((reason: any) =\u003e TResult2 | PromiseLike\u003cTResult2\u003e) | null | undefined`\n\n- *returns*: `Promise\u003cTResult1 | TResult2\u003e`\n\nwait for the underlying promise to resolve. the waiting target may be swapped, before resolved or rejected.\n\nif no underlying Promise yet, will keep waiting.\n\n\n\n\u003c/details\u003e\n\n\n\u003cbr/\u003e\n\n## 🧩 `string/string`\n\n### `stringHash(str)`\n\n- **str**: `string`\n\n- *returns*: `number`\n\nQuickly compute string hash with [cyrb53 algorithm](https://github.com/bryc/code/blob/master/jshash/experimental/cyrb53.js)\n\n\n\u003cbr/\u003e\n\n### `getVariableName(basicName, existingVariables?)`\n\n- **basicName**: `string`\n\n- **existingVariables?**: `CollectionOf\u003cstring\u003e | undefined`\n\n- *returns*: `string`\n\ninput anything weird, get a valid variable name\n\noptionally, you can give a `existingVariables` to avoid conflicting -- the new name might have a numeric suffix\n\n#### Example\n\n```js\ngetVariableName('foo-bar')   // -\u003e \"fooBar\"\ngetVariableName('123abc')    // -\u003e \"_123abc\"\ngetVariableName('')          // -\u003e \"foobar\"\ngetVariableName('name', ['name', 'age'])    // -\u003e \"name2\"\n```\n\n\n\u003cbr/\u003e\n\n### `bracket(text1, text2, brackets?)`\n\n- **text1?**: `string | number | null | undefined`\n\n- **text2?**: `string | number | null | undefined`\n\n- **brackets?**: `string | [string, string] | undefined` — defaults to `[\" (\", \")\"]`\n\n- *returns*: `string`\n\nAdd bracket (parenthesis) to text\n\n- `bracket(\"c_name\", \"Column Name\")` =\u003e `\"c_name (Column Name)\"`\n- `bracket(\"Column Name\", \"c_name\")` =\u003e `\"Column Name (c_name)\"`\n\nIf one parameter is empty, it returns the other one:\n\n- `bracket(\"c_name\", null)` =\u003e `\"c_name\"`\n- `bracket(null, \"c_name\")` =\u003e `\"c_name\"`\n\n\n\u003cbr/\u003e\n\n### `randomStr(size?, dict?)`\n\n- **size?**: `number`\n\n- **dict?**: `string`\n\n- *returns*: `string`\n\nGenerate a random string\n\n\n\u003cbr/\u003e\n\n## 🧩 `value/compare`\n\n### `shallowEqual(objA, objB, depth?)`\n\n- **objA**: `any`\n\n- **objB**: `any`\n\n- **depth?**: `number` — defaults to 1\n\n- *returns*: `boolean`\n\n\n\u003cbr/\u003e\n\n## 🧩 `value/iterable`\n\n### `toArray(value)`\n\n- **value?**: `OneOrMany\u003cT\u003e`\n\n- *returns*: `NonNullable\u003cT\u003e[]`\n\nInput anything, always return an array.\n\n- If the input is a single value that is not an array, wrap it as a new array.\n- If the input is already an array, it returns a shallow copy.\n- If the input is an iterator, it is equivalent to using `Array.from()` to process it.\n\nFinally before returning, all `null` and `undefined` will be omitted\n\n\n\u003cbr/\u003e\n\n### `find(iterator, predicate)`\n\n- **iterator?**: `Nil | Iterable\u003cT\u003e`\n\n- **predicate**: `Predicate\u003cT\u003e`\n\n- *returns*: `T | undefined`\n\nLike `Array#find`, but the input could be a Iterator (for example, from generator, `Set` or `Map`)\n\n\n\u003cbr/\u003e\n\n### `reduce(iterator, initial, reducer)`\n\n- **iterator?**: `Nil | Iterable\u003cT\u003e`\n\n- **initial**: `U`\n\n- **reducer**: `(agg: U, item: T, index: number) =\u003e U`\n\n- *returns*: `U`\n\nLike `Array#reduce`, but the input could be a Iterator (for example, from generator, `Set` or `Map`)\n\n\n\u003cbr/\u003e\n\n### `head(iterator)`\n\n- **iterator?**: `Nil | Iterable\u003cT\u003e`\n\n- *returns*: `T | undefined`\n\nTake the first result from a Iterator\n\n\n\u003cbr/\u003e\n\n### `contains(collection, item)`\n\n- **collection?**: `Nil | CollectionOf\u003cT\u003e`\n\n- **item**: `T`\n\n- *returns*: `boolean`\n\ninput an array / Set / Map / WeakSet / WeakMap / object etc, check if it contains the `item`\n\n\n\u003cbr/\u003e\n\n### `forEach(objOrArray, iter)`\n\n- **objOrArray**: `U`\n\n- **iter**: `(value: U[keyof U], key: keyof U, whole: U) =\u003e void`\n\n- *returns*: `void`\n\na simple forEach iterator that support both `Array | Set | Map | Object | Iterable` as the input\n\nwhen met plain object, it works like `forIn` of lodash.\n\n\n\u003cbr/\u003e\n\n### `type OneOrMany\u003cT\u003e`\n\n```ts\nexport type OneOrMany\u003cT\u003e = Iterable\u003cT | Nil\u003e | T | Nil\n```\n\n\n\u003cbr/\u003e\n\n### `type Predicate\u003cT\u003e`\n\n```ts\nexport type Predicate\u003cT\u003e = (value: T, index: number) =\u003e boolean;\n```\n\n\n\u003cbr/\u003e\n\n### `type CollectionOf\u003cT\u003e`\n\n```ts\nexport type CollectionOf\u003cT\u003e =\n  | ReadonlyArray\u003cT\u003e\n  | Set\u003cT\u003e | Map\u003cT, any\u003e\n  | (T extends object ? WeakMap\u003cT, any\u003e | WeakSet\u003cT\u003e : never)\n  | (T extends string ? Record\u003cT, any\u003e : never)\n```\n\n\n\u003cbr/\u003e\n\n### `type IterItem\u003cT\u003e`\n\n```ts\nexport type IterItem\u003cT\u003e = T extends Iterable\u003cinfer U\u003e ? U : never;\n```\n\n\n\u003cbr/\u003e\n\n## 🧩 `value/types`\n\n### `isNil(obj)`\n\n- **obj**: `any`\n\n- *returns*: `boolean`\n\nTell if `obj` is null or undefined\n\n\n\u003cbr/\u003e\n\n### `isObject(obj)`\n\n- **obj**: `any`\n\n- *returns*: `false | \"array\" | \"object\"`\n\nTell if `obj` is Array, Object or other(`false`)\n\n\n\u003cbr/\u003e\n\n### `isThenable(sth)`\n\n- **sth**: `any`\n\n- *returns*: `boolean`\n\n\n\u003cbr/\u003e\n\n### `type MaybePromise\u003cT\u003e`\n\n```ts\nexport type MaybePromise\u003cT\u003e = Promise\u003cT\u003e | T\n```\n\n\n\u003cbr/\u003e\n\n### `type Falsy`\n\nTypeScript type presents all falsy value, including Nil, false, 0, \"\"\n\n```ts\nexport type Falsy = null | undefined | false | 0 | \"\";\n```\n\n\n\u003cbr/\u003e\n\n### `type Nil`\n\nTypeScript type presents null or undefined\n\n```ts\nexport type Nil = null | undefined;\n```\n\n\n\u003cbr/\u003e\n\n### `type Fn\u003cRET, ARGS\u003e`\n\nTypeScript type presents any function\n\n```ts\nexport type Fn\u003cRET = any, ARGS extends any[] = any[]\u003e = (...args: ARGS) =\u003e RET;\n```\n\n\n\u003cbr/\u003e\n\n\n\n\n\u003c!-- auto generate end --\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flyonbot%2Fyon-utils","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flyonbot%2Fyon-utils","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flyonbot%2Fyon-utils/lists"}