Schemas
Schemas in Routier define the structure, behavior, and constraints of your data entities. They provide type safety, transformation, and metadata that ensures your application works correctly with your data structure.
Quick Navigation
What Are Schemas?
Schemas are type definitions that:
- Define the structure of your data entities
- Provide compile-time type safety
- Enable automatic transformation and serialization
- Support database indexing and constraints
- Enable change tracking and data management
Schema Builder
Routier provides a fluent, type-safe schema builder API:
import { s } from "@routier/core/schema";
// Basic user schema with common properties
const userSchema = s
.define("users", {
id: s.string().key().identity(),
email: s.string().distinct(),
name: s.string(),
createdAt: s.date().default(() => new Date()),
})
.compile();Key Features
Type Safety
- Full TypeScript support with generic constraints
- Literal type constraints for enum-like values
- Compile-time type checking of schema structure
Flexible Modifiers
- Behavior:
optional(),nullable(),readonly() - Values:
default(),identity() - Constraints:
key(),distinct() - Serialization:
serialize(),deserialize() - Performance:
index() - Mapping:
from()
Collection-level Modifiers
- Computed:
computed(fn)derives a value from the entity (and optionally collection name/injected context). Defaults to untracked and not persisted. Computation is performed on save. - Tracked:
tracked()on a computed property persists the derived value to storage for faster reads and indexing. - Function:
function(fn)attaches non-persisted methods to entities.
Rich Type System
- Primitives:
string,number,boolean,date - Complex:
object,array - Constraints: Literal types with generics
- Composition: Nested schemas and arrays
Documentation
Getting Started
- Creating A Schema - Learn how to create your first schema
Core Concepts
- Property Types - Available property types and their capabilities
- Modifiers - All available property modifiers and constraints
- InferType - Type inference and type safety
Reference
- Why Schemas? - Understanding the benefits and philosophy
Quick Examples
Basic Entity
import { s } from "@routier/core/schema";
// Basic user schema with common properties
const userSchema = s
.define("users", {
id: s.string().key().identity(),
email: s.string().distinct(),
name: s.string(),
createdAt: s.date().default(() => new Date()),
})
.compile();Complex Nested Schema
import { s } from "@routier/core/schema";
// Complex nested schema with relationships
const productSchema = s
.define("products", {
id: s.string().key().identity(),
name: s.string(),
price: s.number(),
category: s.string(),
tags: s.array(s.string()),
metadata: s.object({
description: s.string(),
specifications: s.object({
weight: s.number(),
dimensions: s.object({
width: s.number(),
height: s.number(),
depth: s.number(),
}),
}),
}),
reviews: s.array(s.object({
id: s.string(),
rating: s.number(),
comment: s.string(),
author: s.string(),
})),
})
.compile();Constrained Values
import { s } from "@routier/core/schema";
// Schema with constrained values and advanced modifiers
const orderSchema = s
.define("orders", {
id: s.string().key().identity(),
status: s.string("pending", "processing", "shipped", "delivered", "cancelled"),
priority: s.string("low", "medium", "high").default("medium"),
customerId: s.string().distinct(),
total: s.number().default(0),
items: s.array(s.object({
productId: s.string(),
quantity: s.number().default(1),
price: s.number(),
})),
createdAt: s.date().default(() => new Date()),
updatedAt: s.date().default(() => new Date()),
})
.compile();Benefits
Development Experience
- IntelliSense: Full autocomplete and type checking
- Refactoring: Safe refactoring with TypeScript
- Documentation: Self-documenting code structure
Data Management
- Serialization: Built-in serialization/deserialization
- Transformation: Property mapping and remapping
- Defaults: Automatic default value application
- Identity: Primary key and identity management
Database Integration
- Indexing: Schema-driven index creation for distinct properties and custom indexes
- Constraints: Unique constraints for distinct properties
- Change Tracking: Efficient change detection
- Schema Translation: Converts to native database schemas
Performance
Schemas are compiled into optimized JavaScript functions that eliminate runtime overhead:
- Code Generation: Schema definitions are compiled into fast, specialized functions for serialization, cloning, comparison, and change tracking
- Zero Runtime Reflection: All property access and transformations are pre-compiled, avoiding expensive runtime property inspection
- Optimized Data Paths: Generated code uses direct property access and optimized algorithms for common operations
- Memory Efficiency: Compiled schemas minimize memory allocations and garbage collection pressure
import { InferType, s } from "@routier/core/schema";
// Example schema with computed property
const userSchema = s.define("users", {
id: s.string().key().identity(),
firstName: s.string(),
lastName: s.string(),
email: s.string(),
// Computed property - calculated on save
fullName: s.computed((entity) => `${entity.firstName} ${entity.lastName}`),
}).compile();
type User = InferType<typeof userSchema>;
// Instead of runtime reflection like this (SLOW):
function slowSerialize(entity: any) {
const result: any = {};
// Creates new objects, uses Object.keys(), property enumeration
for (const key in entity) {
if (entity.hasOwnProperty(key)) {
result[key] = entity[key];
}
}
return result;
}
// Routier generates optimized code like this (FAST):
function fastSerialize(entity: User) {
const result: any = {};
// Direct property access - no loops, no Object.keys()
if (Object.hasOwn(entity, "id")) result.id = entity.id;
if (Object.hasOwn(entity, "firstName")) result.firstName = entity.firstName;
if (Object.hasOwn(entity, "lastName")) result.lastName = entity.lastName;
if (Object.hasOwn(entity, "email")) result.email = entity.email;
// Computed properties are calculated inline
if (result.firstName != null && result.lastName != null) {
result.fullName = `${result.firstName} ${result.lastName}`;
}
return result;
}
// Memory efficiency benefits:
// 1. No temporary arrays from Object.keys()
// 2. No for...in loop iterations
// 3. Direct property access (entity.firstName vs entity[key])
// 4. Inline computations instead of function calls
// 5. Pre-compiled property paths instead of string concatenationNext Steps
- Create your first schema - Start building schemas
- Understand property types - Choose the right types for your data
- Apply modifiers - Customize behavior and constraints
- Learn about type inference - Leverage TypeScript integration
- Data Manipulation - Learn how to update entities with proxies
Schemas are the foundation of Routier’s data management system. They provide the structure and rules that make your data consistent, safe, and performant.