or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

built-in-validators.mddata-transformation.mderror-handling.mdgeneric-types.mdindex.mdscope-modules.mdstring-processing.mdtype-creation.md
tile.json

generic-types.mddocs/

Generic Types

Parameterized type system with constraints, validation, and full TypeScript integration for creating reusable type templates.

Capabilities

Generic Creation

Create parameterized types with parameter constraints.

/**
 * Create a generic type with parameters and constraints
 * @param params - Array of parameter definitions with optional constraints
 * @returns GenericBodyParser for defining the generic body
 */
function generic<const paramsDef extends array<GenericParamDef>>(
  ...params: {
    [i in keyof paramsDef]: paramsDef[i] extends readonly [infer name, infer def] ?
      readonly [name, type.validate<def>] :
      paramsDef[i]
  }
): GenericBodyParser<genericParamDefsToAst<paramsDef>, $>;

interface GenericBodyParser<params extends array<GenericParamAst>, $> {
  /** Define generic body using parameter names */
  <const body>(
    body: type.validate<body, $, baseGenericConstraints<params>>
  ): Generic<params, body, $>;
  
  /** Define generic using Higher-Kinded Type constructor */
  <hkt extends Hkt.constructor>(
    instantiateDef: LazyGenericBody<baseGenericResolutions<params, $>>,
    hkt: hkt
  ): Generic<params, InstanceType<hkt>, $>;
}

Usage Examples:

import { type, generic } from "arktype";

// Basic generic without constraints
const Container = generic("t")({
  value: "t",
  isEmpty: "boolean"
});

// Generic with constraints
const NumericContainer = generic(["n", "number"])({
  value: "n",
  doubled: "n",
  isPositive: "boolean"
});

// Multiple parameters with constraints
const KeyValuePair = generic(
  ["k", "string"],
  ["v", "unknown"]
)({
  key: "k", 
  value: "v",
  timestamp: "string.date.iso"
});

// Using the generics
const StringContainer = Container("string");
const IntContainer = NumericContainer("number.integer");
const UserKV = KeyValuePair("string", { name: "string", age: "number" });

Generic Interface

The Generic interface provides instantiation and introspection capabilities.

interface Generic<
  params extends array<GenericParamAst> = array<GenericParamAst>,
  bodyDef = unknown,
  $ = {},
  arg$ = $
> {
  /** Instantiate generic with arguments (up to 6 parameters supported) */
  <const args extends array>(
    ...args: validateGenericArgs<args, params, arg$>
  ): Type<instantiateGeneric<bodyDef, params, args, $, arg$>>;
  
  /** Parameter information */
  params: { [i in keyof params]: [params[i][0], Type<params[i][1], $>] };
  
  /** Parameter names array */
  names: genericParamNames<params>;
  
  /** Parameter constraint types */
  constraints: { [i in keyof params]: Type<params[i][1], $> };
  
  /** Body definition */
  bodyDef: bodyDef;
  
  /** Scope contexts */
  $: Scope<$>;
  arg$: Scope<arg$>;
  
  /** Internal representation and JSON */
  internal: GenericRoot;
  json: JsonStructure;
}

Usage Examples:

// Inspect generic properties
const Pair = generic(["a", "unknown"], ["b", "unknown"])({
  first: "a",
  second: "b"
});

console.log(Pair.names); // ["a", "b"]  
console.log(Pair.params); // [[a", Type<unknown>], ["b", Type<unknown>]]

// Instantiate with different types
const StringNumberPair = Pair("string", "number");
const BooleanArrayPair = Pair("boolean", "boolean[]");

// Access constraint information
const ConstrainedGeneric = generic(["t", "string | number"])({
  value: "t"
});

console.log(ConstrainedGeneric.constraints); // [Type<string | number>]

String-based Generic Syntax

Create generics using parameter string syntax similar to TypeScript.

/**
 * Create generic using parameter string syntax
 * @param params - Parameter string like "<t, n extends number>"
 * @param def - Body definition using parameter names
 * @returns Generic type constructor
 */
function type<
  const params extends ParameterString,
  const def
>(
  params: validateParameterString<params>,
  def: type.validate<def, {}, baseGenericConstraints<parseValidGenericParams<params>>>
): Generic<parseValidGenericParams<params>, def>;

type ParameterString<params extends string = string> = `<${params}>`;

// Common utility types  
type JsonStructure = object;
type UndeclaredKeyBehavior = "ignore" | "reject" | "delete";

Usage Examples:

// Single parameter
const Box = type("<t>", {
  value: "t",
  label: "string"
});

// Multiple parameters
const Result = type("<t, e>", {
  data: "t",
  error: "e",
  success: "boolean"
});

// Parameters with constraints
const NumericBox = type("<n extends number>", {
  value: "n",
  squared: "n"
});

const StringContainer = type("<s extends string>", {
  content: "s",
  length: "number"
});

// Complex constraints
const ComparableContainer = type("<t extends string | number | Date>", {
  value: "t",
  min: "t",
  max: "t"
});

// Usage
const StringBox = Box("string");
const ApiResult = Result("object", "string");
const IntBox = NumericBox("number.integer");

Generic Constraints

Define and validate parameter constraints to ensure type safety.

