prax-orm 0.6.4

A next-generation, type-safe ORM for Rust inspired by Prisma
Documentation
// Prax Schema Definition Language
// File: schema.prax
//
// This schema defines your data models, relations, and database mappings.
// Configuration is handled separately in prax.toml

// =============================================================================
// ENUMS
// =============================================================================

/// User roles for authorization
enum Role {
    User
    Admin
    Moderator
}

/// Status of a post
enum PostStatus {
    Draft
    Published
    Archived

    @map("post_status")  // Custom database enum name
}

// =============================================================================
// MODELS
// =============================================================================

/// User accounts in the system
model User {
    // Primary key with auto-increment
    id          Int         @id @auto

    // Unique email with index
    email       String      @unique @index

    // Optional display name
    name        String?

    // Password hash (never select by default)
    password    String      @omit

    // User role with default
    role        Role        @default(User)

    // Timestamps
    created_at  DateTime    @default(now())
    updated_at  DateTime    @updated_at

    // Soft delete support
    deleted_at  DateTime?

    // Relations
    posts       Post[]
    profile     Profile?
    comments    Comment[]

    // Table mapping and indexes
    @@map("users")
    @@index([email, role])
    @@index([created_at])
}

/// User profile with extended information
model Profile {
    id          Int         @id @auto
    bio         String?     @db.Text
    avatar_url  String?     @db.VarChar(500)
    website     String?
    location    String?

    // One-to-one relation
    user_id     Int         @unique
    user        User        @relation(fields: [user_id], references: [id], onDelete: Cascade)

    @@map("profiles")
}

/// Blog posts
model Post {
    id          Int         @id @auto
    slug        String      @unique @db.VarChar(200)
    title       String      @db.VarChar(200)
    content     String      @db.Text
    excerpt     String?     @db.VarChar(500)

    // Status enum
    status      PostStatus  @default(Draft)

    // Full-text search on title and content
    @@search([title, content])

    // View counter
    views       Int         @default(0)

    // Publishing metadata
    published_at DateTime?

    // Timestamps
    created_at  DateTime    @default(now())
    updated_at  DateTime    @updated_at

    // Author relation
    author_id   Int
    author      User        @relation(fields: [author_id], references: [id], onDelete: Cascade)

    // Many-to-many with tags
    tags        Tag[]       @relation("PostTags")

    // One-to-many with comments
    comments    Comment[]

    // Categories (self-referential many-to-many example would go in Category)
    categories  Category[]  @relation("PostCategories")

    @@map("posts")
    @@index([author_id])
    @@index([status, published_at])
    @@unique([author_id, slug])  // Composite unique
}

/// Tags for categorizing posts
model Tag {
    id          Int         @id @auto
    name        String      @unique @db.VarChar(50)
    slug        String      @unique @db.VarChar(50)
    color       String?     @db.VarChar(7)  // Hex color

    // Many-to-many with posts
    posts       Post[]      @relation("PostTags")

    @@map("tags")
}

/// Hierarchical categories
model Category {
    id          Int         @id @auto
    name        String      @db.VarChar(100)
    slug        String      @unique @db.VarChar(100)
    description String?     @db.Text

    // Self-referential relation for hierarchy
    parent_id   Int?
    parent      Category?   @relation("CategoryTree", fields: [parent_id], references: [id])
    children    Category[]  @relation("CategoryTree")

    // Posts in this category
    posts       Post[]      @relation("PostCategories")

    @@map("categories")
    @@index([parent_id])
}

/// Comments on posts
model Comment {
    id          Int         @id @auto
    content     String      @db.Text

    // Timestamps
    created_at  DateTime    @default(now())
    updated_at  DateTime    @updated_at

    // Author (optional for anonymous comments)
    author_id   Int?
    author      User?       @relation(fields: [author_id], references: [id], onDelete: SetNull)

    // Post relation
    post_id     Int
    post        Post        @relation(fields: [post_id], references: [id], onDelete: Cascade)

    // Nested comments (self-referential)
    parent_id   Int?
    parent      Comment?    @relation("CommentReplies", fields: [parent_id], references: [id])
    replies     Comment[]   @relation("CommentReplies")

    @@map("comments")
    @@index([post_id])
    @@index([author_id])
    @@index([parent_id])
}

// =============================================================================
// COMPOSITE TYPES (for MongoDB or JSON columns)
// =============================================================================

/// Address composite type (embedded document)
type Address {
    street      String
    city        String
    state       String?
    postal_code String
    country     String      @default("US")
}

/// Geolocation point
type GeoPoint {
    latitude    Float
    longitude   Float
}

// =============================================================================
// VIEWS (database views as read-only models)
// =============================================================================

/// Aggregated post statistics (maps to a database view)
view PostStats {
    post_id         Int     @unique
    comment_count   Int
    view_count      Int
    tag_count       Int

    post            Post    @relation(fields: [post_id], references: [id])

    @@map("post_stats_view")
}

// =============================================================================
// RAW SQL DEFINITIONS
// =============================================================================

/// Custom SQL for the post_stats_view
@@sql("post_stats_view", """
    CREATE OR REPLACE VIEW post_stats_view AS
    SELECT
        p.id as post_id,
        COUNT(DISTINCT c.id) as comment_count,
        p.views as view_count,
        COUNT(DISTINCT pt.tag_id) as tag_count
    FROM posts p
    LEFT JOIN comments c ON c.post_id = p.id
    LEFT JOIN _post_tags pt ON pt.post_id = p.id
    GROUP BY p.id, p.views
""")