Parameterized type system with constraints, validation, and full TypeScript integration for creating reusable type templates.
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" });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>]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");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" });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"
});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 }>");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
);