# @dirstack/utils 中文文档

规范地址：https://npmcn.com/package/%40dirstack/utils/

版本：3\.3\.0
npm 元数据更新时间：2026-11-10T09:51:12.797Z
中文译文更新时间：2026-11-12T10:55:02.066Z
软件包许可：MIT

本文由上游 README 翻译，可能存在偏差，请结合原文核对 API、命令与配置。版权与许可归原作者所有。

原始 npm 页面：https://www.npmjs.com/package/%40dirstack/utils
原文对照：https://npmcn.com/package/%40dirstack/utils/?lang=original
项目主页：https://github.com/dirstack/utils#readme
源码仓库：https://github.com/dirstack/utils

## 关键信息（来自 npm 元数据）

- 包体积（解压后）：226 kB，86 个文件
- 模块格式：ES Module
- TypeScript 类型：包内自带
- Node.js 要求：\>=22\.12
- 运行依赖：1 个
- 首次发布：2026-03-02T10:43:17.986Z
- 最新版本发布：2026-11-10T09:51:11.448Z
- 近 30 天下载量：8918（2026-10-13 至 2026-11-11，npm 官方下载统计）
- 已发布版本数：20（近 12 个月 20 个）

## 中文简介

一套轻型公用事业

## 安装

```sh
npm install @dirstack/utils
```

也可以使用：`pnpm add @dirstack/utils`、`yarn add @dirstack/utils`、`bun add @dirstack/utils`

## README

# @ dirstack/ utils

一套轻量级、依赖灯光的TypeScript公用事业,由各项目共享。 树能震动,只使用无害环境管理,并完全打入.

## 安装

```bash
bun add @dirstack/utils
# or: npm install @dirstack/utils
```

需要节点js 22.12 或以后。 软件包只装ES模块,而CommonJS代码可以 `require()` 它在这些版本.

## 使用量

从软件包根导出所有 :

```ts
import { chunk, clamp, formatBytes, slugify, tryCatch } from "@dirstack/utils"

chunk([1, 2, 3, 4, 5], 2) // [[1, 2], [3, 4], [5]]
clamp(12, 0, 10) // 10
formatBytes(1536, 1) // "1.5 KB"
slugify("Hello World") // "hello-world"

const { data, error } = await tryCatch(fetch("/api"))
```

每一个模块在自己的路径上也可以使用,例如: `@dirstack/utils/array` 或者说 `@dirstack/utils/time`. Bundlers 将未使用的代码从根导入中去除,因为构建每个模块保存一个文件.

## 里面的东西

