@fishka/assertions

中文文档
1.0.1发布于 2026/01/09MITTypeScriptESM + CJS零依赖近 30 天下载 6188

类型脚本的测试和验证

中文使用文档
文档目录33 节
  1. 特征
  2. 安装
  3. 快速启动
  4. 核心概念
  5. 请求与验证
  6. 常用案例
  7. 1. 典型类型
  8. 2. 格式请求
  9. 3. 对象验证
  10. 4. 阵列验证
  11. 5. 记录(词典)验证
  12. 6. 自定义请求
  13. 7. 用户输入验证(不投掷)
  14. 8. API 验证
  15. API 参考
  16. 原始请求
  17. 格式请求
  18. 结构请求
  19. 工厂功能
  20. 校验函数( 非抛出)
  21. 类型预测( 检查)
  22. 核心职能
  23. 高级特性
  24. 上下文传播出错
  25. 类型脚本编译-时间安全
  26. 自定义出错厂
  27. 常见模式
  28. 正在验证配置文件
  29. 表格验证
  30. 运行时间类型缩小
  31. 为什么?
  32. 许可证
  33. 捐款

@fishka/ servitions (英语)

TypeScript 的安全运行时间断言和验证,并附有编译时间验证.

特征

  • 类型安全 - 完整的脚本支持 asserts 类型警卫
  • 编译时间检查 - 对象评估确保所有字段都覆盖
  • 两种模式 - 请求(推出错误)或验证(返回错误字符串)
  • 零依赖关系 - 轻而自足
  • 可编曲 - 从简单的构件中建造复杂的验证器
  • 丰富的制约因素 - 矩阵长度、独特性、字符串模式、跨字段验证
  • 可动摇的树 - 只导入你需要的东西

安装

npm install @fishka/assertions

或者说

yarn add @fishka/assertions

快速启动

import { assertString, assertNumber, assertObject } from '@fishka/assertions';

// Basic type assertions
assertString('hello'); // ✓ passes
assertNumber(42); // ✓ passes
assertString(123); // ✗ throws: "Not a string <number:123>"

// Object validation with compile-time type safety
interface User {
  name: string;
  age: number;
}

const userAssertion: ObjectAssertion<User> = {
  name: assertString,
  age: assertNumber,
};

const data: unknown = { name: 'Alice', age: 30 };
assertObject(data, userAssertion);
// TypeScript now knows `data` is User type
console.log(data.name); // ✓ type-safe

核心概念

请求与验证

请求 扔出错误并使用类型脚本 asserts 类型守护 :

import { assertString, assertObject } from '@fishka/assertions';

function processUser(data: unknown) {
  assertObject(data, userAssertion); // throws if invalid
  // TypeScript now knows data is User
  console.log(data.name.toUpperCase());
}

验证 不丢弃返回错误消息 :

import { validateObject } from '@fishka/assertions';

function validateUserInput(input: unknown) {
  const error = validateObject(input, userAssertion);
  if (error) {
    return { success: false, error };
  }
  return { success: true, data: input }; // TypeScript knows input is User
}

使用 主张 用于内部一致性检查。 使用 审定 用于用户输入或需要优雅处理错误.

常用案例

1. 典型类型

import { assertString, assertNumber, assertBoolean, assertDate, assertNonNullable } from '@fishka/assertions';

assertString('hello'); // ✓
assertNumber(42); // ✓
assertBoolean(true); // ✓
assertDate(new Date()); // ✓
assertNonNullable('value'); // ✓
assertNonNullable(null); // ✗ throws: "Value is null"

2. 格式请求

import { assertUuid, assertEmail, assertHexString } from '@fishka/assertions';

assertUuid('550e8400-e29b-41d4-a716-446655440000'); // ✓
assertEmail('[email protected]'); // ✓
assertHexString('a1b2c3'); // ✓

3. 对象验证

基本对象

import { assertObject, type ObjectAssertion } from '@fishka/assertions';

interface User {
  name: string;
  age: number;
  email: string;
}

const userAssertion: ObjectAssertion<User> = {
  name: assertString,
  age: assertNumber,
  email: assertEmail,
};

assertObject({ name: 'Alice', age: 30, email: '[email protected]' }, userAssertion);

可选字段

import { undefinedOr, nullOr } from '@fishka/assertions';

interface Profile {
  name: string;
  nickname?: string; // optional
  bio: string | null; // nullable
}

const profileAssertion: ObjectAssertion<Profile> = {
  name: assertString,
  nickname: undefinedOr(assertString), // allows undefined
  bio: nullOr(assertString), // allows null
};

