@faybl/utils

中文文档
2.5.2发布于 2026/11/13TBDES Module近 30 天下载 3702

Generic JavaScript for Faybl services and web apps

原始使用文档
文档目录7 节
  1. History
  2. Waiting
  3. Paths and picks
  4. Clipboard
  5. Result
  6. Channels
  7. Names and mock data

@faybl/utils

Generic JavaScript for Faybl services and web apps.

npm install @faybl/utils

History

fetchHistory reads a channel's history through a faybl-client as an array in ascending time, each point with ts and pl, which is what a chart draws.

import { fetchHistory, fetchHistoryLast } from '@faybl/utils';

const { data } = await fetchHistory({ client, rid, from, to });
const newest = await fetchHistoryLast({ client, rid });

It decides ascending and reverse for data-history from the range:

Asked forReturns
from, with or without tothe range, from its start
limit and fromthe first limit points from from
limit without fromthe newest limit points, before to when given
nothingfrom the oldest point

samplingInterval, downsamplingMethod and timeZone are passed on. Empty values are left out, and nothing else reaches data-history.

fetchHistoryLast is the newest point, before to when given, or undefined.

Waiting

waitFor calls a function until its promise resolves, and resolves with the value. It is how a service waits at start for a dependency that comes up on its own, logging each attempt instead of exiting.

import { waitFor } from '@faybl/utils';

const storage = await waitFor(connectToDatabase, {
  onRetry: (error, attempt) => logger.warn(`Database not reachable, attempt ${attempt}:`, error),
});
OptionDefaultWhat it does
interval5000Milliseconds between attempts
retryIfalways trueGiven the rejection; false passes it on instead of trying again
onRetrydoes nothingGiven the rejection and the attempt's number, before the wait

There is no limit on attempts.

delay(milliseconds, signal?) resolves after the time. An abort of signal clears the timer and rejects with the signal's reason.

await delay(1000);
await delay(1000, AbortSignal.timeout(500)); // rejects after 500 ms

Paths and picks

pathStr('pl.temp')(data) reads a dot-separated path, such as one a user types; an array index is a number in the path ('list.0'). A missing path and a null (or NaN) value both give undefined.

pickRandom(items) is a random item, or undefined for none. It takes its randomness as a second argument for tests: pickRandom(items, () => 0).

Clipboard

copyText(text) writes to the system clipboard and resolves to a Result: Ok, or Err({ kind: 'ClipboardWriteFailed', cause }) when there is no clipboard, permission is denied or the page has no focus. Call it from the click handler before any await, or the browser rejects the write.

match(({ cause }) => console.warn('Copy by hand:', cause))(() => setCopied(true))(
  await copyText(url),
);

Result

Expected failures as values: plain, serializable data, Ok(value) = { ok: true, value } or Err(error) = { ok: false, error }. Compose with map / chain / mapErr from this package (never R.map / R.chain: Ramda maps a Result's fields as a plain object), collect a list with all (first Err wins) and unwrap once at the edge with match(onErr)(onOk).

import * as R from 'ramda';
import { Err, Ok, chain, fromNil, match, tryCatchAsync } from '@faybl/utils';

const fetchJson = tryCatchAsync(async (url) => Ok(await (await fetch(url)).json()))((cause, url) =>
  Err({ kind: 'FetchFailed', url, cause }),
);
const getPort = R.pipe(R.path(['server', 'port']), fromNil({ kind: 'MissingPort' }));

const port = chain(getPort)(await fetchJson(url));
match(({ kind }) => console.error(kind))(console.log)(port);

Wrap foreign throwers at the boundary with R.tryCatch (sync) or tryCatchAsync (async). When the wrapped call can also throw errors that are not expected failures, use catchWhen(isExpected)(toErr) as the catcher: it maps matching errors to an Err and rethrows anything else unchanged, so defects still crash.

const readText = tryCatchAsync(async (path) => Ok(await readFile(path, 'utf8')))(
  catchWhen((e) => e?.code === 'ENOENT')((cause, path) =>
    Err({ kind: 'FileNotFound', path, cause }),
  ),
);

Channels

channelsOfParent(parent)(records) keeps the channel records an asset or thing lists in its channels (name → rid), each with nameFromParent; a missing parent gives none. recordsToSelectOptions turns channel records into { id: rid, label } options.

Names and mock data

haiku() is a random two-word title such as Misty creek. mockHistory() is ten points over the last ten seconds, shaped like a data-history response, for an app running without a backend; mockHistoryEmpty() has none. Both take their randomness (and mockHistory its now) as arguments for tests.

版本与发布记录

@faybl/utils 共发布 19 个版本,首次发布于 2026/10/22;过去 12 个月发布了 19 个版本。

版本发布日期距今
2.5.2latest2026/11/131 天前
2.5.12026/11/122 天前
2.4.02026/11/113 天前
2.3.02026/11/0410 天前
2.2.02026/10/2718 天前
2.2.0-rc.52026/10/2718 天前

关于 @faybl/utils 的常见问题

@faybl/utils 是什么?

Generic JavaScript for Faybl services and web apps

如何安装 @faybl/utils?

在项目目录运行 npm install @faybl/utils,或使用 pnpm add / yarn add / bun add。

@faybl/utils 支持 TypeScript 吗?

该包的 package.json 没有声明类型入口。需要类型提示时,可以在 npm 上查找社区维护的 @types/faybl__utils 是否存在。

@faybl/utils 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@faybl/utils 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@faybl/utils 可以商用吗?

@faybl/utils 使用 TBD 许可。声明的许可为 TBD,请阅读许可原文确认使用条件。以上为概括,不构成法律意见,请以许可原文为准。

@faybl/utils 的下载量有多少?

根据 npm 官方下载统计,@faybl/utils 在 2026/10/15 – 2026/11/13 这 30 天内被下载 3,702 次,日均约 123 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@faybl/utils 还在维护吗?

最新版本 2.5.2 发布于 2026/11/13(1 天前);至今共发布 19 个版本,其中过去 12 个月发布了 19 个;首次发布于 2026/10/22。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。