| 模块 | 要点 |
| --- | --- |
| `array` | `uniq`, (中文(简体) ), `uniqBy`, (中文(简体) ), `chunk`, (中文(简体) ), `groupBy`, (中文(简体) ), `keyBy`, (中文(简体) ), `countBy`, (中文(简体) ), `compact`, (中文(简体) ), `sortBy`, (中文(简体) ), `sum`, (中文(简体) ), `sumBy`, (中文(简体) ), `range` |
| `batch` | `processBatch`, (中文(简体) ), `processBatchSettled` (有秩序、货币限制、 `onProgress`(中文(简体) ) |
| `errors` | `getErrorMessage`, (中文(简体) ), `toError` |
| `files` | `formatBytes`, (中文(简体) ), `isMimeTypeMatch`, (中文(简体) ), `toBase64`, (中文(简体) ), `toDataUrl` |
| `format` | `formatNumber`, (中文(简体) ), `formatCurrency`, (中文(简体) ), `formatIntervalAmount`, (中文(简体) ), `currencyExponent`, (中文(简体) ), `minorUnits` |
| `helpers` | `sleep`, (中文(简体) ), `isTruthy`, (中文(简体) ), `tryCatch`, (中文(简体) ), `retry`, (中文(简体) ), `withTimeout` |
| `http` | `isValidUrl`, (中文(简体) ), `addProtocol`, (中文(简体) ), `removeProtocol`, (中文(简体) ), `getDomain`, (中文(简体) ), `isExternalUrl`, (中文(简体) ), `joinUrlPaths`, (中文(简体) ), `setQueryParams`, (中文(简体) ), `removeQueryParams`, (中文(简体) ), `isValidImageSrc`, (中文(简体) ), `isHostname`, (中文(简体) ), `normalizeHostname`, (中文(简体) ), `stripWildcard`, (中文(简体) ), `isWithinDomain`, (中文(简体) ), `isIp`, (中文(简体) ), `isIpInRange`, (中文(简体) ), `isCloudflareIp`, (中文(简体) ), `getClientIp`, (中文(简体) ), `CLOUDFLARE_IP_RANGES` |
| `numbers` | `clamp`, (中文(简体) ), `preciseRound` |
| `objects` | `pick`, (中文(简体) ), `omit`, (中文(简体) ), `isEmptyObject`, (中文(简体) ), `isKeyInObject` |
| `parsers` | `serialize` |
| `random` | `getRandomString`, (中文(简体) ), `getRandomDigits`, (中文(简体) ), `getRandomNumber`, (中文(简体) ), `getRandomElement` |
| `string` | `ucFirst`, (中文(简体) ), `lcFirst`, (中文(简体) ), `truncate`, (中文(简体) ), `slugify`, (中文(简体) ), `getInitials`, (中文(简体) ), `joinAsSentence` |
| `time` | `formatDate`, (中文(简体) ), `formatTime`, (中文(简体) ), `formatDateTime`, (中文(简体) ), `formatDateRange`, (中文(简体) ), `getReadTime`, (中文(简体) ), `dayKey`, (中文(简体) ), `shiftDayKey`, (中文(简体) ), `dayIn`, (中文(简体) ), `fromUnix`, (中文(简体) ), `toUnix`, (中文(简体) ), `SECOND_MS`, (中文(简体) ), `MINUTE_MS`, (中文(简体) ), `HOUR_MS`, (中文(简体) ), `DAY_MS`, (中文(简体) ), `MINUTE_SECONDS`, (中文(简体) ), `HOUR_SECONDS`, (中文(简体) ), `DAY_SECONDS` |
| `ui` | `getBalancedColumns`, (中文(简体) ), `isLightColor`, (中文(简体) ), `getCurrentPage`, (中文(简体) ), `getPagination`, (中文(简体) ), `getPageCount`, (中文(简体) ), `isPageOutOfRange`, (中文(简体) ), `getPageLink` |
| 类型 | `WithOptional`, (中文(简体) ), `WithRequired`,加上帮助签名中的类型,例如 `Result` (从) `tryCatch`, (中文(简体) ), `Timestamp` 财务报告和审定财务报表 `ProcessBatchOptions` |

### 日期和时区

日期帮助者接受任何 `Intl.DateTimeFormat` 选项加a `locale` (违约) `"en-US"`) (中文(简体) ). 没有 `timeZone`,它们格式化为运行它们的机器的时区。 在服务器发送的页面上,服务器(通常为UTC)和浏览器(访问者区域)然后可以生成不同的文本,这会导致回放水合错误. 通过固定 `timeZone` 在两地获得相同的输出:

```ts
import { formatDate, formatDateRange } from "@dirstack/utils"

formatDate("2026-10-05T23:30:00Z", { dateStyle: "long", timeZone: "UTC", locale: "en-GB" }) // "5 October 2026"
formatDate("2026-10-05", { day: "2-digit", month: "short", year: "numeric", timeZone: "UTC" }) // "Oct 05, 2026"
formatDateRange("2026-10-05", "2026-10-09", { timeZone: "UTC" }) // "Oct 5 – 9, 2026"
```

只约会字符串, 如 `"2026-10-05"` 解析为 UTC 午夜,所以格式化为 `timeZone: "UTC"` 在任何地方保持同一个日历日.

### 代理服务器背后的客户端 IP

