# @clipboard-health/util-ts 中文文档

规范地址：https://npmcn.com/package/%40clipboard-health/util-ts/

版本：5\.18\.15
npm 元数据更新时间：2026-11-05T04:55:35.323Z
中文译文更新时间：2026-11-10T01:49:39.123Z
软件包许可：MIT

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

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

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

- 包体积（解压后）：112 kB，113 个文件
- 模块格式：CommonJS
- TypeScript 类型：包内自带
- 运行依赖：1 个
- peer 依赖：zod ^3，type-fest ^4 \|\| ^5\.0\.0
- 首次发布：2023-04-19T03:14:28.442Z
- 最新版本发布：2026-11-05T04:55:35.140Z
- 近 30 天下载量：405933（2026-10-10 至 2026-11-08，npm 官方下载统计）
- 已发布版本数：540（近 12 个月 386 个）

## 中文简介

TypeScript 服务.

## 安装

```sh
npm install @clipboard-health/util-ts
```

也可以使用：`pnpm add @clipboard-health/util-ts`、`yarn add @clipboard-health/util-ts`、`bun add @clipboard-health/util-ts`

## README

# @clippboard-health/util-ts (英语)

TypeScript 服务.

## 目录

-   [安装](https://npmcn.com/package/%40clipboard-health/util-ts/#readme-install)
-   [使用量](https://npmcn.com/package/%40clipboard-health/util-ts/#readme-usage)
    -   [服务错误](https://npmcn.com/package/%40clipboard-health/util-ts/#readme-serviceerror)
    -   [服务成果](https://npmcn.com/package/%40clipboard-health/util-ts/#readme-serviceresult)
        -   [`matchServiceResult`](https://npmcn.com/package/%40clipboard-health/util-ts/#readme-matchserviceresult)
        -   [`tryCatchAsync`](https://npmcn.com/package/%40clipboard-health/util-ts/#readme-trycatchasync)
        -   [`tryCatch`](https://npmcn.com/package/%40clipboard-health/util-ts/#readme-trycatch)
        -   [`fromSafeParseReturnType`](https://npmcn.com/package/%40clipboard-health/util-ts/#readme-fromsafeparsereturntype)
    -   [职能](https://npmcn.com/package/%40clipboard-health/util-ts/#readme-functional)
        -   [`pipe`](https://npmcn.com/package/%40clipboard-health/util-ts/#readme-pipe)
        -   [`option`](https://npmcn.com/package/%40clipboard-health/util-ts/#readme-option)
        -   [`either`](https://npmcn.com/package/%40clipboard-health/util-ts/#readme-either)
-   [地方发展指挥部](https://npmcn.com/package/%40clipboard-health/util-ts/#readme-local-development-commands)

## 安装

```bash
npm install @clipboard-health/util-ts
```

## 使用量

见 `./src/lib` 每个工具.

### 服务错误

```ts
import { deepEqual, strictEqual } from "node:assert/strict";

import { ERROR_CODES, ServiceError } from "@clipboard-health/util-ts";
import { z } from "zod";

{
  const error = new ServiceError("boom");
  strictEqual(error.toString(), `ServiceError[${error.id}]: [internal]: boom`);
}

try {
  throw new Error("boom");
} catch (error) {
  const serviceError = ServiceError.fromUnknown(error);
  strictEqual(serviceError.toString(), `ServiceError[${serviceError.id}]: [internal]: boom`);
}

{
  const serviceError = ServiceError.fromZodError(
    new z.ZodError([{ code: "custom", path: ["foo"], message: "boom" }]),
  );
  strictEqual(serviceError.toString(), `ServiceError[${serviceError.id}]: [badRequest]: boom`);
}

{
  const errorWithCause = new ServiceError({
    issues: [{ message: "boom" }],
    cause: new Error("Original error"),
  });
  strictEqual(errorWithCause.toString(), `ServiceError[${errorWithCause.id}]: [internal]: boom`);
}

{
  const multipleIssues = new ServiceError({
    issues: [
      {
        code: ERROR_CODES.badRequest,
        message: "Invalid email format",
        path: ["data", "attributes", "email"],
      },
      {
        code: ERROR_CODES.unprocessableEntity,
        message: "Phone number too short",
        path: ["data", "attributes", "phoneNumber"],
      },
    ],
    cause: new Error("Original error"),
  });

  strictEqual(
    multipleIssues.toString(),
    `ServiceError[${multipleIssues.id}]: [badRequest]: Invalid email format; [unprocessableEntity]: Phone number too short`,
  );

  deepEqual(multipleIssues.toJsonApi(), {
    errors: [
      {
        id: multipleIssues.id,
        status: "400",
        code: "badRequest",
        title: "Invalid or malformed request",
        detail: "Invalid email format",
        source: {
          pointer: "/data/attributes/email",
        },
      },
      {
        id: multipleIssues.id,
        status: "422",
        code: "unprocessableEntity",
        title: "Request failed validation",
        detail: "Phone number too short",
        source: {
          pointer: "/data/attributes/phoneNumber",
        },
      },
    ],
  });
}
```

### 服务成果

`ServiceResult<A, E>` 表示一个成功的 `A` 值或一个 `ServiceError` 子类型 `E`。 。 。 . 错误类型默认为 `ServiceError` 与现有呼叫者兼容。 传递明确 当呼叫者需要分解错误时, 操作可以返回 .

```ts
import { ok } from "node:assert/strict";

import {
  ERROR_CODES,
  failure,
  isFailure,
  isSuccess,
  type ServiceResult,
  success,
} from "@clipboard-health/util-ts";

function validateUser(params: { email: string; phone: string }): ServiceResult<{ id: string }> {
  const { email, phone } = params;
  const code = ERROR_CODES.unprocessableEntity;

  if (!email.includes("@")) {
    return failure({ issues: [{ code, message: "Invalid email format" }] });
  }

  if (phone.length !== 12) {
    return failure({ issues: [{ code, message: "Invalid phone number" }] });
  }

  return success({ id: "user-123" });
}

ok(isFailure(validateUser({ email: "invalidEmail", phone: "invalidPhoneNumber" })));
ok(isSuccess(validateUser({ email: "user@example.com", phone: "555-555-5555" })));
```

#### `matchServiceResult`

`matchServiceResult` 映射输入的地图 `ServiceResult` 到一个输出,同时需要处理器 字义 `_tag` 在错误的结合。 添加或删除一个错误类型, 从而更新每个穷尽 匹配编译时间。 不需要全部处理的呼叫者可以继续使用 `isFailure` 财务报告和审定财务报表 `isSuccess`。 。 。 .

```ts
import { strictEqual } from "node:assert/strict";

import {
  failure,
  matchServiceResult,
  ServiceError,
  type ServiceResult,
  success,
} from "@clipboard-health/util-ts";

interface User {
  id: string;
  name: string;
}

class InvalidUserIdError extends ServiceError {
  public readonly _tag = "InvalidUserIdError" as const;

  public constructor() {
    super("User ID is required");
  }
}

class UserNotFoundError extends ServiceError {
  public readonly _tag = "UserNotFoundError" as const;

  public readonly userId: string;

  public constructor({ userId }: { userId: string }) {
    super(`User ${userId} was not found`);
    this.userId = userId;
  }
}

type FindUserError = InvalidUserIdError | UserNotFoundError;

function findUser({ userId }: { userId: string }): ServiceResult<User, FindUserError> {
  if (userId.length === 0) {
    return failure(new InvalidUserIdError());
  }

  if (userId === "missing") {
    return failure(new UserNotFoundError({ userId }));
  }

  return success({ id: userId, name: "Ada" });
}

const message = matchServiceResult(findUser({ userId: "missing" }), {
  onSuccess: (user) => `Found ${user.name}`,
  onError: {
    InvalidUserIdError: (error) => error.message,
    UserNotFoundError: (error) => `User ${error.userId} was not found`,
  },
});

strictEqual(message, "User missing was not found");
```

#### `tryCatchAsync`

```ts
import { ok, strictEqual } from "node:assert/strict";

import { isFailure, isSuccess, ServiceError, tryCatchAsync } from "@clipboard-health/util-ts";

async function example() {
  const successResult = await tryCatchAsync(
    async () => {
      const response = await fetch("https://jsonplaceholder.typicode.com/posts/1");
      return (await response.json()) as { id: number };
    },
    (error) => new ServiceError(`Failed to fetch: ${String(error)}`),
  );

  ok(isSuccess(successResult));
  strictEqual(successResult.value.id, 1);

  const failureResult = await tryCatchAsync(
    async () => await Promise.reject(new Error("Network error")),
    (error) => new ServiceError(`Failed to fetch: ${String(error)}`),
  );

  ok(isFailure(failureResult));
  strictEqual(failureResult.error.issues[0]?.message, "Failed to fetch: Error: Network error");
}

void example();
```

#### `tryCatch`

```ts
import { ok, strictEqual } from "node:assert/strict";

import { isFailure, isSuccess, parseJson, ServiceError, tryCatch } from "@clipboard-health/util-ts";

const successResult = tryCatch(
  () => parseJson<{ name: string }>('{"name": "John"}'),
  (error) => new ServiceError(`Parse error: ${String(error)}`),
);

ok(isSuccess(successResult));
strictEqual(successResult.value.name, "John");

const failureResult = tryCatch(
  () => parseJson("invalid json"),
  (error) => new ServiceError(`Parse error: ${String(error)}`),
);

ok(isFailure(failureResult));
ok(failureResult.error.issues[0]?.message?.includes("Parse error") === true);
```

#### `fromSafeParseReturnType`

```ts
import { ok, strictEqual } from "node:assert/strict";

import { fromSafeParseReturnType, isFailure, isSuccess } from "@clipboard-health/util-ts";
import { z } from "zod";

const schema = z.object({ name: z.string(), age: z.number() });

const validData = { name: "John", age: 30 };
const successResult = fromSafeParseReturnType(schema.safeParse(validData));

ok(isSuccess(successResult));
strictEqual(successResult.value.name, "John");

const invalidData = { name: "John", age: "thirty" };
const failureResult = fromSafeParseReturnType(schema.safeParse(invalidData));

ok(isFailure(failureResult));
ok(failureResult.error.issues.length > 0);
```

### 职能

#### `pipe`

```ts
import { strictEqual } from "node:assert/strict";

import { pipe } from "@clipboard-health/util-ts";

const result = pipe(
  "  hello world  ",
  (s) => s.trim(),
  (s) => s.split(" "),
  (array) => array.map((word) => word.charAt(0).toUpperCase() + word.slice(1)),
  (array) => array.join(" "),
);

strictEqual(result, "Hello World");
```

#### `option`

```ts
import { strictEqual } from "node:assert/strict";

import { option as O, pipe } from "@clipboard-health/util-ts";

function double(n: number) {
  return n * 2;
}

function inverse(n: number): O.Option<number> {
  return n === 0 ? O.none : O.some(1 / n);
}

const result = pipe(
  O.some(5),
  O.map(double),
  O.flatMap(inverse),
  O.match(
    () => "No result",
    (n) => `Result is ${n}`,
  ),
);

strictEqual(result, "Result is 0.1");
```

#### `either`

```ts
import { strictEqual } from "node:assert/strict";

import { either as E, pipe } from "@clipboard-health/util-ts";

function double(n: number): number {
  return n * 2;
}

function inverse(n: number): E.Either<string, number> {
  return n === 0 ? E.left("Division by zero") : E.right(1 / n);
}

const result = pipe(
  E.right(5),
  E.map(double),
  E.flatMap(inverse),
  E.match(
    (error) => `Error: ${error}`,
    (r) => `Result is ${r}`,
  ),
);

strictEqual(result, "Result is 0.1");
```

## 地方发展指挥部

见 [`package.json`](https://npmcn.com/package/%40clipboard-health/util-ts/package.json) `scripts` 用于命令列表.

## 常见问题

### @clipboard-health/util-ts 是什么？

TypeScript 服务\.

### 如何安装 @clipboard-health/util-ts？

在项目目录运行 npm install @clipboard-health/util-ts，或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖：zod（^3）、type-fest（^4 \|\| ^5\.0\.0）。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖，Yarn 需要手动安装。

### @clipboard-health/util-ts 支持 TypeScript 吗？

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

### @clipboard-health/util-ts 支持 ESM 还是 CommonJS？

以 CommonJS 形式发布，可以直接 require\(\)；在 ES Module 项目中也可以用 import 引入（Node\.js 会把 module\.exports 作为默认导出）。

### @clipboard-health/util-ts 可以商用吗？

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

### @clipboard-health/util-ts 的下载量有多少？

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

### @clipboard-health/util-ts 还在维护吗？

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

