Property Types

Routier provides a comprehensive set of property types for building robust schemas. Each type can be enhanced with modifiers to specify behavior and constraints.

Basic Types

String

import { s } from "@routier/core/schema";

// String property types within schemas
const userSchema = s.define("users", {
    // Basic string
    name: s.string(),

    // String with literals
    status: s.string("active", "inactive", "pending"),

    // String with modifiers
    email: s.string().distinct().optional(),
    description: s.string().nullable().default(""),
    readonlyField: s.string().readonly(),

    // String with serialization
    jsonData: s.string().serialize((obj) => JSON.stringify(obj))
        .deserialize((str) => JSON.parse(str)),
}).compile();

Number

import { s } from "@routier/core/schema";

// Number property types within schemas
const productSchema = s.define("products", {
    // Basic number
    age: s.number(),

    // Number with literals
    priority: s.number(1, 2, 3, 4, 5),

    // Number with modifiers
    score: s.number().default(0),
    rating: s.number().nullable().optional(),
    readonlyCount: s.number().readonly(),

    // Number with serialization
    price: s.number().serialize((num) => num.toString())
        .deserialize((str) => parseFloat(str)),
}).compile();

Boolean

import { s } from "@routier/core/schema";

// Boolean property types within schemas
const settingsSchema = s.define("settings", {
    // Basic boolean
    isActive: s.boolean(),

    // Boolean with modifiers
    isEnabled: s.boolean().default(true),
    isOptional: s.boolean().optional().nullable(),
    isReadonly: s.boolean().readonly(),

    // Boolean with serialization
    flag: s.boolean().serialize((bool) => bool ? "1" : "0")
        .deserialize((str) => str === "1"),
}).compile();

Date

import { s } from "@routier/core/schema";

// Date property types within schemas
const eventSchema = s.define("events", {
    // Basic date
    createdAt: s.date(),

    // Date with modifiers
    updatedAt: s.date().default(() => new Date()),
    publishedAt: s.date().optional().nullable(),
    readonlyDate: s.date().readonly(),

    // Date with serialization
    customDate: s.date().serialize((date) => date.toISOString())
        .deserialize((str) => new Date(str)),
}).compile();

Complex Types

Object

import { s } from "@routier/core/schema";

// Object property types within schemas
const companySchema = s.define("companies", {
    // Basic object
    address: s.object({
        street: s.string(),
        city: s.string(),
        zipCode: s.string(),
    }),

    // Object with modifiers
    metadata: s.object({
        version: s.string(),
        author: s.string(),
    }).optional(),

    config: s.object({
        theme: s.string(),
        language: s.string(),
    }).nullable().default({}),

    // Object with serialization
    settings: s.object({
        notifications: s.boolean(),
        privacy: s.string(),
    }).serialize((obj) => JSON.stringify(obj))
        .deserialize((str) => JSON.parse(str)),
}).compile();

Array

import { s } from "@routier/core/schema";

// Array property types within schemas
const postSchema = s.define("posts", {
    // Basic array
    tags: s.array(s.string()),

    // Array with modifiers
    items: s.array(s.string()).default([]),
    optionalList: s.array(s.number()).optional(),
    nullableArray: s.array(s.boolean()).nullable(),

    // Array with serialization
    serializedArray: s.array(s.string()).serialize((arr) => arr.join(","))
        .deserialize((str) => str.split(",")),

    // Complex array
    users: s.array(s.object({
        name: s.string(),
        email: s.string(),
    })),
}).compile();

Type Constraints with Generics

Routier’s type system allows you to constrain properties to specific literal values using TypeScript generics:

String Literals

import { s } from "@routier/core/schema";

// String literals within schemas
const orderSchema = s.define("orders", {
    status: s.string("pending", "approved", "rejected"),
    role: s.string("admin", "user", "guest"),
    theme: s.string("light", "dark", "auto"),
}).compile();

Number Literals

import { s } from "@routier/core/schema";

// Number literals within schemas
const taskSchema = s.define("tasks", {
    priority: s.number(1, 2, 3, 4, 5),
    level: s.number(0, 1, 2, 3),
    rating: s.number(1, 2, 3, 4, 5),
}).compile();

Boolean Literals

import { s } from "@routier/core/schema";

// Boolean literals within schemas (boolean type doesn't need literals)
const configSchema = s.define("config", {
    // Boolean is already constrained to true/false
    isActive: s.boolean(),
    isEnabled: s.boolean(),
    isVisible: s.boolean(),
}).compile();

Type Composition

Combining Types

import { s } from "@routier/core/schema";

// Type composition examples within schemas
const userSchema = s.define("users", {
    // Combining different types
    id: s.string().key().identity(),
    name: s.string(),
    age: s.number(),
    isActive: s.boolean().default(true),
    createdAt: s.date().default(() => new Date()),
    tags: s.array(s.string()).default([]),
}).compile();

const postSchema = s.define("posts", {
    // Nested objects with arrays
    title: s.string(),
    content: s.string(),
    metadata: s.object({
        views: s.number().default(0),
        tags: s.array(s.string()),
    }),
}).compile();

Type Conversion

Converting to Arrays

Any type can be converted to an array using the .array() modifier:

import { s } from "@routier/core/schema";