嵌入对象

interface Address {
  street: string;
  city: string;
}

interface User {
  name: string;
  address: Address;
}

const addressAssertion: ObjectAssertion<Address> = {
  street: assertString,
  city: assertString,
};

const userAssertion: ObjectAssertion<User> = {
  name: assertString,
  address: addressAssertion, // nested object
};

跨外地验证

const userAssertion: ObjectAssertion<User> = {
  name: assertString,
  age: assertNumber,
  email: assertEmail,
  $o: user => {
    // Custom validation after all fields pass
    assertTruthy(user.age >= 18, 'User must be 18 or older');
  },
};

严格模式( 无额外字段)

assertObject(
  { name: 'Alice', age: 30, unknownField: 'oops' },
  userAssertion,
  undefined,
  { failOnUnknownFields: true }, // ✗ throws: "property can't be checked: unknownField"
);

4. 阵列验证

基本阵列

import { assertArray, arrayAssertion } from '@fishka/assertions';

assertArray(['a', 'b', 'c'], assertString); // ✓
assertArray([1, 2, 3], assertNumber); // ✓

// Or create reusable array assertion
const stringArrayAssertion = arrayAssertion(assertString);
assertArray(['x', 'y'], stringArrayAssertion);

矩阵限制

// Length constraints
assertArray([1, 2, 3], assertNumber, { minLength: 2, maxLength: 5 }); // ✓
assertArray([1], assertNumber, { minLength: 2 }); // ✗ throws

// Uniqueness check
assertArray(
  ['a', 'b', 'c'],
  assertString,
  { uniqueByIdentity: v => v }, // ✓
);

assertArray(
  ['a', 'a', 'b'],
  assertString,
  { uniqueByIdentity: v => v }, // ✗ throws: "array contains non-unique elements"
);

对象中的矩阵

import { undefinedOr } from '@fishka/assertions';

interface User {
  name: string;
  tags?: string[];
}

const userAssertion: ObjectAssertion<User> = {
  name: assertString,
  tags: undefinedOr(arrayAssertion(assertString)),
};

5. 记录(词典)验证

import { assertRecord, recordAssertion } from '@fishka/assertions';

// Record<string, number>
assertRecord({ a: 1, b: 2, c: 3 }, assertNumber); // ✓

// With key validation
assertRecord(
  { '[email protected]': 'Alice' },
  assertString,
  { keyAssertion: assertEmail }, // validates keys are emails
);

// Ensure key matches field in value
assertRecord(
  {
    user123: { id: 'user123', name: 'Alice' },
    user456: { id: 'user456', name: 'Bob' },
  },
  objectAssertion({ id: assertString, name: assertString }),
  { keyField: 'id' }, // ensures record key === value.id
);

6. 自定义请求

已检查的自定义请求

import { $a } from '@fishka/assertions';

// Create custom assertion with type checking
const assertPositive = $a<number>(value => value > 0, 'Must be positive');

assertPositive(42); // ✓
assertPositive(-5); // ✗ throws: "Check is failed: Must be positive"

自定义请求

import { $u } from '@fishka/assertions';

const assertValidDate = $u(value => value instanceof Date && !isNaN(value.getTime()), 'Must be a valid Date');

assertValidDate(new Date()); // ✓
assertValidDate(new Date('invalid')); // ✗ throws

字符串限制

import { stringAssertion } from '@fishka/assertions';

const passwordAssertion = stringAssertion({
  minLength: 8,
  maxLength: 64,
});

passwordAssertion('short'); // ✗ throws: "length is too small: 5 < 8"
passwordAssertion('validpassword'); // ✓

7. 用户输入验证(不投掷)

import { validateObject } from '@fishka/assertions';

function handleFormSubmit(formData: unknown) {
  const error = validateObject(formData, userAssertion);

  if (error) {
    // Show error to user
    console.error('Validation failed:', error);
    return { success: false, error };
  }

  // formData is now typed as User
  saveUser(formData);
  return { success: true };
}

8. API 验证

备选案文1:API 请求验证(Express.js Server)

验证服务器侧收到的 HTTP 请求 :

import express from 'express';
import { validateObject, assertObject, type ObjectAssertion } from '@fishka/assertions';

interface CreateUserRequest {
  name: string;
  email: string;
  age: number;
  tags?: string[];
}

const createUserRequestAssertion: ObjectAssertion<CreateUserRequest> = {
  name: assertString,
  email: assertEmail,
  age: assertNumber,
  tags: undefinedOr(arrayAssertion(assertString)),
};

