ts-type-forge is a comprehensive TypeScript type utility library that provides powerful type-level operations with zero runtime cost. It enhances TypeScript development by offering advanced type manipulations, strict type checking utilities, and comprehensive type safety features.
This library offers a comprehensive suite of type-level utilities, including:
StrictExclude, StrictOmit, ReadonlyRecord, and many more.Int, Uint, SafeInt, FiniteNumber, etc.) for enhanced type safety.List and Tuple namespaces for complex array manipulations.DeepReadonly, DeepPartial, and advanced path-based record updates.UintRange), and mathematical type computations.npm add --save-dev ts-type-forge
Or with other package managers:
# Yarn
yarn add --dev ts-type-forge
# pnpm
pnpm add --save-dev ts-type-forge
ts-type-forge works best with strict TypeScript settings:
{
"compilerOptions": {
"strict": true, // important
"noUncheckedIndexedAccess": true, // important
"noPropertyAccessFromIndexSignature": true, // important
"noFallthroughCasesInSwitch": true,
"noImplicitOverride": true,
"noImplicitReturns": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"allowUnreachableCode": false,
"allowUnusedLabels": false,
"exactOptionalPropertyTypes": false
}
}
There are two ways to use the types provided by ts-type-forge:
Explicit Imports (Recommended — side-effect free):
Import the types you need by name. Only the imported types come into scope; nothing else from ts-type-forge is added to the global namespace, so the package can be loaded without affecting the rest of your project.
// src/types/dice.ts
import { type UintRange } from 'ts-type-forge';
export type DiceValue = UintRange<1, 7>; // 1 | 2 | 3 | 4 | 5 | 6
Triple-Slash Directive (opt-in to ambient access):
When you prefer ambient access, add /// <reference types="ts-type-forge/global" /> to any .ts file in your project (e.g., globals.d.ts or at the top of a frequently used file included in the tsconfig.json). This makes every type provided by ts-type-forge globally available throughout your project — useful for prototyping or for projects that already rely on ambient typings.
// src/globals.d.ts or any other .ts file
/// <reference types="ts-type-forge/global" />
// src/types/dice.ts
// No import needed
export type DiceValue = UintRange<1, 7>; // 1 | 2 | 3 | 4 | 5 | 6
Essential type-level conditional logic for advanced type operations.
TypeEq for exact type matchingTypeExtends for subtype relationshipsIsUnion for union type identificationIsNever for never type checkingAdvanced object type manipulations with strict type safety.
StrictOmit, StrictPick, StrictExclude with key validationDeepReadonly, DeepPartial, DeepRequiredPartiallyPartial, PartiallyOptional, PartiallyRequiredRecordPaths, RecordValueAtPath for type-safe property accessComprehensive branded types for enhanced numeric type safety.
Int, Uint, SafeInt, FiniteNumberInt16, Int32, Uint16, Uint32PositiveInt, NonZeroInt, NonNegativeIntFloat32, Float64 with proper constraintsType-safe array and tuple utilities with functional programming patterns.
NonEmptyArray, ArrayOfLength, ArrayAtLeastLenMathematical operations performed entirely at the type level.
Increment, Decrement, AbsoluteValueUintRange, UintRangeInclusive for precise numeric constraintsMax, Min for type-level comparisonsPre-defined type constants for common use cases.
Primitive, FalsyValue, UnknownRecordHTTPRequestMethod for web developmentMonthEnum, DateEnum, HoursEnum, etc.Here are detailed examples showcasing the power of ts-type-forge's type utilities.
For a comprehensive list of all available types and their detailed documentation, please refer to the API Reference section.
TypeEq and TypeExtendsThe type utilities allow you to perform complex type checking and assertions at compile time.
// No import needed if using triple-slash directive
// import type { TypeEq, TypeExtends } from 'ts-type-forge'; // if importing explicitly
type User = { id: number; name: string };
type Admin = { id: number; name: string; role: 'admin' };
// Check exact type equality
type IsExactMatch = TypeEq<User, Admin>; // false
type IsSameType = TypeEq<User, User>; // true
// Check type extension relationships
type AdminExtendsUser = TypeExtends<Admin, User>; // true
type UserExtendsAdmin = TypeExtends<User, Admin>; // false
// Use in conditional types
type GetUserType<T> =
TypeExtends<T, Admin> extends true
? 'admin'
: TypeExtends<T, User> extends true
? 'user'
: 'unknown';
type AdminType = GetUserType<Admin>; // 'admin'
type UserType = GetUserType<User>; // 'user'
DeepReadonly and DeepPartialTransform nested object types with precise control over mutability and optionality.
type Config = {
port: number;
database: {
host: string;
port: number;
credentials?: {
user: string;
pass: string;
};
};
features: string[];
};
// Create a type where all properties, nested or not, are readonly
type ReadonlyConfig = DeepReadonly<Config>;
const config: ReadonlyConfig = {
port: 8080,
database: {
host: 'localhost',
port: 5432,
credentials: {
user: 'admin',
pass: 'secret',
},
},
features: ['featureA', 'featureB'],
};
// @ts-expect-error Cannot assign to 'port' because it is a read-only property
config.port = 8081;
// @ts-expect-error Cannot assign to 'host' because it is a read-only property
config.database.host = 'remote';
// @ts-expect-error Property 'push' does not exist on type 'readonly string[]'
config.features.push('featureC');
// Create a type where all properties are optional (useful for partial updates)
type PartialConfig = DeepPartial<Config>;
const partialUpdate: PartialConfig = {
database: {
host: 'new-host', // Only update specific fields
// port and credentials are optional
},
// port and features are optional
};
StrictOmitEnhanced versions of built-in Omit utility that provide compile-time key validation.
type UserProfile = Readonly<{
id: string;
username: string;
email: string;
lastLogin: Date;
bio?: string;
}>;
// StrictOmit ensures keys actually exist in the source type
type UserCreationData = StrictOmit<UserProfile, 'id' | 'lastLogin'>;
expectType<
UserCreationData,
// Result:
Readonly<{
username: string;
email: string;
bio?: string | undefined;
}>
>('=');
const newUser: UserCreationData = {
username: 'jane_doe',
email: 'jane@example.com',
bio: 'Software Developer', // Optional
};
// @ts-expect-error 'nonExistentKey' doesn't exist:
type InvalidOmit = StrictOmit<UserProfile, 'id' | 'nonExistentKey'>;
NonEmptyArray and List OperationsGuarantee array constraints and perform type-safe operations on collections.
type Post = Readonly<{
title: string;
content: string;
}>;
// A blog must have at least one post
type Blog = Readonly<{
name: string;
posts: NonEmptyArray<Post>; // Ensures posts array is never empty
}>;
const myBlog: Blog = {
name: 'My Tech Journey',
posts: [
// This array must have at least one element
{ title: 'First Post', content: 'Hello world!' },
{ title: 'Understanding TypeScript', content: '...' },
],
};
// This would cause a type error:
const emptyBlog: Blog = {
name: 'Empty Thoughts',
// @ts-expect-error Source has 0 element(s) but target requires 1
posts: [],
};
const getFirstPostTitle = (posts: NonEmptyArray<Post>): string =>
posts[0].title; // Safe to access posts[0] - guaranteed to exist
// Advanced List operations at the type level
type NumberList = readonly [1, 2, 3, 4, 5];
type FirstElement = List.Head<NumberList>; // 1
type LastElement = List.Last<NumberList>; // 5
type WithoutFirst = List.Tail<NumberList>; // readonly [2, 3, 4, 5]
type FirstThree = List.Take<3, NumberList>; // readonly [1, 2, 3]
type Reversed = List.Reverse<NumberList>; // readonly [5, 4, 3, 2, 1]
// Combine operations
type LastThreeReversed = List.Reverse<List.TakeLast<3, NumberList>>; // readonly [5, 4, 3]
JsonValueSafely represent and work with JSON data structures.
const jsonString =
'{"name": "Alice", "age": 30, "isAdmin": false, "tags": ["user", "active"], "metadata": null}';
try {
// Cast the result of JSON.parse to JsonValue for type safety
const parsedData = JSON.parse(jsonString) as JsonValue;
// Use type guards to safely work with parsed data
if (
typeof parsedData === 'object' &&
parsedData !== null &&
!Array.isArray(parsedData)
) {
// parsedData is now known to be JsonObject
const jsonObj = parsedData as JsonObject;
console.log(jsonObj['name']); // Access properties safely
if (typeof jsonObj['age'] === 'number') {
console.log(`Age: ${jsonObj['age']}`);
}
if (Array.isArray(jsonObj['tags'])) {
for (const tag of jsonObj['tags']) {
if (typeof tag === 'string') {
console.log(`Tag: ${tag}`);
}
}
}
} else if (Array.isArray(parsedData)) {
// parsedData is a JSON array
for (const item of parsedData) {
console.log(item);
}
}
} catch (error) {
console.error('Failed to parse JSON:', error);
}
// Define API response types using JsonValue
type ApiResponse = JsonObject &
Readonly<{
status: 'success' | 'error';
data?: JsonValue;
message?: string;
}>;
UintRange and Branded TypesDefine exact numeric constraints and enhance type safety with branded number types.
/**
* Parse integer with constrained radix parameter
* @param str A string to convert into a number
* @param radix A value between 2 and 36 that specifies the base
*/
export const parseInteger = (str: string, radix?: UintRange<2, 37>): number =>
Number.parseInt(str, radix);
// Alternative using inclusive range
export const parseIntegerInclusive = (
str: string,
radix?: UintRangeInclusive<2, 36>,
): number => Number.parseInt(str, radix);
// Valid usages:
parseInteger('10'); // radix defaults to 10
parseInteger('10', 2); // Binary
parseInteger('255', 16); // Hexadecimal
parseInteger('123', 36); // Maximum base
// Invalid usages (TypeScript will error):
// @ts-expect-error Argument of type '1' is not assignable to parameter of type 'UintRange<2, 37> | undefined'
parseInteger('10', 1);
// @ts-expect-error Argument of type '37' is not assignable to parameter of type 'UintRange<2, 37> | undefined'
parseInteger('10', 37);
// Branded types for additional safety
type UserId = Brand<number, 'UserId'>;
type ProductId = Brand<number, 'ProductId'>;
// Create branded values (you would typically have constructor functions)
declare const userId: UserId;
declare const productId: ProductId;
// Type-safe functions that can't mix up IDs
const getUserById = (id: UserId): User | undefined => {
/* ... */
return undefined;
};
const getProductById = (id: ProductId): Product | undefined => {
/* ... */
return undefined;
};
// @ts-expect-error Argument of type 'ProductId' is not assignable to parameter of type 'UserId'
getUserById(productId);
The library is organized into logical modules for easy navigation and understanding:
condition/: Type predicates like TypeEq, TypeExtends, IsUnion, IsNever for conditional type logic.record/: Object type utilities including StrictOmit, DeepReadonly, RecordPaths, and partial operations.branded-types/: Comprehensive branded number types (Int, Uint, SafeInt, FiniteNumber, etc.) with range constraints.tuple-and-list/: Array and tuple operations with List and Tuple namespaces for type-safe manipulations.type-level-integer/: Mathematical operations like Increment, UintRange, AbsoluteValue performed at the type level.constants/: Pre-defined constants like Primitive, FalsyValue, HTTPRequestMethod, and enum types.others/: Utility types like JsonValue, Mutable, WidenLiteral, and helper functions.For detailed information on all types, see the Full API Reference.
While ts-type-forge provides powerful compile-time type utilities, combining it with ts-data-forge unlocks runtime type validation capabilities that make your TypeScript applications even more robust.
ts-data-forge complements ts-type-forge by providing:
is* functionsJsonValue typesNonEmptyArray and other array constraints at runtime/// <reference types="ts-type-forge/global" />
// Runtime validation with ts-data-forge
import {
isUint,
expectType,
assertNonEmptyArray,
parseJsonValue,
isRecord,
hasKey,
} from 'ts-data-forge';
const numbers: readonly number[] = [1, 2, 3, 4, 5, 2, 3];
// Type-safe length checking
if (Arr.isArrayAtLeastLength(numbers, 2)) {
// numbers is now guaranteed to have at least 3 elements
expectType<typeof numbers, ArrayAtLeastLen<2, number>>('=');
console.log(numbers[1]); // Array access to index 0, 1 is now safe even with noUncheckedIndexedAccess enabled
}
// Safe JSON parsing with type validation
const jsonString = '{"count": 42, "items": [1, 2, 3]}';
const data: JsonValue = parseJsonValue(jsonString); // Validates at runtime
// Use the data with confidence
if (isRecord(data) && hasKey(data, 'count')) {
console.log(data.count); // Safe access
}
Install both libraries to get the full TypeScript type safety experience:
npm add ts-data-forge
npm add -D ts-type-forge
This library requires TypeScript version 4.8 or higher for full compatibility with advanced type features.
Contributions are welcome! Please see the repository's contribution guidelines for detailed information on how to contribute to this project.
Apache-2.0