`getClientIp` 从Traefik等反向代理设置的请求头读取访客的地址。 第一个 `x-forwarded-for` 条目来自客户端,可以被伪造,因此它返回最后一个条目,代理附件作为TCP对等点。 掉下来了 `x-real-ip`返回时 `undefined` 当没有标题持有有效的 IP 地址时 :

```ts
import { getClientIp } from "@dirstack/utils"

getClientIp(request.headers) ?? "127.0.0.1"
```

在代理Cloudflare记录后,对等点是一个Cloudflare服务器. 过 `trust: "cloudflare"` 返回 `cf-connecting-ip` 换句话说 只有在对等点出现时才读取页眉 `CLOUDFLARE_IP_RANGES`,因为一个直接发送到来源的请求可以将其设定为任何:

```ts
getClientIp(request.headers, { trust: "cloudflare" })
```

IP 助手在纯类型脚本中解析 IPv4 和 IPv6, 不 `node:net`,因此它们也会在浏览器和边缘运行时运行.

### 用时间预算重试

`retry` 投出时再次运行一个同步函数。 默认情况下,它使3次复试,它们之间没有等待. `delay` 财务报告和审定财务报表 `factor` 添加指数后退,以及 `maxDelay` 每个等 :

```ts
import { retry } from "@dirstack/utils"

// Waits 100ms, 200ms, 400ms, 800ms, then 1s between attempts, for at most 15 seconds.
const rows = await retry(() => client.query(sql), {
  retries: Infinity,
  delay: 100,
  maxDelay: 1000,
  timeout: 15_000,
  shouldRetry: isConnectionError,
})
```

`timeout` 计数从调用到 `retry`。 。 。 . `deadline` 取用划时代的时间戳代替,这样几个电话就可以共享一个预算. 两者设定后,适用前一种. `retry` 永远不要开始一个会结束预算的等待: 它会重排最后一个错误 . `retries` 仍然默认为 3 有预算设置,所以通过 `retries: Infinity` 任由预算自行结束循环.

```ts
const deadline = Date.now() + 15_000
const user = await retry(() => findUser(id), { retries: Infinity, delay: 100, deadline })
await retry(() => insertEvent(user), { retries: Infinity, delay: 100, deadline })
```

`signal` 需要一个 `AbortSignal`一旦它流产, `retry` 停下来,甚至在等待的中间, 拒绝与 `signal.reason`。预算或信号都无法中断已经运行的尝试。 将回调包入 `withTimeout` 当一次尝试可以绞死.

## 从v2 移动

v3留了一条路给每个助手打电话. 以下被移除的每个别名或调用表都有直接替换.

### 软件包格式

通用JS/UMD大楼`dist/index.umd.cjs`)不见了。 一揽子措施只采用无害环境管理,需要节点22.12或以后.

```ts
// Before: CommonJS on any Node.js version
const { slugify } = require("@dirstack/utils")
// After: an import, or require() on Node.js 22.12+
import { slugify } from "@dirstack/utils"
```

### 删除的别名

```ts
// Before                                    // After
splitArrayIntoChunks(items, 10)              chunk(items, 10)
keepNumberInRange(value, 0, 100)             clamp(value, 0, 100)
pickFromObject(user, ["id", "name"])         pick(user, ["id", "name"])
```

### 日期格式化器只选择选项对象

```ts
// Before                                                   // After
formatDate(date, "long", "en-GB")                           formatDate(date, { dateStyle: "long", locale: "en-GB" })
formatTime(date, "short", "pl")                             formatTime(date, { timeStyle: "short", locale: "pl" })
formatDateTime(date, "long", "short", "de")                 formatDateTime(date, { dateStyle: "long", timeStyle: "short", locale: "de" })
formatDateRange(start, end, "short", "es")                  formatDateRange(start, end, { dateStyle: "short", locale: "es" })
```

### `sortBy` 选择选项对象

```ts
// Before                                  // After
sortBy(posts, post => post.date, "desc")   sortBy(posts, post => post.date, { order: "desc" })
```

### 删除的助手

没有项目使用这些, 所以 v3 放弃它们 。 每个都有一个简短的本地或图书馆替换.