const app = express();
app.use(express.json());

// POST /users - Create new user
app.post('/users', (req, res) => {
  // Validate request body
  const error = validateObject(req.body, createUserRequestAssertion);
  if (error) {
    return res.status(400).json({ success: false, error: `Invalid request: ${error}` });
  }
  // req.body is now typed as CreateUserRequest
  const user = createUser(req.body);
  res.status(201).json({ success: true, data: user });
});

// GET /users/:id - Get user by ID
app.get('/users/:id', (req, res) => {
  const userId = req.params.id;
  // Validate UUID format
  assertUuid(userId, 'userId');
  const user = getUserById(userId);
  res.json({ success: true, data: user });
});

备选案文2:API 响应验证(客户端)

验证客户端的API响应,以确保类型安全 :

interface ApiResponse<T> {
  success: boolean;
  data: T;
  error?: string;
}

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

interface UsersListResponse {
  users: User[];
  meta: {
    total: number;
    page: number;
    pageSize: number;
  };
}

// Define response assertion
const userAssertion: ObjectAssertion<User> = {
  id: assertUuid,
  name: assertString,
  email: assertEmail,
  createdAt: assertString,
};

const usersListResponseAssertion: ObjectAssertion<UsersListResponse> = {
  users: arrayAssertion(userAssertion),
  meta: {
    total: assertNumber,
    page: assertNumber,
    pageSize: assertNumber,
  },
};

// Client function to fetch users
async function fetchUsers(page: number = 1): Promise<UsersListResponse> {
  const response = await fetch(`/api/users?page=${page}`);
  const data: unknown = await response.json();

  // Validate response structure
  assertObject(data, usersListResponseAssertion);

  // Now data is fully typed as UsersListResponse!
  return data;
}

// Usage
const { users, meta } = await fetchUsers(1);
console.log(`Loaded ${users.length} of ${meta.total} users`);

奖金:快递的中间软件模式

创建可重复使用的验证中间软件 :

import { Request, Response, NextFunction } from 'express';
import { validateObject, type ObjectAssertion } from '@fishka/assertions';

// Generic validation middleware
function validateBody<T>(assertion: ObjectAssertion<T>) {
  return (req: Request, res: Response, next: NextFunction) => {
    const error = validateObject(req.body, assertion);

    if (error) {
      return res.status(400).json({
        success: false,
        error: `Validation failed: ${error}`,
      });
    }

    // Body is valid, continue to route handler
    next();
  };
}

// Use the middleware
app.post('/users', validateBody(createUserRequestAssertion), (req, res) => {
  // req.body is typed and validated
  const user = createUser(req.body);
  res.status(201).json({ success: true, data: user });
});

API 参考

原始请求

assertString(value: unknown, context?: string): asserts value is string
assertNumber(value: unknown, context?: string): asserts value is number
assertBoolean(value: unknown, context?: string): asserts value is boolean
assertDate(value: unknown, context?: string): asserts value is Date
assertNonNullable<T>(value: T, context?: string): asserts value is NonNullable<T>

格式请求

assertUuid(value: unknown, context?: string): asserts value is string
assertHexString(value: unknown, context?: string): asserts value is string
assertEmail(value: unknown, context?: string): asserts value is string

结构请求

assertObject<T>(
  value: unknown,
  assertion: ObjectAssertion<T>,
  context?: string,
  constraints?: { failOnUnknownFields?: boolean; allowedUnknownFieldNames?: string[] }
): asserts value is T

assertArray<T>(
  value: unknown,
  elementAssertion: Assertion<T>,
  constraints?: { minLength?: number; maxLength?: number; uniqueByIdentity?: (el: T) => string },
  context?: string
): asserts value is Array<T>

assertRecord<T>(
  value: unknown,
  valueAssertion: Assertion<T>,
  constraints?: { keyAssertion?: (key: string) => void; keyField?: string },
  context?: string
): asserts value is Record<string, T>

工厂功能

arrayAssertion<T>(elementAssertion: Assertion<T>, constraints?): ValueAssertion<Array<T>>
recordAssertion<T>(valueAssertion: Assertion<T>, constraints?): ValueAssertion<Record<string, T>>
objectAssertion<T>(objAssertion: ObjectAssertion<T>): ValueAssertion<T>

$a<T>(checkFn: (value: T) => boolean, errorMsg?: string): ValueAssertion<T>
$u(checkFn: (value: unknown) => boolean, errorMsg?: string): ValueAssertion<unknown>

