基于断言的运行时数据验证的轻量级TypeScript库
A lightweight TypeScript library for assertion-based runtime data validation

原始链接: https://github.com/nimeshnayaju/decode-kit

## Decode-Kit:零依赖TypeScript验证 Decode-kit是一个轻量级、零依赖的TypeScript库,用于验证运行时数据,*无需*数据转换。它不是返回解析后的值,而是**就地断言类型**,直接细化原始变量的TypeScript类型——通过避免克隆或复制数据,从而提供显著的性能优势。 验证是基于断言的;`validate(input, schema)` 在运行时检查数据,并在失败时抛出`DecoderError`,提供详细的错误消息,包括失败路径和预期的模式。成功后,`input`的类型会根据模式进行收窄。 Decode-kit支持验证基本类型(字符串、数字、布尔值、null、undefined)、字面量、ISO 8601日期时间、数组和对象。它还允许使用`or`函数创建联合类型。一个关键特性是`InferOutputOf`工具,用于提取验证后的类型。 其快速失败的方法和最小的开销使其非常适合对性能要求严格的应用,在这些应用中,内存效率和速度至关重要。API的设计灵感来自Decoders等库。

## Decode-Kit: A Performant TypeScript Validation Library Decode-kit is a new TypeScript library for runtime data validation focused on performance and memory efficiency. Unlike popular libraries like Zod, it avoids deep cloning of data during validation, instead using an “assertion-based” approach that validates and narrows TypeScript types *in-place*. This can be a significant advantage in high-throughput applications. The library features a clear API and a structured error system with detailed paths to pinpoint validation failures. It also employs a “fail-fast” strategy, immediately stopping validation upon the first error for quicker results. Discussion centers on performance comparisons with other libraries (Zod, Valibot, Arktype) and the potential overhead of using `try/catch` blocks within the validation process. The author plans to share benchmarks and is open to feedback, particularly regarding the fail-fast approach and potential for automatic validator inference from TypeScript types. The project aims to complement the static type system by safely asserting types at I/O boundaries.
相关文章

原文

Lightweight, zero-dependency library for validating arbitrary runtime data in TypeScript. decode-kit provides assertion-based validation that refines your types in-place — no cloning, no transformations, and minimal runtime overhead.

decode-kit validates your data and narrows its type in-place. Your original values remain unchanged - only their TypeScript types are refined. The validate function runs a runtime check and, on success, asserts the original variable's type. On failure, it throws a DecoderError.

import { object, string, number, validate } from "decode-kit";

// Example of untrusted data (e.g., from an API)
const input: unknown = { id: 123, name: "Alice" };

// Validate the data (throws if validation fails)
validate(input, object({ id: number(), name: string() }));

// `input` is now typed as { id: number; name: string }
console.log(input.id, input.name);

Libraries like Zod, Valibot, Decoders, etc typically return a new (often transformed) value from .parse(). decode-kit instead asserts types directly - this provides several performance benefits as no time is spent copying arrays, strings or nested objects. This makes decode-kit ideal for performance-critical applications where memory efficiency and speed matter.

decode-kit uses a fail-fast approach - validation stops and throws an error immediately when the first validation failure is encountered. This provides better performance and clearer error messages by focusing on the first issue found. The validate function throws a DecoderError if validation fails. This error object includes the path to the exact location of the validation failure (e.g., nested objects/arrays), the expected schema and ruleset that the value failed to match. Additionally, the error includes a pre-formatted human-readable error message.

Example error message:

Validation failed at user.age due to schema mismatch; expected schema: {"type":"number"}

For more control over error messages, you can catch DecoderError and traverse its path property to build custom messages.

Validates that a value is a string. Optionally accepts rules for validation.

import { string, validate } from "decode-kit";

// Basic usage
validate(input, string());
// input is typed as string

// With rules
validate(input, string({ minLength: 3, maxLength: 50 }));

Rules:

  • minLength?: number - Minimum string length
  • maxLength?: number - Maximum string length

Validates that a value is a finite number.

import { number, validate } from "decode-kit";

// Basic usage
validate(input, number());
// input is typed as number

// With rules
validate(input, number({ min: 0, max: 100 }));

Rules:

  • min?: number - Minimum value (inclusive)
  • max?: number - Maximum value (inclusive)

Validates that a value is a boolean.

import { boolean, validate } from "decode-kit";

validate(input, boolean());
// input is typed as boolean

Validates that a value is exactly equal to a specific literal value.

import { constant, validate } from "decode-kit";

// String literals
validate(input, constant("hello"));
// input is typed as "hello"

// Number literals
validate(input, constant(42));
// input is typed as 42

// Boolean literals
validate(input, constant(true));
// input is typed as true

Validates that a value is null.

import { null_, validate } from "decode-kit";

validate(input, null_());
// input is typed as null

Validates that a value is undefined.

import { undefined_, validate } from "decode-kit";

validate(input, undefined_());
// input is typed as undefined

Validates that a value is a valid ISO 8601 datetime string with timezone.

import { iso8601, validate } from "decode-kit";

validate(input, iso8601());
// input is typed as Iso8601 (a branded string type)

// Valid examples:
// "2024-01-15T10:30:00Z"
// "2024-01-15T10:30:00+01:00"
// "2024-01-15T10:30:00.123Z"

Validates arrays with optional item validation and rules.

import { array, string, number, validate } from "decode-kit";

// Array of any values
validate(input, array());
// input is typed as unknown[]

// Array with minimum length
validate(input, array({ minLength: 1 }));

// Array of specific type
validate(input, array(string()));
// input is typed as string[]

// Array of specific type with rules
validate(input, array(number(), { minLength: 3 }));
// input is typed as number[] with at least 3 items

Rules:

  • minLength?: number - Minimum array length

Validates objects with optional property validation.

import { object, string, number, boolean, validate } from "decode-kit";

// Any object
validate(input, object());
// input is typed as Record<string, unknown>

// Object with specific shape
validate(input, object({ id: number(), name: string(), active: boolean() }));
// input is typed as { id: number; name: string; active: boolean }

// Nested objects
validate(
  input,
  object({
    user: object({
      id: number(),
      profile: object({ name: string(), age: number() }),
    }),
  })
);

Creates a union type decoder that accepts any of the provided decoders.

import { or, string, number, null_, validate } from "decode-kit";

// String or number
validate(input, or([string(), number()]));
// input is typed as string | number

// Nullable string (string or null)
validate(input, or([string(), null_()]));
// input is typed as string | null

// Multiple types
validate(
  input,
  or([constant("pending"), constant("active"), constant("completed")])
);
// input is typed as "pending" | "active" | "completed"

A type utility that extracts the output type from a decoder. Useful when you need to reference the type that a decoder validates.

import { object, string, number, InferOutputOf } from "decode-kit";

const userDecoder = object({ id: number(), name: string() });
type User = InferOutputOf<typeof userDecoder>;
// User is { id: number; name: string }

A branded string type for ISO 8601 date strings that can only be obtained after validating using the iso8601 decoder.

import { Iso8601 } from "decode-kit";

function formatDate(date: Iso8601): string {
  return new Date(date).toLocaleDateString();
}

The API is inspired by Decoders, which is also my go-to validation library. There is an in-progress pull request to support readonly decoders, which should bring similar benefits to the library.

联系我们 contact @ memedata.com