| 删除 | 改为使用 |
| --- | --- |
| `getElementPosition` | `element.getBoundingClientRect()` 或者说 `element.scrollIntoView()` |
| `setInputValue` | 将五行复制到需要它们的项目中 |
| `subscribe`, (中文(简体) ), `unsubscribe`, (中文(简体) ), `publish` | `document.addEventListener`, (中文(简体) ), `removeEventListener`, (中文(简体) ), `dispatchEvent(new CustomEvent(name, { detail }))` |
| `publishEscape` | `document.dispatchEvent(new KeyboardEvent("keydown", { key: "Escape" }))` |
| `isErrorWithMessage`, (中文(简体) ), `toErrorWithMessage`, (中文(简体) ), `ErrorWithMessage` | `getErrorMessage(error)` 或者说 `toError(error)` |
| `formatMimeType` | `mimeType.split("/")[1]?.toUpperCase()` |
| `formatToDecimals` | `formatNumber(value, { maximumFractionDigits: 2, useGrouping: false })` |
| `debounce`, (中文(简体) ), `throttle` | `useDebouncedCallback` 财务报告和审定财务报表 `useThrottledCallback` 从 `@mantine/hooks` |
| `getBaseUrl` | `new URL(url).origin` |
| `getQueryParams` | `Object.fromEntries(new URL(url).searchParams)` |
| `checkUrlAvailability` | `fetch(url, { method: "HEAD", signal: AbortSignal.timeout(5000) })` |
| `normalizeUrl`, (中文(简体) ), `isLocalhostUrl` | 仍在包内使用, 不再输出 |
| `parseNumericValue` | `Number.parseFloat(value)` 带有 `Number.isNaN` 检查,或 `z.coerce.number()` |
| `sortObject` | `Object.fromEntries(Object.entries(source).sort(([a], [b]) => a.localeCompare(b)))` |
| `sortObjectKeys` | 需要它的项目的参照者 |
| `nullsToUndefined`, (中文(简体) ), `ReplaceNullWithUndefined` | 绘制需要的字段 |
| `maybeParseJson`, (中文(简体) ), `maybeStringifyJson`, (中文(简体) ), `deserialize` | `JSON.parse` 财务报告和审定财务报表 `JSON.stringify` |
| `getRandomColor` | `Math.floor(Math.random() * 0x1000000).toString(16).padStart(6, "0")` |
| `getRandomProperty` | `getRandomElement(Object.values(source))` |
| `isCuid` | `isCuid` 从 `@paralleldrive/cuid2` |
| `formatDateOrTime` | `formatDate`, (中文(简体) ), `formatTime` 或者说 `formatDateTime` |
| `getShortcutLabel` | A 类 `<Kbd>` 了解平台修改键的组件 |
| `DeepIdx`, (中文(简体) ), `DeepIndex`, (中文(简体) ), `ValidatePath` | `Path` 财务报告和审定财务报表 `PathValue` 从 `react-hook-form`,或 `Get` 财务报告和审定财务报表 `Paths` 从 `type-fest` |
| `NestedPartial`, (中文(简体) ), `NestedRequired` | `PartialDeep` 财务报告和审定财务报表 `RequiredDeep` 从 `type-fest` |

### `processBatchWithErrorHandling` 现在 `processBatchSettled`

喜欢 `Promise.allSettled`,每个结果现在都说明其项目是否成功并携带了项目,而不是混合值和 `Error` 在一个阵列中的对象。 选项,包括 `onError`,不变.

```ts
// Before
const results = await processBatchWithErrorHandling(items, processor, { batchSize: 10 })
const failed = results.filter(result => result instanceof Error)

// After
const results = await processBatchSettled(items, processor, { batchSize: 10 })
const failed = results.filter(result => result.status === "rejected") // { item, error }
```

### `toBase64` 返回平面基数64,以及 `toDataUrl` 返回数据 URL

`toBase64` 用于返回数据 URL(`data:image/png;base64,…`)),所以大多数打电话者都剥去前缀. 它现在只返回 Base64 字符串,和 `toDataUrl` 返回完整的数据 URL。 双方接受任何 `Blob` 还在Node.js工作.