// Parameter constraint types
type GenericParamAst = readonly [name: string, constraint: unknown];

type baseGenericConstraints<params extends array<GenericParamAst>> = {
  [i in keyof params & `${number}` as params[i][0]]: params[i][1];
};

type validateGenericArg<arg, param extends GenericParamAst, $> =
  type.infer<arg, $> extends param[1] ? unknown :
  ErrorType<`Invalid argument for ${param[0]}`, [expected: param[1]]>;

Usage Examples:

// Constraint validation
const NumberContainer = type("<n extends number>", {
  value: "n"
});

// Valid instantiations
const IntContainer = NumberContainer("number.integer");
const FloatContainer = NumberContainer("number");

// Invalid - would cause TypeScript error
// const StringContainer = NumberContainer("string"); 

// Multiple constraints  
const ComparableCollection = type("<t extends string | number | Date>", {
  items: "t[]",
  min: "t",
  max: "t"
});

const StringCollection = ComparableCollection("string");
const NumberCollection = ComparableCollection("number");
const DateCollection = ComparableCollection("Date");

// Object constraints
const HasId = type("<t extends { id: string }>", {
  entity: "t",
  entityId: "string"
});

const UserContainer = HasId({ id: "string", name: "string", age: "number" });

Generic Instantiation Patterns

Different ways to instantiate and use generic types.

// Direct instantiation
type instantiateGeneric<
  def,
  params extends array<GenericParamAst>,
  args,
  $,
  args$
> = Type<
  [def] extends [Hkt] ?
    Hkt.apply<def, { [i in keyof args]: type.infer<args[i], args$> }> :
    inferDefinition<def, $, bindGenericArgs<params, args$, args>>
>;

// Argument binding
type bindGenericArgs<params extends array<GenericParamAst>, $, args> = {
  [i in keyof params & `${number}` as params[i][0]]: type.infer<
    args[i & keyof args],
    $
  >;
};

Usage Examples:

// Standard instantiation
const Pair = type("<a, b>", {
  first: "a",
  second: "b"
});

const StringNumberPair = Pair("string", "number");

// Nested generic usage
const Container = type("<t>", {
  value: "t",
  nested: {
    inner: "t"
  }
});

const NestedStringContainer = Container("string");

// Generic composition
const Response = type("<t>", {
  data: "t",
  status: "number.integer",
  message: "string"
});

const UserResponse = Response({
  id: "string.uuid",
  name: "string",
  email: "string.email"
});

// Array and collection generics
const Collection = type("<t>", {
  items: "t[]",
  count: "number.integer >= 0",
  isEmpty: "boolean"
});

const UserCollection = Collection({
  id: "string.uuid", 
  name: "string"
});

Generic Scope Integration

Using generics within scopes and modules.

// Generic definitions in scopes
const scopeWithGenerics = scope({
  "container<t>": {
    value: "t",
    metadata: {
      type: "string",
      created: "string.date.iso"
    }
  },
  
  "validation<t>": {
    data: "t",
    isValid: "boolean",
    errors: "string[]"
  },
  
  // Use generics within scope
  stringContainer: "container<string>",
  userValidation: "validation<{ name: string, age: number }>"
});

Usage Examples:

// Create scope with generic definitions
const apiScope = scope({
  "response<t>": {
    data: "t",
    status: "number.integer >= 200 <= 299",
    timestamp: "string.date.iso"
  },
  
  "paginated<t>": {
    items: "t[]",
    total: "number.integer >= 0",
    page: "number.integer >= 1",
    pageSize: "number.integer >= 1"
  },
  
  // Pre-instantiated common types
  userResponse: "response<{ id: string, name: string }>",
  paginatedUsers: "paginated<{ id: string, name: string }>"
});

// Use generics from scope
const ProductResponse = apiScope.type("response<{ id: string, price: number }>");
const PaginatedProducts = apiScope.type("paginated<{ id: string, name: string, price: number }>");

Higher-Kinded Types (HKT)

Advanced generic patterns using Higher-Kinded Types for complex transformations.

// HKT support for advanced generic patterns
abstract class Hkt<args extends array = array> {
  abstract body: unknown;
  readonly args!: args;
  
  // Apply HKT with type arguments
  static apply<hkt extends Hkt, args extends array>(
    hkt: hkt,
    args: args
  ): hkt["body"];
}

// Custom HKT example
class ArrayHkt extends Hkt<[unknown]> {
  declare body: Array<this[0]>;
}

Usage Examples:

// Using HKT for complex generic patterns
class ContainerHkt extends Hkt<[unknown]> {
  declare body: {
    value: this[0];
    label: string;
    created: Date;
  };
}

const Container = generic("t")(
  (args) => ({
    value: args.t,
    label: "string",
    created: "Date"
  }),
  ContainerHkt
);

// Advanced transformation patterns
class ValidationHkt extends Hkt<[unknown]> {
  declare body: {
    data: this[0];
    isValid: boolean;
    errors: string[];
    validated: Date;
  };
}

const Validator = generic(["t", "object"])(
  (args) => ({
    data: args.t,
    isValid: "boolean",
    errors: "string[]",
    validated: "Date"
  }),
  ValidationHkt
);