Typanion

repository·main·Indexed 42 days ago

https://github.com/arcanis/typanion

A zero-dependency runtime TypeScript validator library for static and runtime type assertion. It provides a functional API to define schemas, validate data structures, and automatically derive TypeScript types using t.InferType. Features include type predicates, data coercion via t.as, detailed validation error reporting, and cascading predicates for complex constraints like range checks, string formats (ISO 8601, UUID 4), and object key relationships.

Tokens
5.6K
Snippets
40
Records
44
Agent score
54%

What's inside Typanion

  1. Overview of Typanion

    main
    Typanion is a runtime validation library designed to integrate deeply with TypeScript's type system. It allows you to describe data structures using composable operators. From these descriptions, Typanion automatically derives both validation protocols (to check data at runtime) and type hints (to provide static type safety during development).
  2. How cascading predicates work in Typanion

    main

    Cascading predicates are used to assert that a value follows a specific pattern or constraint without contributing to the refinement of the value's type. Because they do not change the inferred type, they are primarily used for validation checks. To use them effectively, you should compose them using the cascade method.

    For example, if you want to validate that a value is a string and also matches a specific pattern, you would use a type-refining predicate followed by a cascading predicate via cascade.

  3. Compose nested schemas

    main

    Schemas can be composed by nesting them within other schemas. For example, you can use t.isArray(otherSchema) to validate an array of objects defined by another schema.

    import * as t from 'typanion';
    
    const isActor = t.isObject({
        name: t.isString();
    });
    
    const isMovie = t.isObject({
        title: t.isString(),
        description: t.isString(),
        actors: t.isArray(isActor),
    });
  4. Validate unknown data and infer types

    main

    Use a Typanion validator to check unknown data. If the data matches the schema, Typanion provides a type refinement. You can also use t.InferType to extract the TypeScript type from the validator to ensure your application code stays in sync with your runtime schemas.

    import * as t from 'typanion';
    
    const isBlogPost = t.isObject({
        title: t.isString(),
        description: t.isString(),
        published: t.isBoolean(),
    });
    
    declare const userData: unknown;
    
    // 1. Runtime validation with type refinement
    if (isBlogPost(userData)) {
        // userData is now typed as the inferred BlogPost type
        console.log(userData.title);
    }
    
    // 2. Type inference for use elsewhere in your app
    type BlogPost = t.InferType<typeof isBlogPost>;
    import * as t from 'typanion';
    
    const isBlogPost = t.isObject({
        title: t.isString(),
        description: t.isString(),
        published: t.isBoolean(),
    });
    
    declare const userData: unknown;
    
    if (isBlogPost(userData)) {
        console.log(userData.title);
    }
    
    type BlogPost = t.InferType<typeof isBlogPost>;
  5. Define data structures with Typanion operators

    main

    You can compose prebuilt Typanion operators to describe complex data structures. These operators create a validator function that can be used to check unknown data at runtime.

    import * as t from 'typanion';
    
    const isBlogPost = t.isObject({
        title: t.isString(),
        description: t.isString(),
        published: t.isBoolean(),
    });
  6. Define and use schemas for type assertion

    main

    Typanion allows you to define schemas using built-in operators (like t.isObject, t.isString, etc.). Once a schema is defined, you can call it as a function to validate unknown data. Because Typanion uses TypeScript type predicates, successful validation automatically narrows the type within the conditional block.

    import * as t from 'typanion';
    
    const isMovie = t.isObject({
        title: t.isString(),
        description: t.isString(),
    });
    
    const userData = JSON.parse(input);
    
    if (isMovie(userData)) {
        // userData is now narrowed to the inferred Movie type
        console.log(userData.title);
    }
  7. Use t.cascade for complex validation logic

    main

    The t.cascade function allows you to chain multiple validation steps. It first checks a primary predicate (the first argument). If that predicate returns true, it then executes the subsequent predicates provided in an array. This is useful for building complex validation rules where subsequent checks depend on the success of the first, such as ensuring a value is a number before checking if it falls within a specific integer range.

    import * as t from 'typanion';
    
    const isPort = t.cascade(t.isNumber(), [
        t.isInteger(),
        t.isInInclusiveRange(1, 65535),
    ]);
    
    // If isPort(value) returns true, the value is a number, an integer, 
    // and within the range 1-65535.
  8. Validate an object with specific fields and unknown extra fields

    main

    Use t.isObject to validate that an object contains specific keys with specific types. To allow the object to contain other fields not defined in your schema, use the extra option. Setting extra: t.isUnknown() allows any additional fields to exist regardless of their type.

    const isDiv = t.isObject({
        tagName: t.isLiteral(`DIV"),
    }, {
        extra: t.isUnknown(),
    });
    
    isDiv({tagName: `div`, appendChild: () => {}});
  9. Validate an object with specific fields and a dictionary of typed extra fields

    main

    Use t.isObject combined with t.isDict to validate an object that has a known set of keys, while ensuring all other (extra) keys follow a specific type pattern. For example, you can require a uid string and ensure all other properties in the object are numbers.

    const isModel = t.isObject({
        uid: t.isString(),
    }, {
        extra: t.isDict(t.isNumber()),
    });
    
    isModel({uid: `foo`, valA: 12, valB: 24});
  10. Validate a value using multiple constraints with applyCascade

    main

    Use t.applyCascade to chain multiple validation predicates together. The first predicate is the base validator, and the subsequent predicates in the array are applied sequentially. If any predicate in the cascade fails, the entire validation fails. This is useful for complex logic like checking if a number is both an integer and within a specific range (e.g., a port number).

    const isPort = t.applyCascade(t.isNumber(), [
        t.isInteger(),
        t.isInInclusiveRange(1, 65535),
    ]);
    
    isPort(42000);