> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cbrock.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Guard vs Zod

> How ts-chas/guard compares to Zod.

## Overview

The Guard module is heavily inspired by [Zod](https://zod.dev); that's not a coincidence, and it's worth saying plainly. Zod's chainable schema builders, type inference from schemas, and developer experience set a high bar for validation libraries in the TypeScript ecosystem. Guard borrows those core ideas directly.

The differences are intentional design choices driven by how Guard fits into the broader ts-chas ecosystem, not attempts to replace Zod entirely. If your team already knows and loves Zod, that's a great reason to keep using it. If you're evaluating Guard, this page explains what's the same and what's different.

***

## What they share

Both Guard and Zod:

* Define schemas with a chainable, declarative API
* Infer TypeScript types from schemas via utility types (`InferGuard` / `z.infer`)
* Implement **Standard Schema v1** via the `~standard` property, so both work with tRPC, react-hook-form, Drizzle, and any other Standard Schema-compatible library
* Validate strings, numbers, booleans, objects, arrays, tuples, unions, intersections, enums, and literals
* Support custom validators and refinements
* Handle `null`, `undefined`, and optional fields

***

## Key differences

<AccordionGroup>
  <Accordion title="Guards as inline type predicates">
    In Zod, you call `.parse()` or `.safeParse()` to validate a value. The schema and the type predicate are separate concerns.

    In ts-chas, every guard **is** the type predicate. You can call it directly as a function, and TypeScript narrows the type at the call site. You most likely don't need a separate parse step for simple checks.

    ```typescript theme={null}
    // Zod
    if (z.string().email().safeParse(value).success) {
      // value is still typed as unknown here — Zod doesn't narrow in-place
    }

    // ts-chas: the guard itself is the type predicate (slightly less boilerplate)
    import { is } from 'ts-chas/guard';

    if (is.string.email(value)) {
      // value is narrowed to string here
      sendEmail(value); // TypeScript knows it's a string
    }
    ```

    This also works inline in type guard function signatures:

    ```typescript theme={null}
    function processEmail(v: unknown): v is string {
      return is.string.email(v);
    }
    ```

    You can use guards anywhere TypeScript expects a type predicate: in `if` statements, `Array.prototype.filter`, assertion functions, or as standalone validators.
  </Accordion>

  <Accordion title="Isomorphic hash validation — is.string.hash()">
    ts-chas includes cryptographic hash validation via [`@noble/hashes`](https://github.com/paulmillr/noble-hashes), a well-audited, zero-dependency cryptography library. The implementation is isomorphic: it works identically in browser, Node.js, and edge runtimes like Cloudflare Workers and Deno, with no polyfills required.

    ```typescript theme={null}
    import { is } from 'ts-chas/guard';

    // Validate that a string is a valid SHA-256 hex hash
    const isSha256 = is.string.hash({ alg: 'sha256', enc: 'hex' });
    isSha256('a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3'); // true

    // Verify a hash against plaintext (timing-safe comparison)
    const isValidHash = is.string.hash({ alg: 'sha256' }).verify('mypassword');
    isValidHash(someHashFromDb); // true or false, comparison is timing-safe

    // Supported algorithms: sha1, sha256, sha384, sha512, md5
    // Supported encodings:  hex, base64, base64url
    ```

    Zod has no built-in hash validation. You would need to implement this yourself using a separate library.
  </Accordion>

  <Accordion title="Result integration">
    When you call `.parse()` on a guard in ts-chas, it returns a `Result<T, GuardErr>` , not a Zod-style `{ success, data, error }` object. This means you can chain directly using the full `Result` API:

    ```typescript theme={null}
    import { is } from 'ts-chas/guard';

    const result = is.string.email.parse(input); // Result<string, GuardErr>

    result.map(email => sendWelcome(email));     // chain directly, no unwrapping needed
    result.mapErr(err => logValidationError(err));
    ```

    Guard `.parse()` is fail-fast: it returns on the first error. For collecting all errors across a nested structure, use `defineSchemas` (see below).
  </Accordion>

  <Accordion title="Schema parsing returns typed error arrays">
    When you define a schema with `defineSchemas` and call `.parse()`, you get `Result<T, AggregateGuardError>` : a class with an array of structured errors, one per failed field. Each error carries the field path, the expected type, and a human-readable message.

    ```typescript theme={null}
    import { is, defineSchemas } from 'ts-chas/guard';

    const schemas = defineSchemas({
      User: is.object({
        name: is.string.min(1),
        age: is.number.gt(0),
        email: is.string.email,
      }),
    });

    const result = schemas.User.parse({ name: '', age: -1, email: 'not-an-email' });

    if (result.isErr()) {
      for (const e of result.error.errors) {
        console.log(`${e.path.join('.')}: ${e.message}`);
        // User.name: Value "" failed validation
        // User.age: Value -1 failed validation
        // User.email: Value "not-an-email" failed validation
      }
    }
    ```

    This is comparable to Zod's `.safeParse()` returning `error.issues`, but ts-chas returns the errors as a typed array in a `Result`, rather than inside a discriminated union object.
  </Accordion>

  <Accordion title="Ecosystem integration">
    Guards are designed to compose with the rest of ts-chas — `Result`, `Task`, and `Option` — without any adapter layer:

    ```typescript theme={null}
    import { is } from 'ts-chas/guard';
    import { optionFromGuard } from 'ts-chas/option';

    // Convert a guard check directly into an Option
    const maybeEmail = optionFromGuard(rawInput, is.string.email);
    // Some(rawInput) if it's a valid email, None otherwise

    // Wrap a function so its inputs and output are validated at runtime
    const processAge = is
      .function({ input: [is.number.gte(18)], output: is.string })
      .implResult(age => `You are ${age} years old`);

    processAge(20); // Result.Ok('You are 20 years old')
    processAge(15); // Result.Err(GuardErr)
    ```

    This is unique to ts-chas.
  </Accordion>

  <Accordion title="Namespace extension">
    You can extend the `is` namespace with your own guards while retaining full access to the built-in guards:

    ```typescript theme={null}
    import { is } from 'ts-chas/guard';

    const myIs = is.extend({
      positiveEmail: (v: unknown): v is string =>
        is.string.email(v) && !v.includes('+'),
    });

    myIs.positiveEmail('user@example.com'); // true
    myIs.positiveEmail('user+tag@example.com'); // false
    myIs.string('still works');              // true — all built-ins are preserved
    ```

    This is similar in spirit to Zod's custom refinements, but `is.extend` creates a new `is` instance with a fully extended namespace, so you can share `myIs` across your codebase and call your custom guards exactly like built-in ones.
  </Accordion>
</AccordionGroup>

***

## When to use each

**Use Zod if:**

* You're in a Zod-centric codebase (tRPC v10 with Zod, Prisma Zod generators, etc.)
* Your team already knows Zod well and the switching cost isn't worth the gain
* You need features Guard doesn't yet have

**Use ts-chas/guard if:**

* You want inline type predicate usage without a separate parse step
* You need isomorphic hash validation without a third-party dependency
* You want tight integration with `Result`, `Task`, and `Option`
* You're building a new codebase and want a cohesive ecosystem

Both libraries implement Standard Schema v1, so migrating guards/schemas between them for form libraries (react-hook-form, TanStack Form) or tRPC is straightforward — swap the import and adjust the API calls.

***

<Note>
  The Guard module is actively inspired by Zod's excellent design. Many of the patterns you already know from Zod carry over directly. The two libraries are more complementary than competing: if you're evaluating ts-chas/guard, you'll find the mental model familiar, and the differences are additive rather than breaking.
</Note>
