---
import DocsLayout from '../../layouts/DocsLayout.astro';
import CodeBlock from '../../components/CodeBlock.astro';
// Field Attributes
const fieldAttrsBasic = `model User {
id Int @id // Primary key
count Int @auto // Auto-increment
email String @unique // Unique constraint
active Boolean @default(true) // Default value
createdAt DateTime @default(now()) // Function default
updatedAt DateTime @updatedAt // Auto-update timestamp
firstName String @map("first_name") // Column name mapping
data Json? @ignore // Ignore in client
}`;
const fieldAttrsAdvanced = `model Post {
// UUID with auto-generation
id String @id @default(uuid())
// CUID alternatives
publicId String @default(cuid())
shortId String @default(nanoid())
sortId String @default(ulid())
// Database-specific column types
content String @db.Text
price Decimal @db.Decimal(10, 2)
metadata Json @db.JsonB
// Relation with custom name
author User @relation("PostAuthor")
}`;
// Model Attributes
const modelAttrs = `model User {
id Int @id @auto
email String
tenantId Int
role String
// Map to different table name
@@map("users")
// Single-field index
@@index([email])
// Composite index with name
@@index([tenantId, role], name: "tenant_role_idx")
// Composite unique constraint
@@unique([email, tenantId])
// Composite primary key
@@id([tenantId, id])
// Full-text search index (PostgreSQL)
@@index([name, bio], type: GIN)
}`;
// Relation Attributes
const relationAttrs = `model Post {
id Int @id @auto
author User @relation(
fields: [authorId], // Foreign key field(s)
references: [id], // Referenced field(s)
name: "UserPosts", // Relation name (for disambiguation)
onDelete: Cascade, // Delete behavior
onUpdate: Cascade // Update behavior
)
authorId Int
}
model Comment {
id Int @id @auto
// Self-relation example
parent Comment? @relation("CommentReplies", fields: [parentId], references: [id])
parentId Int?
replies Comment[] @relation("CommentReplies")
}`;
// String Validators
const stringValidators = `model User {
// Email format validation
email String @validate.email
// URL format validation
website String? @validate.url
// ID format validators
externalId String @validate.uuid
trackingId String @validate.cuid
shortCode String @validate.nanoid
sortableId String @validate.ulid
// Length constraints
username String @validate.minLength(3) @validate.maxLength(30)
bio String? @validate.length(10, 500)
// Pattern matching
phone String @validate.regex("^\\+[1-9]\\d{1,14}$")
slug String @validate.slug
// Content validators
firstName String @validate.alpha
code String @validate.alphanumeric
lowerName String @validate.lowercase
upperCode String @validate.uppercase
// String content
searchTerm String @validate.startsWith("search:")
filePath String @validate.endsWith(".json")
keywords String @validate.contains("prax")
// Whitespace handling
cleanInput String @validate.trim
noSpaces String @validate.noWhitespace
// Network validators
ipAddress String @validate.ip
ipv4Only String @validate.ipv4
ipv6Only String @validate.ipv6
// Format validators
cardNumber String @validate.creditCard
phoneNum String @validate.phone
hexColor String @validate.hex
encoded String @validate.base64
jsonStr String @validate.json
}`;
// Numeric Validators
const numericValidators = `model Product {
// Range constraints
price Decimal @validate.min(0)
discount Int @validate.max(100)
quantity Int @validate.range(0, 10000)
// Sign validators
rating Float @validate.positive
adjustment Int @validate.negative
balance Decimal @validate.nonNegative
debt Decimal @validate.nonPositive
// Type validators
wholeNumber Float @validate.integer
percentage Float @validate.multipleOf(0.01)
safeNumber Float @validate.finite
}`;
// Array Validators
const arrayValidators = `model Post {
// Array length constraints
tags String[] @validate.minItems(1)
categories String[] @validate.maxItems(5)
keywords String[] @validate.items(1, 10)
// Array content validators
uniqueTags String[] @validate.unique
requiredArr String[] @validate.nonEmpty
}`;
// Date Validators
const dateValidators = `model Event {
// Relative date validators
birthDate DateTime @validate.past
eventDate DateTime @validate.future
lastLogin DateTime @validate.pastOrPresent
nextReview DateTime @validate.futureOrPresent
// Absolute date constraints
startDate DateTime @validate.after("2024-01-01")
endDate DateTime @validate.before("2025-12-31")
}`;
// General Validators
const generalValidators = `model User {
// Required even if type is optional
middleName String? @validate.required
// Non-empty (works with strings, arrays, etc.)
nickname String @validate.notEmpty
// Enum-like constraint
status String @validate.oneOf("active", "inactive", "pending")
role String @validate.oneOf("admin", "user", "guest")
// Custom validator function
password String @validate.custom("strongPassword")
}`;
// ID Generation - UUID
const uuidExamples = `model User {
// UUID v4 as primary key (recommended for distributed systems)
id String @id @default(uuid())
// UUID with native database type (PostgreSQL)
id String @id @default(uuid()) @db.Uuid
}
model Session {
// UUID for non-primary key fields
id Int @id @auto
token String @unique @default(uuid())
}
// Example generated values:
// "550e8400-e29b-41d4-a716-446655440000"
// "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
// "f47ac10b-58cc-4372-a567-0e02b2c3d479"`;
// ID Generation - CUID
const cuidExamples = `model Post {
// CUID as primary key (collision-resistant, sortable)
id String @id @default(cuid())
// CUID for public-facing IDs
slug String @unique @default(cuid())
}
model ApiKey {
// CUID2 - more secure, shorter (next generation)
id Int @id @auto
key String @unique @default(cuid2())
}
// CUID example values (25 chars, starts with 'c'):
// "cjld2cjxh0000qzrmn831i7rn"
// "cjld2cyuq0000t3rmniod1foy"
// CUID2 example values (24 chars, random start):
// "tz4a98xxat96iws9zmbrgj3a"
// "pfh0haxfpzowht3oi213cqos"`;
// ID Generation - NanoID
const nanoidExamples = `model ShortUrl {
// NanoID - URL-friendly, customizable length
id String @id @default(nanoid()) // 21 chars default
code String @unique @default(nanoid(8)) // 8 chars custom
}
model InviteCode {
id Int @id @auto
// Short codes for sharing
code String @unique @default(nanoid(6))
}
// NanoID example values (21 chars default):
// "V1StGXR8_Z5jdHi6B-myT"
// "FwcE6X9N4f7kLpQrS2hJm"
// NanoID(8) example values:
// "5fX8pK2m"
// "R9qW3vNx"`;
// ID Generation - ULID
const ulidExamples = `model Event {
// ULID - sortable by creation time
id String @id @default(ulid())
}
model LogEntry {
// ULID preserves insertion order
id String @id @default(ulid())
level String
message String
timestamp DateTime @default(now())
}
// ULID example values (26 chars, time-sortable):
// "01ARZ3NDEKTSV4RRFFQ69G5FAV"
// "01BX5ZZKBKACTAV9WEVGEMMVRY"
// Structure: TTTTTTTTTTSSSSSSSSSSSSSSSS
// |---------|--------------|
// timestamp randomness`;
// ID Generation Comparison
const idComparison = `// Choose the right ID type for your use case:
model Example {
// UUID v4 - Universal, widely supported
// ✓ Standard format (RFC 4122)
// ✓ Native database support (PostgreSQL UUID type)
// ✗ Not sortable by creation time
// ✗ Longer (36 chars with dashes)
uuidId String @default(uuid())
// CUID - Collision-resistant, horizontal scaling
// ✓ Sortable (roughly by creation time)
// ✓ URL-safe characters
// ✓ Designed for distributed systems
// ✗ Longer than NanoID (25 chars)
cuidId String @default(cuid())
// CUID2 - Next generation CUID
// ✓ More secure (unpredictable)
// ✓ Shorter than CUID (24 chars)
// ✓ Better entropy distribution
// ✗ Not sortable by time
cuid2Id String @default(cuid2())
// NanoID - Compact, customizable
// ✓ URL-safe (A-Za-z0-9_-)
// ✓ Customizable length
// ✓ Smaller than UUID (21 chars default)
// ✗ Not sortable by time
nanoidId String @default(nanoid())
// ULID - Time-sortable
// ✓ Lexicographically sortable
// ✓ Encodes timestamp (first 10 chars)
// ✓ Compatible with UUID format
// ✗ Timestamp can leak creation time
ulidId String @default(ulid())
// Auto-increment - Simple sequential
// ✓ Smallest storage (integer)
// ✓ Natural ordering
// ✗ Reveals record count
// ✗ Not suitable for distributed systems
autoId Int @auto
}`;
// Default Value Functions
const defaultFunctions = `model Record {
// Timestamp functions
createdAt DateTime @default(now())
// UUID generation (v4)
id String @default(uuid())
// CUID generation (collision-resistant)
publicId String @default(cuid())
// CUID2 (next generation, more secure)
trackingId String @default(cuid2())
// NanoID (URL-friendly, customizable)
shortId String @default(nanoid())
customId String @default(nanoid(10)) // Custom length
// ULID (sortable)
sortableId String @default(ulid())
// Auto-increment (integers)
sequence Int @default(autoincrement())
// Database sequence
orderNum Int @default(dbgenerated("nextval('order_seq')"))
// Static defaults
active Boolean @default(true)
role String @default("user")
count Int @default(0)
tags String[] @default([])
}`;
// Database-specific Attributes
const dbSpecific = `model Document {
// PostgreSQL types
id Int @id @auto
data Json @db.JsonB // JSONB for indexing
content String @db.Text // TEXT type
amount Decimal @db.Decimal(19, 4) // DECIMAL(19,4)
small Int @db.SmallInt // SMALLINT
big BigInt @db.BigInt // BIGINT
uuid String @db.Uuid // UUID type
xml String @db.Xml // XML type
inet String @db.Inet // INET type
cidr String @db.Cidr // CIDR type
macaddr String @db.MacAddr // MACADDR type
// MySQL types
tinyText String @db.TinyText
mediumText String @db.MediumText
longText String @db.LongText
tinyInt Int @db.TinyInt
mediumInt Int @db.MediumInt
year Int @db.Year
// Column charset (MySQL)
name String @db.VarChar(255) @db.Charset("utf8mb4")
}`;
// Documentation Attributes
const docAttrs = `model User {
/// @hidden - Exclude from public API
internalId String
/// @internal - Admin-only visibility
debugInfo Json?
/// @sensitive - Mask in logs
ssn String?
/// @readonly - Cannot be set via API
createdAt DateTime @default(now())
/// @writeonly - Not returned in responses
password String
/// @deprecated Use 'email' instead
/// @since 1.0.0
oldEmail String?
/// @example "john@example.com"
/// @label "Email Address"
/// @placeholder "Enter your email"
email String
/// @group "Personal Info"
/// @order 1
firstName String
/// @alias "userName"
/// @json "user_name"
username String
}`;
// Index Types
const indexTypes = `model SearchDocument {
id Int @id @auto
title String
content String
tags String[]
location Json
// B-Tree index (default)
@@index([title])
// Hash index (equality only)
@@index([id], type: Hash)
// GIN index (arrays, JSONB, full-text)
@@index([tags], type: GIN)
@@index([content], type: GIN) // Full-text search
// GiST index (geometric, full-text)
@@index([location], type: GiST)
// BRIN index (large sorted tables)
@@index([createdAt], type: BRIN)
// Partial index with condition
@@index([email], where: "active = true")
// Unique index with nulls handling
@@unique([email], nulls: NotDistinct)
}`;
// Vector Index Types
const vectorIndexTypes = `// Vector indexes for AI/ML similarity search (requires pgvector)
// Database URL is configured in prax.toml, not in the schema
datasource db {
provider = "postgresql"
extensions = [vector]
}
model Document {
id Int @id @auto
title String
content String
embedding Vector(1536) // OpenAI embedding dimension
// HNSW index - best recall, recommended for most use cases
@@index([embedding], type: Hnsw, ops: Cosine)
}
model ImageSearch {
id Int @id @auto
features Vector(512)
// IVFFlat index - faster builds for large datasets
@@index([features], type: IvfFlat, ops: L2, lists: 100)
}
model AdvancedSearch {
id Int @id @auto
embedding Vector(768)
// HNSW with custom parameters
@@index([embedding], type: Hnsw, ops: Cosine, m: 32, ef_construction: 128)
// Inner product for max similarity
@@index([embedding], type: Hnsw, ops: InnerProduct, name: "ip_idx")
}
// Index Type Options:
// - type: Hnsw | IvfFlat
// - ops: Cosine | L2 | InnerProduct
// - m: HNSW connections (default 16)
// - ef_construction: HNSW build quality (default 64)
// - lists: IVFFlat clusters (default 100)`;
// Index types reference table
const indexTypesTable = [
{ type: 'BTree', use: 'Default, range queries, sorting', pg: '✅', mysql: '✅', sqlite: '✅' },
{ type: 'Hash', use: 'Equality comparisons only', pg: '✅', mysql: '✅', sqlite: '❌' },
{ type: 'GIN', use: 'Arrays, JSONB, full-text', pg: '✅', mysql: '❌', sqlite: '❌' },
{ type: 'GiST', use: 'Geometric, full-text, ranges', pg: '✅', mysql: '❌', sqlite: '❌' },
{ type: 'BRIN', use: 'Large sorted tables', pg: '✅', mysql: '❌', sqlite: '❌' },
{ type: 'Hnsw', use: 'Vector similarity (best recall)', pg: '✅*', mysql: '❌', sqlite: '❌' },
{ type: 'IvfFlat', use: 'Vector similarity (fast build)', pg: '✅*', mysql: '❌', sqlite: '❌' },
];
const vectorOpsTable = [
{ op: 'Cosine', desc: 'Cosine distance (1 - similarity)', best: 'Text embeddings (normalized)' },
{ op: 'L2', desc: 'Euclidean distance', best: 'Image features (unnormalized)' },
{ op: 'InnerProduct', desc: 'Negative inner product', best: 'Max inner product search' },
];
---
<DocsLayout title="Attributes - Prax ORM">
<article class="max-w-4xl mx-auto px-6 py-12">
<header class="mb-12">
<h1 class="text-4xl font-bold mb-4">Attributes</h1>
<p class="text-xl text-muted">
Configure field and model behavior with powerful attributes and validators.
</p>
</header>
<nav class="mb-12 p-4 bg-surface-elevated rounded-lg">
<h3 class="text-sm font-semibold text-muted uppercase tracking-wider mb-3">On this page</h3>
<ul class="grid grid-cols-2 md:grid-cols-3 gap-2 text-sm">
<li><a href="/schema/attributes#field-attrs" class="text-accent hover:underline">Field Attributes</a></li>
<li><a href="/schema/attributes#model-attrs" class="text-accent hover:underline">Model Attributes</a></li>
<li><a href="/schema/attributes#relations" class="text-accent hover:underline">Relation Attributes</a></li>
<li><a href="/schema/attributes#id-generation" class="text-accent hover:underline">ID Generation</a></li>
<li><a href="/schema/attributes#string-validators" class="text-accent hover:underline">String Validators</a></li>
<li><a href="/schema/attributes#numeric-validators" class="text-accent hover:underline">Numeric Validators</a></li>
<li><a href="/schema/attributes#array-validators" class="text-accent hover:underline">Array Validators</a></li>
<li><a href="/schema/attributes#date-validators" class="text-accent hover:underline">Date Validators</a></li>
<li><a href="/schema/attributes#general-validators" class="text-accent hover:underline">General Validators</a></li>
<li><a href="/schema/attributes#defaults" class="text-accent hover:underline">Default Functions</a></li>
<li><a href="/schema/attributes#db-specific" class="text-accent hover:underline">Database Types</a></li>
<li><a href="/schema/attributes#documentation" class="text-accent hover:underline">Documentation</a></li>
<li><a href="/schema/attributes#index-types" class="text-accent hover:underline">Index Types</a></li>
<li><a href="/schema/attributes#vector-indexes" class="text-accent hover:underline">Vector Indexes</a></li>
</ul>
</nav>
<div class="space-y-16">
<!-- Field Attributes -->
<section id="field-attrs">
<h2 class="text-2xl font-semibold mb-4">Field Attributes</h2>
<p class="text-muted mb-4">
Field attributes modify individual field behavior, constraints, and mapping.
</p>
<div class="overflow-x-auto mb-6">
<table class="w-full text-sm">
<thead>
<tr class="border-b border-border">
<th class="text-left py-2 pr-4 font-semibold">Attribute</th>
<th class="text-left py-2 font-semibold">Description</th>
</tr>
</thead>
<tbody class="divide-y divide-border">
<tr><td class="py-2 pr-4"><code>@id</code></td><td>Marks field as primary key</td></tr>
<tr><td class="py-2 pr-4"><code>@auto</code></td><td>Auto-increment for integers</td></tr>
<tr><td class="py-2 pr-4"><code>@unique</code></td><td>Unique constraint</td></tr>
<tr><td class="py-2 pr-4"><code>@default(value)</code></td><td>Default value or function</td></tr>
<tr><td class="py-2 pr-4"><code>@updatedAt</code></td><td>Auto-update timestamp on modification</td></tr>
<tr><td class="py-2 pr-4"><code>@map("name")</code></td><td>Map to different column name</td></tr>
<tr><td class="py-2 pr-4"><code>@ignore</code></td><td>Exclude from generated client</td></tr>
<tr><td class="py-2 pr-4"><code>@relation(...)</code></td><td>Configure relation behavior</td></tr>
<tr><td class="py-2 pr-4"><code>@db.Type</code></td><td>Database-specific column type</td></tr>
</tbody>
</table>
</div>
<h3 class="text-lg font-medium mb-3">Basic Usage</h3>
<CodeBlock code={fieldAttrsBasic} lang="prax" filename="prax/schema.prax" />
<h3 class="text-lg font-medium mb-3 mt-6">Advanced Usage</h3>
<CodeBlock code={fieldAttrsAdvanced} lang="prax" filename="prax/schema.prax" />
</section>
<!-- Model Attributes -->
<section id="model-attrs">
<h2 class="text-2xl font-semibold mb-4">Model Attributes</h2>
<p class="text-muted mb-4">
Model-level attributes (prefixed with <code>@@</code>) configure table-wide behavior.
</p>
<div class="overflow-x-auto mb-6">
<table class="w-full text-sm">
<thead>
<tr class="border-b border-border">
<th class="text-left py-2 pr-4 font-semibold">Attribute</th>
<th class="text-left py-2 font-semibold">Description</th>
</tr>
</thead>
<tbody class="divide-y divide-border">
<tr><td class="py-2 pr-4"><code>@@map("name")</code></td><td>Map to different table name</td></tr>
<tr><td class="py-2 pr-4"><code>@@id([fields])</code></td><td>Composite primary key</td></tr>
<tr><td class="py-2 pr-4"><code>@@unique([fields])</code></td><td>Composite unique constraint</td></tr>
<tr><td class="py-2 pr-4"><code>@@index([fields])</code></td><td>Create database index</td></tr>
<tr><td class="py-2 pr-4"><code>@@ignore</code></td><td>Exclude model from client</td></tr>
<tr><td class="py-2 pr-4"><code>@@schema("name")</code></td><td><strong>Not yet supported</strong> — per-model schema assignment has no effect in the DSL and is a compile error in the derive macro</td></tr>
</tbody>
</table>
</div>
<CodeBlock code={modelAttrs} lang="prax" filename="prax/schema.prax" />
<div class="mt-4 p-4 rounded-xl bg-info-500/10 border border-info-500/30">
<p class="text-info-400 text-sm">
<strong>Note:</strong> On the Rust derive side (<code>#[derive(Model)]</code>), unknown
<code>#[prax(...)]</code> attribute keys are compile errors in v0.11 — the macro rejects
them rather than silently ignoring them.
</p>
</div>
</section>
<!-- Relation Attributes -->
<section id="relations">
<h2 class="text-2xl font-semibold mb-4">Relation Attributes</h2>
<p class="text-muted mb-4">
Configure how models relate to each other with referential actions.
</p>
<div class="overflow-x-auto mb-6">
<table class="w-full text-sm">
<thead>
<tr class="border-b border-border">
<th class="text-left py-2 pr-4 font-semibold">Parameter</th>
<th class="text-left py-2 font-semibold">Description</th>
</tr>
</thead>
<tbody class="divide-y divide-border">
<tr><td class="py-2 pr-4"><code>fields</code></td><td>Local foreign key field(s)</td></tr>
<tr><td class="py-2 pr-4"><code>references</code></td><td>Referenced field(s) on related model</td></tr>
<tr><td class="py-2 pr-4"><code>name</code></td><td>Relation name for disambiguation</td></tr>
<tr><td class="py-2 pr-4"><code>onDelete</code></td><td>Action when referenced record deleted</td></tr>
<tr><td class="py-2 pr-4"><code>onUpdate</code></td><td>Action when referenced key updated</td></tr>
</tbody>
</table>
</div>
<h3 class="text-lg font-medium mb-3">Referential Actions</h3>
<div class="overflow-x-auto mb-6">
<table class="w-full text-sm">
<thead>
<tr class="border-b border-border">
<th class="text-left py-2 pr-4 font-semibold">Action</th>
<th class="text-left py-2 font-semibold">Behavior</th>
</tr>
</thead>
<tbody class="divide-y divide-border">
<tr><td class="py-2 pr-4"><code>Cascade</code></td><td>Delete/update related records</td></tr>
<tr><td class="py-2 pr-4"><code>Restrict</code></td><td>Prevent delete/update if references exist</td></tr>
<tr><td class="py-2 pr-4"><code>NoAction</code></td><td>Similar to Restrict (database-dependent)</td></tr>
<tr><td class="py-2 pr-4"><code>SetNull</code></td><td>Set foreign key to NULL</td></tr>
<tr><td class="py-2 pr-4"><code>SetDefault</code></td><td>Set foreign key to default value</td></tr>
</tbody>
</table>
</div>
<CodeBlock code={relationAttrs} lang="prax" filename="prax/schema.prax" />
</section>
<!-- ID Generation -->
<section id="id-generation">
<h2 class="text-2xl font-semibold mb-4">ID Generation</h2>
<p class="text-muted mb-4">
Prax supports multiple ID generation strategies for different use cases.
Choose the right one based on your requirements for uniqueness, sortability, and distribution.
</p>
<!-- UUID -->
<div class="mb-8">
<h3 class="text-xl font-semibold mb-3 flex items-center gap-2">
<span class="px-2 py-1 bg-blue-500/20 text-blue-400 rounded text-sm font-mono">uuid()</span>
UUID v4
</h3>
<p class="text-muted mb-4">
Universally Unique Identifier - the industry standard for distributed systems.
Generates 128-bit identifiers with extremely low collision probability.
</p>
<div class="grid md:grid-cols-2 gap-4 mb-4">
<div class="p-4 bg-green-500/10 border border-green-500/20 rounded-lg">
<h4 class="font-semibold text-green-400 mb-2">Advantages</h4>
<ul class="text-sm space-y-1 text-muted">
<li>• RFC 4122 standard format</li>
<li>• Native PostgreSQL <code>UUID</code> type support</li>
<li>• Widely recognized and supported</li>
<li>• Cryptographically random (v4)</li>
<li>• No central coordination needed</li>
</ul>
</div>
<div class="p-4 bg-red-500/10 border border-red-500/20 rounded-lg">
<h4 class="font-semibold text-red-400 mb-2">Considerations</h4>
<ul class="text-sm space-y-1 text-muted">
<li>• 36 characters with dashes</li>
<li>• Not sortable by creation time</li>
<li>• Random distribution (index fragmentation)</li>
<li>• Larger storage than auto-increment</li>
</ul>
</div>
</div>
<div class="p-3 bg-surface-elevated rounded-lg mb-4">
<span class="text-xs text-muted uppercase tracking-wider">Example Output</span>
<code class="block mt-1 text-accent font-mono">550e8400-e29b-41d4-a716-446655440000</code>
</div>
<CodeBlock code={uuidExamples} lang="prax" filename="prax/schema.prax" />
</div>
<!-- CUID -->
<div class="mb-8">
<h3 class="text-xl font-semibold mb-3 flex items-center gap-2">
<span class="px-2 py-1 bg-purple-500/20 text-purple-400 rounded text-sm font-mono">cuid()</span>
CUID
</h3>
<p class="text-muted mb-4">
Collision-resistant Unique Identifier - designed for horizontal scaling and distributed systems.
Includes a timestamp component for rough time-ordering.
</p>
<div class="grid md:grid-cols-2 gap-4 mb-4">
<div class="p-4 bg-green-500/10 border border-green-500/20 rounded-lg">
<h4 class="font-semibold text-green-400 mb-2">Advantages</h4>
<ul class="text-sm space-y-1 text-muted">
<li>• Roughly sortable by creation time</li>
<li>• URL-safe characters only</li>
<li>• Designed for distributed systems</li>
<li>• Shorter than UUID (25 chars)</li>
<li>• Includes machine fingerprint</li>
</ul>
</div>
<div class="p-4 bg-red-500/10 border border-red-500/20 rounded-lg">
<h4 class="font-semibold text-red-400 mb-2">Considerations</h4>
<ul class="text-sm space-y-1 text-muted">
<li>• Not a standard format</li>
<li>• Timestamp is extractable</li>
<li>• Consider CUID2 for new projects</li>
</ul>
</div>
</div>
<div class="p-3 bg-surface-elevated rounded-lg mb-4">
<span class="text-xs text-muted uppercase tracking-wider">Example Output</span>
<code class="block mt-1 text-accent font-mono">cjld2cjxh0000qzrmn831i7rn</code>
</div>
<CodeBlock code={cuidExamples} lang="prax" filename="prax/schema.prax" />
</div>
<!-- CUID2 -->
<div class="mb-8">
<h3 class="text-xl font-semibold mb-3 flex items-center gap-2">
<span class="px-2 py-1 bg-pink-500/20 text-pink-400 rounded text-sm font-mono">cuid2()</span>
CUID2
</h3>
<p class="text-muted mb-4">
Next-generation CUID with improved security. More unpredictable and shorter than the original.
<strong class="text-foreground">Recommended for new projects.</strong>
</p>
<div class="grid md:grid-cols-2 gap-4 mb-4">
<div class="p-4 bg-green-500/10 border border-green-500/20 rounded-lg">
<h4 class="font-semibold text-green-400 mb-2">Advantages</h4>
<ul class="text-sm space-y-1 text-muted">
<li>• More secure than CUID</li>
<li>• Shorter (24 characters)</li>
<li>• Better entropy distribution</li>
<li>• No extractable timestamp</li>
<li>• URL-safe characters</li>
</ul>
</div>
<div class="p-4 bg-red-500/10 border border-red-500/20 rounded-lg">
<h4 class="font-semibold text-red-400 mb-2">Considerations</h4>
<ul class="text-sm space-y-1 text-muted">
<li>• Not sortable by time</li>
<li>• Newer, less widespread adoption</li>
</ul>
</div>
</div>
<div class="p-3 bg-surface-elevated rounded-lg mb-4">
<span class="text-xs text-muted uppercase tracking-wider">Example Output</span>
<code class="block mt-1 text-accent font-mono">tz4a98xxat96iws9zmbrgj3a</code>
</div>
</div>
<!-- NanoID -->
<div class="mb-8">
<h3 class="text-xl font-semibold mb-3 flex items-center gap-2">
<span class="px-2 py-1 bg-orange-500/20 text-orange-400 rounded text-sm font-mono">nanoid()</span>
NanoID
</h3>
<p class="text-muted mb-4">
Compact, URL-friendly unique IDs with customizable length.
Perfect for short URLs, invite codes, and user-facing identifiers.
</p>
<div class="grid md:grid-cols-2 gap-4 mb-4">
<div class="p-4 bg-green-500/10 border border-green-500/20 rounded-lg">
<h4 class="font-semibold text-green-400 mb-2">Advantages</h4>
<ul class="text-sm space-y-1 text-muted">
<li>• URL-safe (A-Za-z0-9_-)</li>
<li>• Customizable length</li>
<li>• Compact (21 chars default)</li>
<li>• Cryptographically secure</li>
<li>• Fast generation</li>
</ul>
</div>
<div class="p-4 bg-red-500/10 border border-red-500/20 rounded-lg">
<h4 class="font-semibold text-red-400 mb-2">Considerations</h4>
<ul class="text-sm space-y-1 text-muted">
<li>• Not sortable by time</li>
<li>• Shorter IDs = higher collision risk</li>
<li>• No embedded metadata</li>
</ul>
</div>
</div>
<div class="p-3 bg-surface-elevated rounded-lg mb-4">
<span class="text-xs text-muted uppercase tracking-wider">Example Output</span>
<div class="mt-1 space-y-1">
<code class="block text-accent font-mono">V1StGXR8_Z5jdHi6B-myT</code>
<code class="block text-muted font-mono text-sm">nanoid(8): 5fX8pK2m</code>
</div>
</div>
<CodeBlock code={nanoidExamples} lang="prax" filename="prax/schema.prax" />
</div>
<!-- ULID -->
<div class="mb-8">
<h3 class="text-xl font-semibold mb-3 flex items-center gap-2">
<span class="px-2 py-1 bg-cyan-500/20 text-cyan-400 rounded text-sm font-mono">ulid()</span>
ULID
</h3>
<p class="text-muted mb-4">
Universally Unique Lexicographically Sortable Identifier.
Encodes creation timestamp, making IDs naturally sortable by time.
</p>
<div class="grid md:grid-cols-2 gap-4 mb-4">
<div class="p-4 bg-green-500/10 border border-green-500/20 rounded-lg">
<h4 class="font-semibold text-green-400 mb-2">Advantages</h4>
<ul class="text-sm space-y-1 text-muted">
<li>• Lexicographically sortable</li>
<li>• Encodes millisecond timestamp</li>
<li>• Case-insensitive</li>
<li>• Compatible with UUID (128-bit)</li>
<li>• Better index performance</li>
</ul>
</div>
<div class="p-4 bg-red-500/10 border border-red-500/20 rounded-lg">
<h4 class="font-semibold text-red-400 mb-2">Considerations</h4>
<ul class="text-sm space-y-1 text-muted">
<li>• Timestamp is extractable</li>
<li>• 26 characters (longer than CUID2)</li>
<li>• Limited entropy in same millisecond</li>
</ul>
</div>
</div>
<div class="p-3 bg-surface-elevated rounded-lg mb-4">
<span class="text-xs text-muted uppercase tracking-wider">Example Output</span>
<code class="block mt-1 text-accent font-mono">01ARZ3NDEKTSV4RRFFQ69G5FAV</code>
<div class="mt-2 text-xs text-muted font-mono">
<span class="text-cyan-400">01ARZ3NDEK</span><span class="text-gray-500">TSV4RRFFQ69G5FAV</span>
<div class="mt-1">
<span class="text-cyan-400">└─ timestamp ─┘</span><span class="text-gray-500">└── randomness ──┘</span>
</div>
</div>
</div>
<CodeBlock code={ulidExamples} lang="prax" filename="prax/schema.prax" />
</div>
<!-- Comparison Table -->
<div class="mb-8">
<h3 class="text-xl font-semibold mb-4">Comparison</h3>
<div class="overflow-x-auto">
<table class="w-full text-sm">
<thead>
<tr class="border-b border-border">
<th class="text-left py-2 pr-4 font-semibold">Type</th>
<th class="text-left py-2 pr-4 font-semibold">Length</th>
<th class="text-left py-2 pr-4 font-semibold">Sortable</th>
<th class="text-left py-2 pr-4 font-semibold">URL-Safe</th>
<th class="text-left py-2 font-semibold">Best For</th>
</tr>
</thead>
<tbody class="divide-y divide-border">
<tr>
<td class="py-2 pr-4"><code>uuid()</code></td>
<td class="py-2 pr-4">36</td>
<td class="py-2 pr-4">No</td>
<td class="py-2 pr-4">No (dashes)</td>
<td class="py-2">Database PKs, APIs</td>
</tr>
<tr>
<td class="py-2 pr-4"><code>cuid()</code></td>
<td class="py-2 pr-4">25</td>
<td class="py-2 pr-4">Roughly</td>
<td class="py-2 pr-4">Yes</td>
<td class="py-2">Distributed systems</td>
</tr>
<tr>
<td class="py-2 pr-4"><code>cuid2()</code></td>
<td class="py-2 pr-4">24</td>
<td class="py-2 pr-4">No</td>
<td class="py-2 pr-4">Yes</td>
<td class="py-2">Security-sensitive apps</td>
</tr>
<tr>
<td class="py-2 pr-4"><code>nanoid()</code></td>
<td class="py-2 pr-4">21*</td>
<td class="py-2 pr-4">No</td>
<td class="py-2 pr-4">Yes</td>
<td class="py-2">Short URLs, invite codes</td>
</tr>
<tr>
<td class="py-2 pr-4"><code>ulid()</code></td>
<td class="py-2 pr-4">26</td>
<td class="py-2 pr-4">Yes</td>
<td class="py-2 pr-4">Yes</td>
<td class="py-2">Time-series, logs, events</td>
</tr>
</tbody>
</table>
<p class="text-xs text-muted mt-2">* NanoID length is customizable</p>
</div>
<CodeBlock code={idComparison} lang="prax" filename="comparison.prax" />
</div>
</section>
<!-- String Validators -->
<section id="string-validators">
<h2 class="text-2xl font-semibold mb-4">String Validators</h2>
<p class="text-muted mb-4">
Validate string field content with built-in rules.
</p>
<div class="grid md:grid-cols-2 gap-4 mb-6">
<div class="p-4 bg-surface-elevated rounded-lg">
<h4 class="font-semibold mb-2">Format Validators</h4>
<ul class="text-sm space-y-1 text-muted">
<li><code>@validate.email</code> - Email format</li>
<li><code>@validate.url</code> - URL format</li>
<li><code>@validate.uuid</code> - UUID format</li>
<li><code>@validate.cuid</code> - CUID format</li>
<li><code>@validate.nanoid</code> - NanoID format</li>
<li><code>@validate.ulid</code> - ULID format</li>
<li><code>@validate.ip</code> - IP address (v4 or v6)</li>
<li><code>@validate.ipv4</code> - IPv4 only</li>
<li><code>@validate.ipv6</code> - IPv6 only</li>
</ul>
</div>
<div class="p-4 bg-surface-elevated rounded-lg">
<h4 class="font-semibold mb-2">Content Validators</h4>
<ul class="text-sm space-y-1 text-muted">
<li><code>@validate.alpha</code> - Letters only</li>
<li><code>@validate.alphanumeric</code> - Letters and numbers</li>
<li><code>@validate.lowercase</code> - Lowercase only</li>
<li><code>@validate.uppercase</code> - Uppercase only</li>
<li><code>@validate.slug</code> - URL slug format</li>
<li><code>@validate.hex</code> - Hexadecimal string</li>
<li><code>@validate.base64</code> - Base64 encoded</li>
<li><code>@validate.json</code> - Valid JSON string</li>
</ul>
</div>
</div>
<CodeBlock code={stringValidators} lang="prax" filename="prax/schema.prax" />
</section>
<!-- Numeric Validators -->
<section id="numeric-validators">
<h2 class="text-2xl font-semibold mb-4">Numeric Validators</h2>
<p class="text-muted mb-4">
Constrain numeric field values with range and sign validators.
</p>
<div class="overflow-x-auto mb-6">
<table class="w-full text-sm">
<thead>
<tr class="border-b border-border">
<th class="text-left py-2 pr-4 font-semibold">Validator</th>
<th class="text-left py-2 font-semibold">Description</th>
</tr>
</thead>
<tbody class="divide-y divide-border">
<tr><td class="py-2 pr-4"><code>@validate.min(n)</code></td><td>Minimum value (inclusive)</td></tr>
<tr><td class="py-2 pr-4"><code>@validate.max(n)</code></td><td>Maximum value (inclusive)</td></tr>
<tr><td class="py-2 pr-4"><code>@validate.range(min, max)</code></td><td>Value between min and max</td></tr>
<tr><td class="py-2 pr-4"><code>@validate.positive</code></td><td>Greater than zero</td></tr>
<tr><td class="py-2 pr-4"><code>@validate.negative</code></td><td>Less than zero</td></tr>
<tr><td class="py-2 pr-4"><code>@validate.nonNegative</code></td><td>Zero or greater</td></tr>
<tr><td class="py-2 pr-4"><code>@validate.nonPositive</code></td><td>Zero or less</td></tr>
<tr><td class="py-2 pr-4"><code>@validate.integer</code></td><td>Must be whole number</td></tr>
<tr><td class="py-2 pr-4"><code>@validate.multipleOf(n)</code></td><td>Must be multiple of n</td></tr>
<tr><td class="py-2 pr-4"><code>@validate.finite</code></td><td>Not Infinity or NaN</td></tr>
</tbody>
</table>
</div>
<CodeBlock code={numericValidators} lang="prax" filename="prax/schema.prax" />
</section>
<!-- Array Validators -->
<section id="array-validators">
<h2 class="text-2xl font-semibold mb-4">Array Validators</h2>
<p class="text-muted mb-4">
Validate array fields for length and content constraints.
</p>
<div class="overflow-x-auto mb-6">
<table class="w-full text-sm">
<thead>
<tr class="border-b border-border">
<th class="text-left py-2 pr-4 font-semibold">Validator</th>
<th class="text-left py-2 font-semibold">Description</th>
</tr>
</thead>
<tbody class="divide-y divide-border">
<tr><td class="py-2 pr-4"><code>@validate.minItems(n)</code></td><td>Minimum array length</td></tr>
<tr><td class="py-2 pr-4"><code>@validate.maxItems(n)</code></td><td>Maximum array length</td></tr>
<tr><td class="py-2 pr-4"><code>@validate.items(min, max)</code></td><td>Array length range</td></tr>
<tr><td class="py-2 pr-4"><code>@validate.unique</code></td><td>All items must be unique</td></tr>
<tr><td class="py-2 pr-4"><code>@validate.nonEmpty</code></td><td>At least one item required</td></tr>
</tbody>
</table>
</div>
<CodeBlock code={arrayValidators} lang="prax" filename="prax/schema.prax" />
</section>
<!-- Date Validators -->
<section id="date-validators">
<h2 class="text-2xl font-semibold mb-4">Date Validators</h2>
<p class="text-muted mb-4">
Validate datetime fields with temporal constraints.
</p>
<div class="overflow-x-auto mb-6">
<table class="w-full text-sm">
<thead>
<tr class="border-b border-border">
<th class="text-left py-2 pr-4 font-semibold">Validator</th>
<th class="text-left py-2 font-semibold">Description</th>
</tr>
</thead>
<tbody class="divide-y divide-border">
<tr><td class="py-2 pr-4"><code>@validate.past</code></td><td>Must be in the past</td></tr>
<tr><td class="py-2 pr-4"><code>@validate.future</code></td><td>Must be in the future</td></tr>
<tr><td class="py-2 pr-4"><code>@validate.pastOrPresent</code></td><td>Not in the future</td></tr>
<tr><td class="py-2 pr-4"><code>@validate.futureOrPresent</code></td><td>Not in the past</td></tr>
<tr><td class="py-2 pr-4"><code>@validate.after("date")</code></td><td>After specific date</td></tr>
<tr><td class="py-2 pr-4"><code>@validate.before("date")</code></td><td>Before specific date</td></tr>
</tbody>
</table>
</div>
<CodeBlock code={dateValidators} lang="prax" filename="prax/schema.prax" />
</section>
<!-- General Validators -->
<section id="general-validators">
<h2 class="text-2xl font-semibold mb-4">General Validators</h2>
<p class="text-muted mb-4">
Universal validators that work across different field types.
</p>
<div class="overflow-x-auto mb-6">
<table class="w-full text-sm">
<thead>
<tr class="border-b border-border">
<th class="text-left py-2 pr-4 font-semibold">Validator</th>
<th class="text-left py-2 font-semibold">Description</th>
</tr>
</thead>
<tbody class="divide-y divide-border">
<tr><td class="py-2 pr-4"><code>@validate.required</code></td><td>Field must have a value (even if optional type)</td></tr>
<tr><td class="py-2 pr-4"><code>@validate.notEmpty</code></td><td>Non-empty string/array</td></tr>
<tr><td class="py-2 pr-4"><code>@validate.oneOf(...)</code></td><td>Value must be one of specified options</td></tr>
<tr><td class="py-2 pr-4"><code>@validate.custom("fn")</code></td><td>Custom validation function</td></tr>
</tbody>
</table>
</div>
<CodeBlock code={generalValidators} lang="prax" filename="prax/schema.prax" />
</section>
<!-- Default Value Functions -->
<section id="defaults">
<h2 class="text-2xl font-semibold mb-4">Default Value Functions</h2>
<p class="text-muted mb-4">
Auto-generate values for fields using built-in functions.
</p>
<div class="overflow-x-auto mb-6">
<table class="w-full text-sm">
<thead>
<tr class="border-b border-border">
<th class="text-left py-2 pr-4 font-semibold">Function</th>
<th class="text-left py-2 font-semibold">Description</th>
</tr>
</thead>
<tbody class="divide-y divide-border">
<tr><td class="py-2 pr-4"><code>now()</code></td><td>Current timestamp</td></tr>
<tr><td class="py-2 pr-4"><code>uuid()</code></td><td>Random UUID v4</td></tr>
<tr><td class="py-2 pr-4"><code>cuid()</code></td><td>Collision-resistant unique ID</td></tr>
<tr><td class="py-2 pr-4"><code>cuid2()</code></td><td>Next-gen CUID (more secure)</td></tr>
<tr><td class="py-2 pr-4"><code>nanoid()</code></td><td>URL-friendly unique ID</td></tr>
<tr><td class="py-2 pr-4"><code>nanoid(n)</code></td><td>NanoID with custom length</td></tr>
<tr><td class="py-2 pr-4"><code>ulid()</code></td><td>Sortable unique ID</td></tr>
<tr><td class="py-2 pr-4"><code>autoincrement()</code></td><td>Auto-incrementing integer</td></tr>
<tr><td class="py-2 pr-4"><code>dbgenerated("expr")</code></td><td>Database-generated expression</td></tr>
</tbody>
</table>
</div>
<CodeBlock code={defaultFunctions} lang="prax" filename="prax/schema.prax" />
</section>
<!-- Database-specific Attributes -->
<section id="db-specific">
<h2 class="text-2xl font-semibold mb-4">Database-Specific Types</h2>
<p class="text-muted mb-4">
Map fields to native database column types with <code>@db.*</code> attributes.
</p>
<div class="grid md:grid-cols-2 gap-4 mb-6">
<div class="p-4 bg-surface-elevated rounded-lg">
<h4 class="font-semibold mb-2">PostgreSQL Types</h4>
<ul class="text-sm space-y-1 text-muted">
<li><code>@db.Text</code> - Unlimited text</li>
<li><code>@db.JsonB</code> - Binary JSON (indexable)</li>
<li><code>@db.Uuid</code> - Native UUID</li>
<li><code>@db.Xml</code> - XML type</li>
<li><code>@db.Inet</code> - IP address</li>
<li><code>@db.Cidr</code> - Network address</li>
<li><code>@db.MacAddr</code> - MAC address</li>
<li><code>@db.Decimal(p, s)</code> - Precise decimal</li>
</ul>
</div>
<div class="p-4 bg-surface-elevated rounded-lg">
<h4 class="font-semibold mb-2">MySQL Types</h4>
<ul class="text-sm space-y-1 text-muted">
<li><code>@db.TinyText</code> - 255 bytes</li>
<li><code>@db.MediumText</code> - 16 MB</li>
<li><code>@db.LongText</code> - 4 GB</li>
<li><code>@db.TinyInt</code> - Tiny integer</li>
<li><code>@db.MediumInt</code> - Medium integer</li>
<li><code>@db.Year</code> - Year type</li>
<li><code>@db.VarChar(n)</code> - Variable char</li>
<li><code>@db.Charset("utf8mb4")</code> - Charset</li>
</ul>
</div>
</div>
<CodeBlock code={dbSpecific} lang="prax" filename="prax/schema.prax" />
</section>
<!-- Documentation Attributes -->
<section id="documentation">
<h2 class="text-2xl font-semibold mb-4">Documentation & Metadata</h2>
<p class="text-muted mb-4">
Add metadata and documentation to fields using doc comments.
</p>
<div class="grid md:grid-cols-2 gap-4 mb-6">
<div class="p-4 bg-surface-elevated rounded-lg">
<h4 class="font-semibold mb-2">Visibility</h4>
<ul class="text-sm space-y-1 text-muted">
<li><code>@hidden</code> - Exclude from public API</li>
<li><code>@internal</code> - Admin-only access</li>
<li><code>@sensitive</code> - Mask in logs</li>
<li><code>@readonly</code> - Not settable via API</li>
<li><code>@writeonly</code> - Not in responses</li>
</ul>
</div>
<div class="p-4 bg-surface-elevated rounded-lg">
<h4 class="font-semibold mb-2">Documentation</h4>
<ul class="text-sm space-y-1 text-muted">
<li><code>@deprecated</code> - Mark as deprecated</li>
<li><code>@since version</code> - Version introduced</li>
<li><code>@example value</code> - Example value</li>
<li><code>@label text</code> - Display label</li>
<li><code>@placeholder text</code> - Input placeholder</li>
</ul>
</div>
</div>
<CodeBlock code={docAttrs} lang="prax" filename="prax/schema.prax" />
</section>
<!-- Index Types -->
<section id="index-types">
<h2 class="text-2xl font-semibold mb-4">Index Types</h2>
<p class="text-muted mb-4">
Create different types of database indexes for query optimization.
</p>
<div class="overflow-x-auto mb-6">
<table class="w-full text-sm border border-border rounded-lg">
<thead class="bg-muted/50">
<tr>
<th class="text-left px-4 py-3 font-semibold">Type</th>
<th class="text-left px-4 py-3 font-semibold">Use Case</th>
<th class="text-left px-4 py-3 font-semibold">PG</th>
<th class="text-left px-4 py-3 font-semibold">MySQL</th>
<th class="text-left px-4 py-3 font-semibold">SQLite</th>
</tr>
</thead>
<tbody>
{indexTypesTable.map((row) => (
<tr class="border-t border-border">
<td class="px-4 py-2 font-mono text-primary">{row.type}</td>
<td class="px-4 py-2 text-muted">{row.use}</td>
<td class="px-4 py-2">{row.pg}</td>
<td class="px-4 py-2">{row.mysql}</td>
<td class="px-4 py-2">{row.sqlite}</td>
</tr>
))}
</tbody>
</table>
</div>
<p class="text-sm text-muted mb-4">* Requires pgvector extension</p>
<CodeBlock code={indexTypes} lang="prax" filename="prax/schema.prax" />
</section>
<!-- Vector Indexes -->
<section id="vector-indexes">
<h2 class="text-2xl font-semibold mb-4">Vector Indexes</h2>
<p class="text-muted mb-4">
Vector indexes enable fast approximate nearest neighbor (ANN) search for AI/ML embeddings.
Requires the <code>pgvector</code> PostgreSQL extension.
</p>
<h3 class="text-lg font-medium mb-3">Distance Operations</h3>
<div class="overflow-x-auto mb-6">
<table class="w-full text-sm border border-border rounded-lg">
<thead class="bg-muted/50">
<tr>
<th class="text-left px-4 py-3 font-semibold">Operation</th>
<th class="text-left px-4 py-3 font-semibold">Description</th>
<th class="text-left px-4 py-3 font-semibold">Best For</th>
</tr>
</thead>
<tbody>
{vectorOpsTable.map((row) => (
<tr class="border-t border-border">
<td class="px-4 py-2 font-mono text-primary">{row.op}</td>
<td class="px-4 py-2">{row.desc}</td>
<td class="px-4 py-2 text-muted">{row.best}</td>
</tr>
))}
</tbody>
</table>
</div>
<div class="mb-6 p-4 bg-primary/10 border border-primary/20 rounded-lg">
<h4 class="font-medium mb-2">💡 Choosing an Index Type</h4>
<ul class="text-sm text-muted space-y-1 list-disc list-inside">
<li><strong>HNSW:</strong> Best recall, faster queries, slower builds - recommended for most cases</li>
<li><strong>IVFFlat:</strong> Faster builds, slightly lower recall - good for large datasets (1M+ vectors)</li>
</ul>
</div>
<CodeBlock code={vectorIndexTypes} lang="prax" filename="prax/schema.prax" />
</section>
</div>
</article>
</DocsLayout>