// Converting types to arrays within schemas
const dataSchema = s.define("data", {
    // String to array
    stringArray: s.string().array(),

    // Number to array
    numberArray: s.number().array(),

    // Boolean to array
    booleanArray: s.boolean().array(),

    // Date to array
    dateArray: s.date().array(),

    // Object to array
    objectArray: s.object({
        name: s.string(),
        value: s.number(),
    }).array(),
}).compile();

Special Use Cases

Identity Properties

Properties that auto-generate values:

import { s } from "@routier/core/schema";

// Identity properties (auto-generate values) within schemas
const entitySchema = s.define("entities", {
    // String identity
    id: s.string().key().identity(),

    // Number identity
    userId: s.number().key().identity(),

    // Date identity
    timestamp: s.date().key().identity(),
}).compile();

Key Properties

Properties that serve as unique identifiers:

import { s } from "@routier/core/schema";

// Key properties (unique identifiers) within schemas
const recordSchema = s.define("records", {
    // String key
    email: s.string().key(),

    // Number key
    userId: s.number().key(),

    // Date key
    timestamp: s.date().key(),
}).compile();

Indexed Properties

Properties that create database indexes:

import { s } from "@routier/core/schema";

// Indexed properties (create database indexes) within schemas
const searchSchema = s.define("search", {
    // Single index
    email: s.string().index("email_idx"),

    // Multiple indexes
    category: s.string().index("category_idx", "search_idx"),

    // Distinct index (unique)
    username: s.string().distinct(),

    // Indexed with other modifiers
    status: s.string("active", "inactive").index("status_idx").default("active"),
}).compile();

Best Practices

1. Use Literal Types for Constrained Values

import { s } from "@routier/core/schema";

// Use literal types for constrained values
const statusSchema = s.define("status", {
    id: s.string().key().identity(),

    // Use literals for constrained values
    orderStatus: s.string("pending", "processing", "shipped", "delivered"),
    userRole: s.string("admin", "user", "guest").default("user"),
    priority: s.number(1, 2, 3, 4, 5).default(1),

    // Instead of free-form strings
    // ❌ Bad: s.string() // Allows any string
    // ✅ Good: s.string("active", "inactive") // Constrains to specific values
}).compile();

2. Leverage Type Inference

import { s, InferType, InferCreateType } from "@routier/core/schema";

// Leverage TypeScript type inference
const userSchema = s.define("users", {
    id: s.string().key().identity(),
    name: s.string(),
    email: s.string().distinct(),
    age: s.number().default(18),
    isActive: s.boolean().default(true),
    tags: s.array(s.string()).default([]),
}).compile();

// TypeScript automatically infers the correct types
type User = InferType<typeof userSchema>;
type CreateUser = InferCreateType<typeof userSchema>;

// Use inferred types for type safety
function processUser(user: User): string {
    return `${user.name} (${user.email})`;
}

function createNewUser(userData: CreateUser): User {
    // TypeScript ensures all required fields are provided
    return userData as User;
}

3. Use Appropriate Types

import { s } from "@routier/core/schema";

// Use appropriate types for the data
const productSchema = s.define("products", {
    id: s.string().key().identity(),

    // Use appropriate types for different data
    name: s.string(), // Text data
    price: s.number(), // Numeric data
    isAvailable: s.boolean(), // True/false data
    releaseDate: s.date(), // Date/time data

    // Use arrays for collections
    tags: s.array(s.string()),
    reviews: s.array(s.object({
        rating: s.number(1, 2, 3, 4, 5),
        comment: s.string(),
    })),

    // Use objects for structured data
    metadata: s.object({
        weight: s.number(),
        dimensions: s.string(),
    }),

    // Avoid inappropriate types
    // ❌ Bad: s.string() for numeric IDs
    // ✅ Good: s.number() for numeric IDs
    // ❌ Bad: s.number() for text descriptions  
    // ✅ Good: s.string() for text descriptions
}).compile();

4. Structure Complex Data

import { s } from "@routier/core/schema";

// Structure complex data with nested objects and arrays
const blogPostSchema = s.define("blogPosts", {
    id: s.string().key().identity(),
    title: s.string(),
    content: s.string(),

    // Structure complex nested data
    author: s.object({
        id: s.string(),
        name: s.string(),
        email: s.string(),
        bio: s.string().optional(),
    }),

    // Use arrays for collections
    tags: s.array(s.string()).default([]),
    categories: s.array(s.string("tech", "business", "lifestyle")),

    // Nested arrays and objects
    comments: s.array(s.object({
        id: s.string().identity(),
        author: s.string(),
        content: s.string(),
        createdAt: s.date().default(() => new Date()),
        replies: s.array(s.object({
            id: s.string().identity(),
            author: s.string(),
            content: s.string(),
            createdAt: s.date().default(() => new Date()),
        })).default([]),
    })).default([]),

    // Metadata as structured object
    metadata: s.object({
        views: s.number().default(0),
        likes: s.number().default(0),
        publishedAt: s.date().optional(),
        lastModified: s.date().default(() => new Date()),
    }),
}).compile();

Type Compatibility

Modifier Support

Different types support different modifiers:

Modifier String Number Boolean Date Object Array
.optional()
.nullable()
.default()
.readonly()
.deserialize()
.serialize()
.array()
.index()
.key()
.identity()
.distinct()

Summary of Types

  • Array: s.array(innerType)
  • Boolean: s.boolean()
  • Date: s.date()
  • Number: s.number()
  • Object: s.object({...})
  • String: s.string()

Next Steps