Quick Start
Jump into typist with practical examples and basic usage patterns
Get up and running with typist in minutes. This guide covers the essential patterns you'll use in everyday type-level programming .
Start by importing the core utilities: t_ , $Equal , $Extends , yes_ , no_ , and example_ .
Your First Phantom Type
Let's start with the most fundamental concept in typist: phantom types. These allow you to create distinct types that share the same runtime representation but are treated differently by TypeScript.
// Basic phantom value creation examples
import { t_, type_, t } from '@type-first/typist';
// Create phantom values for any type
const user = t_<{ name: string; age: number }>();
const id = t_<string>();
const config = t_<{ theme: 'dark' | 'light' }>();
// All equivalent ways to create phantom values
const value1 = t_<string>();
const value2 = type_<string>();
const value3 = t<string>();
// Complex types work too
type ApiResponse<T> = {
data: T;
status: number;
message: string;
};
const response = t_<ApiResponse<User>>();
// Use phantom values for type-level operations
type UserType = typeof user; // { name: string; age: number }Type Comparisons
One of typist's most powerful features is the ability to compare types at compile time and get meaningful verdicts about their relationships.
import { $Equal, $Extends, yes_, no_ } from '@type-first/typist';
// Basic type equality
type StringTest = $Equal<string, string>; // $Yes
type MismatchTest = $Equal<string, number>; // $No<...>
// Verify the results
yes_<StringTest>(); // ✅ Compiles - strings are equal
no_<MismatchTest>(); // ✅ Compiles - string ≠ number
// Type extension relationships
type LiteralExtends = $Extends<"hello", string>; // $Yes
type GeneralExtends = $Extends<string, "hello">; // $No<...>
yes_<LiteralExtends>(); // ✅ Literal types extend their base type
no_<GeneralExtends>(); // ✅ General types don't extend specific literalsPractical Example: User Management
Let's build a practical example that demonstrates how typist can prevent common bugs in a user management system.
import { t_, $Equal, $Extends, yes_, has_ } from '@type-first/typist';
// Define distinct ID types
type UserId = string & { readonly __brand: 'UserId' };
type ProductId = string & { readonly __brand: 'ProductId' };
// Create phantom values for testing
const userId = t_<UserId>();
const productId = t_<ProductId>();
// Verify they're different types despite same runtime representation
no_<$Equal<UserId, ProductId>>(); // ✅ These should be different
// Define user structure
interface User {
id: UserId;
name: string;
email: string;
age: number;
}
// Test user structure compliance
function validateUser<T>(user: T) {
// Ensure it extends the base User interface
yes_<$Extends<T, { id: UserId }>>();
yes_<$Extends<T, { name: string }>>();
yes_<$Extends<T, { email: string }>>();
// Test specific properties exist
has_<"id", UserId>(user);
has_<"name", string>(user);
has_<"email", string>(user);
return user;
}
// Usage example
const validUser = validateUser({
id: 'user_123' as UserId,
name: 'John Doe',
email: 'john@example.com',
age: 30
});
// This would cause compile errors:
// validateUser({
// id: 'product_456' as ProductId, // ❌ Wrong ID type
// name: 'John' // ❌ Missing required fields
// });Advanced Pattern: API Response Validation
Here's a more advanced example showing how to validate API responses at the type level:
import { t_, $Equal, $Extends, yes_, extends_, example_ } from '@type-first/typist';
// Define expected API response structure
interface ApiResponse<T> {
data: T;
status: 'success' | 'error';
message: string;
timestamp: number;
}
// Create a validation helper
example_('API Response Validation', () => {
type UserResponse = ApiResponse<User>;
// Validate the response structure
extends_<UserResponse, { data: User }>();
extends_<UserResponse, { status: string }>();
extends_<UserResponse, { message: string }>();
// Test that data field has correct type
yes_<$Extends<UserResponse['data'], User>>();
return t_<UserResponse>();
});
// Usage in actual API handling
function handleApiResponse<T>(response: unknown): response is ApiResponse<T> {
// Runtime validation would go here
// Type-level validation is already enforced by typist
return typeof response === 'object' && response !== null;
}
// Type-safe API client
class TypeSafeApiClient {
async getUser(id: UserId): Promise<ApiResponse<User>> {
const response = await fetch(`/api/users/${id}`);
const data = await response.json();
// Typist ensures we return the correct type structure
return data as ApiResponse<User>;
}
}Testing Your Types
Typist encourages you to test your types just like you test your runtime code. Here's how to create a comprehensive type test suite:
import { test_, proof_, $Equal, $Extends, yes_, no_ } from '@type-first/typist';
// Named test for user type validation
const userTypeTest = test_('User type structure', () => {
// Test basic structure
yes_<$Extends<User, { id: UserId }>>();
yes_<$Extends<User, { name: string }>>();
// Test that User is not assignable to simpler types
no_<$Equal<User, { id: string }>>();
return t_<User>();
});
// Mathematical-style proof for type distributivity
const distributivityProof = proof_('Union type distributivity', () => {
type Distribute<T, U> = T extends any ? T | U : never;
yes_<$Equal<
Distribute<'a' | 'b', 'c'>,
'a' | 'c' | 'b' | 'c'
>>();
return t_<boolean>();
});
// Generic constraint testing
const repositoryTest = test_('Repository pattern constraints', () => {
interface Entity {
id: string;
}
interface Repository<T extends Entity> {
save(entity: T): Promise<T>;
findById(id: string): Promise<T | null>;
}
type UserRepository = Repository<User>;
// Test that repository methods have correct signatures
extends_<Parameters<UserRepository['save']>[0], User>();
extends_<ReturnType<UserRepository['findById']>, Promise<User | null>>();
return t_<UserRepository>();
});Simple, practical examples to get you started with typist's core functionality . Learn the essential patterns through hands-on examples with object shapes, arrays, functions, and type relationships.
Start with phantom values , move to type comparisons , then explore object validation and API contract testing .
Common Patterns
Branded Types for Domain Safety
Use phantom types to prevent mixing up similar values:
// Before: Easy to mix up
function transferMoney(fromAccount: string, toAccount: string, amount: number) {
// What if someone passes arguments in wrong order?
}
// After: Type-safe with brands
type AccountId = string & { readonly __brand: 'AccountId' };
type Amount = number & { readonly __brand: 'Amount' };
function transferMoney(fromAccount: AccountId, toAccount: AccountId, amount: Amount) {
// Impossible to mix up parameters!
}Compile-Time Configuration Validation
Validate configuration objects at build time:
interface Config {
database: {
host: string;
port: number;
};
features: {
enableNewUI: boolean;
maxUsers: number;
};
}
// Validate config at compile time
function validateConfig<T>(config: T) {
extends_<T, Config>();
has_<"database", { host: string; port: number }>(config);
has_<"features", { enableNewUI: boolean; maxUsers: number }>(config);
return config;
}Next Steps
Now that you've seen the basics, explore these areas to deepen your understanding:
- Phantom Types - Master nominal typing patterns
- Verdict System - Learn about compile-time validation
- Type Operators - Explore comparison and transformation utilities
- Type Assertions - Build robust type tests
Interactive Learning
Master typist concepts through hands-on practice with our interactive scenarios:
Phantom Types Basics
Learn the fundamentals of phantom types and type-level programming with typist. Create phantom values and build branded types.
Type Comparisons & Verdicts
Master type-level comparisons using $Equal, $Extends, and the verdict system. Learn to create compile-time assertions.
🚀 Pro Tip
Start small! Begin by adding phantom types to distinguish similar values in your existing code, then gradually introduce more advanced typist patterns as you become comfortable with type-level thinking.