undefinedOr<T>(assertion: Assertion<T>): Assertion<T | undefined>
nullOr<T>(assertion: Assertion<T>): Assertion<T | null>
valueOr<T>(expectedValue: T, orAssertion: Assertion<T>): Assertion<T>

stringAssertion(constraints: { minLength?: number; maxLength?: number }): ValueAssertion<string>

校验函数( 非抛出)

validateObject<T>(value: unknown, assertion: ObjectAssertion<T>, context?, constraints?): string | undefined
validateArray<T>(value: unknown, elementAssertion: Assertion<T>, constraints?, context?): string | undefined
validateRecord<T>(value: unknown, valueAssertion: Assertion<T>, constraints?, context?): string | undefined

类型预测( 检查)

isString(value: unknown): value is string
isNumber(value: unknown): value is number
isBoolean(value: unknown): value is boolean
isDate(value: unknown): value is Date
isUuid(value: unknown): value is string
isHexString(value: unknown): value is string
isEmail(value: unknown, constraints?): value is string
isNonNullable<T>(value: T): value is NonNullable<T>

核心职能

assertTruthy(value: unknown, error?: string | (() => string)): asserts value
fail(error?: string | (() => string)): never  // throws assertion error

高级特性

上下文传播出错

错误自动包含到失败字段的全部路径 :

const data = {
  user: {
    profile: {
      age: 'twenty', // wrong type
    },
  },
};

assertObject(data, userAssertion, 'apiResponse');
// throws: "apiResponse.user.profile.age: Not a number <string:twenty>"

类型脚本编译-时间安全

ObjectAsertion 正在编译-时间验证 - TypeScript 确保所有字段覆盖:

interface User {
  name: string;
  age: number;
  email: string;
}

const userAssertion: ObjectAssertion<User> = {
  name: assertString,
  age: assertNumber,
  // ✗ TypeScript error: Property 'email' is missing
};

自定义出错厂

覆盖所有断言的默认错误厂:

import { setDefaultAssertionErrorFactory } from '@fishka/assertions';

setDefaultAssertionErrorFactory((message: string) => {
  return new CustomError(message);
});

常见模式

正在验证配置文件

interface AppConfig {
  port: number;
  host: string;
  database: {
    url: string;
    poolSize: number;
  };
  features: string[];
}

const configAssertion: ObjectAssertion<AppConfig> = {
  port: assertNumber,
  host: assertString,
  database: {
    url: assertString,
    poolSize: assertNumber,
  },
  features: arrayAssertion(assertString),
};

const config: unknown = JSON.parse(configFileContents);
assertObject(config, configAssertion);
// config is now fully typed and validated

表格验证

function validateLoginForm(formData: FormData): { error?: string; data?: LoginData } {
  const input = {
    email: formData.get('email'),
    password: formData.get('password'),
  };

  const error = validateObject(input, loginAssertion);
  if (error) {
    return { error };
  }

  return { data: input };
}

运行时间类型缩小

function processValue(value: unknown) {
  if (isString(value)) {
    // TypeScript knows value is string
    console.log(value.toUpperCase());
  } else if (isNumber(value)) {
    // TypeScript knows value is number
    console.log(value.toFixed(2));
  }
}

为什么?

  • 类型安全: 存储类型Script的编译时间和运行时间安全类型系统
  • 开发者经验: 以完整上下文路径清除错误消息
  • 无依赖: 没有外部依赖, 最小捆绑大小
  • 弹性: 在投掷断言或返回错误消息之间选择
  • 可编曲: 从简单的构件构建复杂的验证器
  • 作战测试: 附有111+测试的综合测试套件

许可证

麻省理工学院

捐款

欢迎提出问题和拉动请求 https://gitea.com/fishka/assertions (英语)

版本与发布记录

@fishka/assertions 共发布 2 个版本,首次发布于 2026/01/06;过去 12 个月发布了 2 个版本。

版本发布日期距今
1.0.1latest2026/01/0910 个月前
1.0.02026/01/0610 个月前

关于 @fishka/assertions 的常见问题

@fishka/assertions 是什么?

类型脚本的测试和验证

如何安装 @fishka/assertions?

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

@fishka/assertions 支持 TypeScript 吗?

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

@fishka/assertions 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@fishka/assertions 需要什么版本的 Node.js?

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

@fishka/assertions 可以商用吗?

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

@fishka/assertions 的下载量有多少?

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

@fishka/assertions 还在维护吗?

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