```ts
// Before                                                          // After
(await toBase64(file)).replace(/^data:[^;]+;base64,/, "")          await toBase64(file)
await toBase64(file) // when the data URL was wanted                await toDataUrl(file)
```

### `slugify` 选择选项对象

```ts
// Before                         // After
slugify(title, true)              slugify(title, { decamelize: true })
```

### `getExcerpt` 现在 `truncate`

每个呼叫者都通过了纯文本,所以 `truncate` 丢弃 HTML 条列( HTML 也删除任何文本) `<`) (中文(简体) ). 它在文字之间切分,从不分割一个emoji, 并保持结果在内 `length` 包括《京都议定书》 `…`。 。 。 .

```ts
// Before                              // After
getExcerpt(description, 125)           truncate(description, 125)
```

三个细节有所改变: `…` 改为 `...`,它数到 `length`空输入返回 `""` 改为 `null`。 。 。 . `stripHtml` 财务报告和审定财务报表 `convertNewlines` 被删除.

### `getPageParams` 现在 `getPagination`

`getPageParams` 从 URL 中读取页面,然后返回未选中类型下的其他查询参数. `getPagination` 只有数学:先用路由器或nuqs来解析页面.

```ts
// Before                                    // After
getPageParams<Filters>(url, 20)              getPagination({ page, perPage: 20 }) // { skip, take }
```

### `joinAsSentence` 选择选项对象

```ts
// Before                                       // After
joinAsSentence(names)                           joinAsSentence(names, { limit: 3, locale: "en-GB" })
joinAsSentence(names, 5)                        joinAsSentence(names, { limit: 5, locale: "en-GB" })
joinAsSentence(names, undefined, t("and"))      joinAsSentence(names, { locale })
joinAsSentence(names, undefined, "or")          joinAsSentence(names, { type: "disjunction", locale: "en-GB" })
```

`joinAsSentence` 现在格式为 `Intl.ListFormat`,它以三种方式改变其输出:

-   它没有默认限制。 A 类 `limit` 将其余部分折叠成一个后缀项目( “ a, b, and 2 more ” ) 而不是丢弃, 并且永远不隐藏单个项目 .
-   默认 `en-US` locane 添加了序列逗号:"a,b,和c". 过 `locale: "en-GB"` 用于“a、b和c”,如v2.
-   地块设置相和相和相接相和相接相. 传出活动语言,而不是一个被翻译的"和".

## 发展

```bash
bun install        # install dependencies
bun test           # run the test suite on Bun
bun run test:node  # run the same suite on Node.js with Vitest
bun run typecheck
bun run lint
bun run build      # build with tsdown, then check the package with publint and attw
```

## 许可证

麻省理工学院 [皮奥特·库尔平斯基](https://kulpinski.pl/)

## 常见问题

### @dirstack/utils 是什么？

一套轻型公用事业

### 如何安装 @dirstack/utils？

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

### @dirstack/utils 支持 TypeScript 吗？

支持。包内自带 TypeScript 类型声明（package\.json 声明了 types 入口），安装后即可获得类型提示，无需再安装 @types 包。

### @dirstack/utils 支持 ESM 还是 CommonJS？

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

### @dirstack/utils 需要什么版本的 Node\.js？

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

### @dirstack/utils 可以商用吗？

@dirstack/utils 使用 MIT 许可。宽松许可：允许商业使用、修改和再分发，通常只需在分发时保留原版权与许可声明。以上为概括，不构成法律意见，请以许可原文为准。

### @dirstack/utils 的下载量有多少？

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

### @dirstack/utils 还在维护吗？

最新版本 3\.3\.0 发布于 2026/11/10（2 天前）；至今共发布 20 个版本，其中过去 12 个月发布了 20 个；首次发布于 2026/03/02。发布频率只是参考信号：功能稳定的工具可能很少需要更新，建议结合源码仓库的 issue 和提交记录判断。

