@ dirstack/ utils
一套轻量级、依赖灯光的TypeScript公用事业,由各项目共享。 树能震动,只使用无害环境管理,并完全打入.
安装
bun add @dirstack/utils
# or: npm install @dirstack/utils
需要节点js 22.12 或以后。 软件包只装ES模块,而CommonJS代码可以 require() 它在这些版本.
使用量
从软件包根导出所有 :
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 在两地获得相同的输出:
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 地址时 :
import { getClientIp } from "@dirstack/utils"
getClientIp(request.headers) ?? "127.0.0.1"
在代理Cloudflare记录后,对等点是一个Cloudflare服务器. 过 trust: "cloudflare" 返回 cf-connecting-ip 换句话说 只有在对等点出现时才读取页眉 CLOUDFLARE_IP_RANGES,因为一个直接发送到来源的请求可以将其设定为任何:
getClientIp(request.headers, { trust: "cloudflare" })
IP 助手在纯类型脚本中解析 IPv4 和 IPv6, 不 node:net,因此它们也会在浏览器和边缘运行时运行.
用时间预算重试
retry 投出时再次运行一个同步函数。 默认情况下,它使3次复试,它们之间没有等待. delay 财务报告和审定财务报表 factor 添加指数后退,以及 maxDelay 每个等 :
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 任由预算自行结束循环.
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或以后.
// 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"
删除的别名
// Before // After
splitArrayIntoChunks(items, 10) chunk(items, 10)
keepNumberInRange(value, 0, 100) clamp(value, 0, 100)
pickFromObject(user, ["id", "name"]) pick(user, ["id", "name"])
日期格式化器只选择选项对象
// 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 选择选项对象
// 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,不变.
// 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工作.
// Before // After
(await toBase64(file)).replace(/^data:[^;]+;base64,/, "") await toBase64(file)
await toBase64(file) // when the data URL was wanted await toDataUrl(file)
slugify 选择选项对象
// Before // After
slugify(title, true) slugify(title, { decamelize: true })
getExcerpt 现在 truncate
每个呼叫者都通过了纯文本,所以 truncate 丢弃 HTML 条列( HTML 也删除任何文本) <) (中文(简体) ). 它在文字之间切分,从不分割一个emoji, 并保持结果在内 length 包括《京都议定书》 …。 。 。 .
// Before // After
getExcerpt(description, 125) truncate(description, 125)
三个细节有所改变: … 改为 ...,它数到 length空输入返回 "" 改为 null。 。 。 . stripHtml 财务报告和审定财务报表 convertNewlines 被删除.
getPageParams 现在 getPagination
getPageParams 从 URL 中读取页面,然后返回未选中类型下的其他查询参数. getPagination 只有数学:先用路由器或nuqs来解析页面.
// Before // After
getPageParams<Filters>(url, 20) getPagination({ page, perPage: 20 }) // { skip, take }
joinAsSentence 选择选项对象
// 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-USlocane 添加了序列逗号:"a,b,和c". 过locale: "en-GB"用于“a、b和c”,如v2. - 地块设置相和相和相接相和相接相. 传出活动语言,而不是一个被翻译的"和".
发展
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
许可证
麻省理工学院 皮奥特·库尔平斯基