InferType
InferType is a TypeScript utility type that extracts the runtime type of entities from Routier schemas. It provides compile-time type safety by inferring the actual TypeScript type that corresponds to your schema definition.
Quick Navigation
- What is InferType?
- Basic Usage
- InferType vs InferCreateType
- Real-World Examples
- Type Safety Benefits
- Best Practices
- Related
What is InferType?
InferType takes a compiled schema and returns the TypeScript type that represents the actual entity structure at runtime. This includes:
- All properties defined in your schema
- Applied modifiers like
optional(),nullable(),default() - Nested objects and arrays with their complete structure
- Computed properties and their return types
Basic Usage
import { InferType, InferCreateType, s } from "@routier/core/schema";
// Define a schema
const userSchema = s.define("users", {
id: s.string().key().identity(),
email: s.string().distinct(),
name: s.string(),
age: s.number().optional(),
createdAt: s.date().default(() => new Date()),
}).compile();
// Extract the TypeScript type
type User = InferType<typeof userSchema>;
type CreateUser = InferCreateType<typeof userSchema>;
// User type includes all properties (including defaults and identities)
const user: User = {
id: "user-123", // Required (identity)
email: "[email protected]", // Required
name: "John Doe", // Required
age: 30, // Optional
createdAt: new Date(), // Required (has default)
};
// CreateUser type excludes defaults and identities
const newUser: CreateUser = {
email: "[email protected]", // Required
name: "Jane Doe", // Required
age: 25, // Optional
// id and createdAt are optional (auto-generated/defaulted)
};InferType vs InferCreateType
Routier provides two related type utilities:
InferType
- Purpose: Represents the complete entity after creation
- Includes: All properties, including those with defaults and identities
- Use case: Working with existing entities from the database
InferCreateType
- Purpose: Represents the entity structure for creation
- Excludes: Properties with defaults (optional) and identity properties (auto-generated)
- Use case: Creating new entities with
addAsync()
import { InferType, InferCreateType, s } from "@routier/core/schema";
const productSchema = s.define("products", {
id: s.string().key().identity(),
name: s.string(),
price: s.number(),
category: s.string().default("general"),
isActive: s.boolean().default(true),
}).compile();
type Product = InferType<typeof productSchema>;
type CreateProduct = InferCreateType<typeof productSchema>;
// InferType - Complete entity type
const existingProduct: Product = {
id: "prod-123", // Required (identity)
name: "Widget", // Required
price: 29.99, // Required
category: "general", // Required (has default)
isActive: true, // Required (has default)
};
// InferCreateType - Creation type (defaults are optional)
const newProduct: CreateProduct = {
name: "Gadget", // Required
price: 19.99, // Required
// category and isActive are optional (have defaults)
// id is optional (auto-generated)
};
// TypeScript will enforce these differences
function updateProduct(product: Product, updates: Partial<Product>) {
// Can update any property of existing product
return { ...product, ...updates };
}
function createProduct(data: CreateProduct) {
// Only requires non-default, non-identity properties
return data;
}Real-World Examples
Function Parameters
import { InferType, s } from "@routier/core/schema";
const userSchema = s.define("users", {
id: s.string().key().identity(),
email: s.string().distinct(),
name: s.string(),
profile: s.object({
bio: s.string().optional(),
avatar: s.string().optional(),
preferences: s.object({
theme: s.string("light", "dark").default("light"),
notifications: s.boolean().default(true),
}),
}),
}).compile();
type User = InferType<typeof userSchema>;
// Type-safe function parameters
function sendEmail(user: User, subject: string, body: string) {
// TypeScript knows user.email exists and is a string
console.log(`Sending email to ${user.email}`);
console.log(`Subject: ${subject}`);
console.log(`Body: ${body}`);
}
function updateUserProfile(user: User, updates: Partial<User['profile']>) {
// TypeScript knows the exact structure of user.profile
return {
...user,
profile: { ...user.profile, ...updates }
};
}
function getUserPreferences(user: User) {
// TypeScript knows user.profile.preferences structure
return {
theme: user.profile.preferences.theme,
notifications: user.profile.preferences.notifications,
};
}
// Usage with full type safety
const user: User = {
id: "user-123",
email: "[email protected]",
name: "John Doe",
profile: {
bio: "Software developer",
preferences: {
theme: "dark",
notifications: true,
},
},
};
sendEmail(user, "Welcome!", "Thanks for joining!");
updateUserProfile(user, { bio: "Senior developer" });
const prefs = getUserPreferences(user);API Responses
import { InferType, s } from "@routier/core/schema";
const orderSchema = s.define("orders", {
id: s.string().key().identity(),
customerId: s.string(),
items: s.array(s.object({
productId: s.string(),
quantity: s.number(),
price: s.number(),
})),
status: s.string("pending", "processing", "shipped", "delivered"),
total: s.number(),
createdAt: s.date().default(() => new Date()),
}).compile();
type Order = InferType<typeof orderSchema>;
// API response type safety
interface ApiResponse<T> {
data: T;
success: boolean;
message: string;
}
type OrderResponse = ApiResponse<Order>;
type OrdersResponse = ApiResponse<Order[]>;
// Type-safe API functions
async function getOrder(id: string): Promise<OrderResponse> {
// Implementation would fetch from API
return {
data: {
id,
customerId: "customer-123",
items: [
{ productId: "prod-1", quantity: 2, price: 29.99 }
],
status: "pending",
total: 59.98,
createdAt: new Date(),
},
success: true,
message: "Order retrieved successfully",
};
}
async function getOrders(): Promise<OrdersResponse> {
// Implementation would fetch from API
return {
data: [],
success: true,
message: "Orders retrieved successfully",
};
}
// Type-safe response handling
async function processOrderResponse() {
const response = await getOrder("order-123");
if (response.success) {
const order = response.data; // TypeScript knows this is Order type
// Full type safety on order properties
console.log(`Order ${order.id} status: ${order.status}`);
console.log(`Total: $${order.total}`);
console.log(`Items: ${order.items.length}`);
// TypeScript knows items structure
order.items.forEach(item => {
console.log(`${item.productId}: ${item.quantity} x $${item.price}`);
});
}
}Complex Nested Types
import { InferType, s } from "@routier/core/schema";
const companySchema = s.define("companies", {
id: s.string().key().identity(),
name: s.string(),
address: s.object({
street: s.string(),
city: s.string(),
state: s.string(),
zipCode: s.string(),
country: s.string().default("USA"),
}),
employees: s.array(s.object({
id: s.string(),
name: s.string(),
email: s.string(),
department: s.string(),
role: s.string("admin", "manager", "employee"),
salary: s.number().optional(),
startDate: s.date(),
})),
departments: s.array(s.object({
id: s.string(),
name: s.string(),
managerId: s.string().optional(),
budget: s.number().optional(),
})),
metadata: s.object({
founded: s.date(),
industry: s.string(),
size: s.string("startup", "small", "medium", "large"),
isPublic: s.boolean().default(false),
}),
}).compile();
type Company = InferType<typeof companySchema>;
// Complex type operations with full type safety
function findEmployeeByEmail(company: Company, email: string) {
return company.employees.find(emp => emp.email === email);
}
function getDepartmentEmployees(company: Company, departmentId: string) {
return company.employees.filter(emp => emp.department === departmentId);
}
function calculateTotalSalary(company: Company) {
return company.employees
.filter(emp => emp.salary !== undefined)
.reduce((total, emp) => total + emp.salary!, 0);
}
function getCompanySummary(company: Company) {
return {
name: company.name,
totalEmployees: company.employees.length,
departments: company.departments.length,
totalSalary: calculateTotalSalary(company),
founded: company.metadata.founded,
industry: company.metadata.industry,
size: company.metadata.size,
};
}
// Type-safe nested property access
function updateEmployeeRole(company: Company, employeeId: string, newRole: Company['employees'][0]['role']) {
const employee = company.employees.find(emp => emp.id === employeeId);
if (employee) {
employee.role = newRole; // TypeScript ensures newRole is valid
}
}
// Usage example
const company: Company = {
id: "comp-123",
name: "TechCorp",
address: {
street: "123 Tech St",
city: "San Francisco",
state: "CA",
zipCode: "94105",
country: "USA",
},
employees: [
{
id: "emp-1",
name: "John Doe",
email: "[email protected]",
department: "engineering",
role: "manager",
salary: 120000,
startDate: new Date("2020-01-15"),
},
],
departments: [
{
id: "dept-1",
name: "Engineering",
managerId: "emp-1",
budget: 500000,
},
],
metadata: {
founded: new Date("2015-03-01"),
industry: "Technology",
size: "medium",
isPublic: false,
},
};
// All operations are fully type-safe
const john = findEmployeeByEmail(company, "[email protected]");
const engineeringTeam = getDepartmentEmployees(company, "dept-1");
const summary = getCompanySummary(company);
updateEmployeeRole(company, "emp-1", "admin");Type Safety Benefits
Compile-Time Type Checking
// ✅ TypeScript will catch this error at compile time
function processUser(user: User) {
console.log(user.email); // ✅ Valid - email exists on User type
console.log(user.invalidField); // ❌ Error - property doesn't exist
}
IntelliSense Support
const user: User = await ctx.users.firstAsync();
user. // ← IntelliSense shows all available properties
Refactoring Safety
When you change your schema, TypeScript will show errors everywhere the type is used, ensuring you update all related code.
Best Practices
1. Use Type Aliases
// ✅ Good - reusable type alias
type User = InferType<typeof userSchema>;
type CreateUser = InferCreateType<typeof userSchema>;
// ❌ Avoid - repeating the type everywhere
function processUser(user: InferType<typeof userSchema>) { ... }
2. Export Types for Reuse
// schema.ts
export const userSchema = s.define("users", { ... }).compile();
export type User = InferType<typeof userSchema>;
export type CreateUser = InferCreateType<typeof userSchema>;
// other-file.ts
import { User, CreateUser } from './schema';
3. Use Appropriate Type
// ✅ Use InferCreateType for creation
async function createUser(data: CreateUser) {
return await ctx.users.addAsync(data);
}
// ✅ Use InferType for existing entities
async function updateUser(user: User, updates: Partial<User>) {
return await ctx.users.updateAsync(user, updates);
}
Related
- Creating A Schema - Learn how to define schemas
- Property Types - Available property types
- Modifiers - Property modifiers and constraints