prax_orm/lib.rs
1//! # Prax - A Next-Generation ORM for Rust
2//!
3//! Prax is a type-safe, async-first Object-Relational Mapper (ORM) for Rust,
4//! inspired by Prisma. It provides a delightful developer experience with
5//! compile-time guarantees and a powerful schema definition language.
6//!
7//! ## Features
8//!
9//! - **Schema Definition Language**: Define your data models in a clear, readable `.prax` file
10//! - **Type-Safe Queries**: Compile-time checked queries with a fluent builder API
11//! - **Async-First**: Built on Tokio for high-performance async database operations
12//! - **Multi-Database Support**: PostgreSQL, MySQL, SQLite, and MongoDB
13//! - **Automatic Migrations**: Generate and apply database migrations from your schema
14//! - **Relations**: Define and query relationships between models with ease
15//! - **Transactions**: Full transaction support with savepoints and isolation levels
16//! - **Middleware System**: Extensible query interception for logging, metrics, and more
17//! - **Multi-Tenant Support**: Built-in support for multi-tenant applications
18//!
19//! ## Quick Start
20//!
21//! ### 1. Define Your Schema
22//!
23//! Create a `prax/schema.prax` file in your project:
24//!
25//! ```text
26//! // Models define your database tables
27//! model User {
28//! id Int @id @auto
29//! email String @unique
30//! name String?
31//! posts Post[]
32//! createdAt DateTime @default(now())
33//! }
34//!
35//! model Post {
36//! id Int @id @auto
37//! title String
38//! content String? @db.Text
39//! published Boolean @default(false)
40//! authorId Int
41//! author User @relation(fields: [authorId], references: [id])
42//! }
43//! ```
44//!
45//! ### 2. Configure Your Database
46//!
47//! Create a `prax.toml` configuration file in your project root:
48//!
49//! ```toml
50//! [database]
51//! provider = "postgresql"
52//! url = "${DATABASE_URL}"
53//!
54//! [database.pool]
55//! max_connections = 10
56//!
57//! [generator.client]
58//! output = "./src/generated"
59//! ```
60//!
61//! ### 3. Use in Your Application
62//!
63//! ```rust,ignore
64//! use prax::prelude::*;
65//!
66//! #[tokio::main]
67//! async fn main() -> Result<(), prax::SchemaError> {
68//! // Initialize the client (in real usage, generated from schema)
69//! let client = PraxClient::new("postgresql://localhost/mydb").await?;
70//!
71//! // Create a new user
72//! let user = client
73//! .user()
74//! .create(CreateData::new()
75//! .set("email", "alice@example.com")
76//! .set("name", "Alice"))
77//! .exec()
78//! .await?;
79//!
80//! // Find users with filtering
81//! let users = client
82//! .user()
83//! .find_many()
84//! .r#where(user::email::contains("@example.com"))
85//! .order_by(user::createdAt::desc())
86//! .take(10)
87//! .exec()
88//! .await?;
89//!
90//! // Update a user
91//! let updated = client
92//! .user()
93//! .update()
94//! .r#where(user::id::equals(1))
95//! .data(UpdateData::new().set("name", "Alice Smith"))
96//! .exec()
97//! .await?;
98//!
99//! // Delete a user
100//! client
101//! .user()
102//! .delete()
103//! .r#where(user::id::equals(1))
104//! .exec()
105//! .await?;
106//!
107//! Ok(())
108//! }
109//! ```
110//!
111//! ## Schema Language
112//!
113//! The Prax schema language provides a powerful way to define your data models:
114//!
115//! ### Models
116//!
117//! Models represent database tables:
118//!
119//! ```text
120//! model User {
121//! id Int @id @auto // Primary key with auto-increment
122//! email String @unique // Unique constraint
123//! name String? // Optional (nullable) field
124//! }
125//! ```
126//!
127//! ### Field Types
128//!
129//! | Type | Description | Database Mapping |
130//! |------------|--------------------------------|-------------------|
131//! | `Int` | Integer | INTEGER/INT |
132//! | `BigInt` | 64-bit integer | BIGINT |
133//! | `Float` | Floating point | FLOAT/DOUBLE |
134//! | `Decimal` | Exact decimal | DECIMAL/NUMERIC |
135//! | `String` | Text | VARCHAR/TEXT |
136//! | `Boolean` | True/false | BOOLEAN |
137//! | `DateTime` | Date and time | TIMESTAMP |
138//! | `Date` | Date only | DATE |
139//! | `Time` | Time only | TIME |
140//! | `Json` | JSON data | JSON/JSONB |
141//! | `Bytes` | Binary data | BYTEA/BLOB |
142//! | `Uuid` | UUID | UUID |
143//!
144//! ### Field Attributes
145//!
146//! | Attribute | Description |
147//! |------------------------|------------------------------------------|
148//! | `@id` | Primary key |
149//! | `@auto` | Auto-increment/generate |
150//! | `@unique` | Unique constraint |
151//! | `@default(value)` | Default value |
152//! | `@map("name")` | Custom column name |
153//! | `@db.Type` | Database-specific type |
154//! | `@index` | Create index on field |
155//! | `@updated_at` | Auto-update timestamp |
156//! | `@relation(...)` | Define relation |
157//!
158//! ### Relations
159//!
160//! Define relationships between models:
161//!
162//! ```text
163//! model User {
164//! id Int @id @auto
165//! posts Post[] // One-to-many
166//! }
167//!
168//! model Post {
169//! id Int @id @auto
170//! authorId Int
171//! author User @relation(fields: [authorId], references: [id])
172//! }
173//! ```
174//!
175//! ### Enums
176//!
177//! Define enumerated types:
178//!
179//! ```text
180//! enum Role {
181//! User
182//! Admin
183//! Moderator
184//! }
185//!
186//! model User {
187//! id Int @id @auto
188//! role Role @default(User)
189//! }
190//! ```
191//!
192//! ## Query API
193//!
194//! ### Finding Records
195//!
196//! ```rust,ignore
197//! // Find many with filters
198//! let users = client
199//! .user()
200//! .find_many()
201//! .r#where(user::email::contains("@example.com"))
202//! .r#where(user::createdAt::gt(some_date))
203//! .order_by(user::name::asc())
204//! .skip(10)
205//! .take(20)
206//! .exec()
207//! .await?;
208//!
209//! // Find unique record
210//! let user = client
211//! .user()
212//! .find_unique()
213//! .r#where(user::id::equals(1))
214//! .exec()
215//! .await?;
216//!
217//! // Find first matching
218//! let user = client
219//! .user()
220//! .find_first()
221//! .r#where(user::email::ends_with("@company.com"))
222//! .exec()
223//! .await?;
224//! ```
225//!
226//! ### Filter Operations
227//!
228//! ```rust,ignore
229//! // Equality
230//! user::email::equals("test@example.com")
231//!
232//! // Comparison
233//! user::age::gt(18)
234//! user::age::gte(18)
235//! user::age::lt(65)
236//! user::age::lte(65)
237//!
238//! // String operations
239//! user::name::contains("john")
240//! user::name::starts_with("Dr.")
241//! user::name::ends_with("Smith")
242//!
243//! // List operations
244//! user::status::in_list(vec!["active", "pending"])
245//! user::status::not_in(vec!["banned"])
246//!
247//! // Null checks
248//! user::deleted_at::is_null()
249//! user::verified_at::is_not_null()
250//!
251//! // Combining filters
252//! Filter::and(vec![
253//! user::active::equals(true),
254//! user::email::contains("@company.com"),
255//! ])
256//!
257//! Filter::or(vec![
258//! user::role::equals(Role::Admin),
259//! user::role::equals(Role::Moderator),
260//! ])
261//! ```
262//!
263//! ### Creating Records
264//!
265//! ```rust,ignore
266//! // Single create
267//! let user = client
268//! .user()
269//! .create(CreateData::new()
270//! .set("email", "new@example.com")
271//! .set("name", "New User"))
272//! .exec()
273//! .await?;
274//!
275//! // Create many
276//! let count = client
277//! .user()
278//! .create_many(vec![
279//! CreateData::new().set("email", "user1@example.com"),
280//! CreateData::new().set("email", "user2@example.com"),
281//! ])
282//! .exec()
283//! .await?;
284//!
285//! // Create with nested relation
286//! let user = client
287//! .user()
288//! .create(CreateData::new()
289//! .set("email", "author@example.com")
290//! .connect("posts", post::id::equals(1)))
291//! .exec()
292//! .await?;
293//! ```
294//!
295//! ### Updating Records
296//!
297//! ```rust,ignore
298//! // Update single
299//! let user = client
300//! .user()
301//! .update()
302//! .r#where(user::id::equals(1))
303//! .data(UpdateData::new()
304//! .set("name", "Updated Name")
305//! .increment("login_count", 1))
306//! .exec()
307//! .await?;
308//!
309//! // Update many
310//! let count = client
311//! .user()
312//! .update_many()
313//! .r#where(user::active::equals(false))
314//! .data(UpdateData::new().set("active", true))
315//! .exec()
316//! .await?;
317//!
318//! // Upsert (create or update)
319//! let user = client
320//! .user()
321//! .upsert()
322//! .r#where(user::email::equals("test@example.com"))
323//! .create(CreateData::new().set("email", "test@example.com"))
324//! .update(UpdateData::new().set("login_count", 1))
325//! .exec()
326//! .await?;
327//! ```
328//!
329//! ### Deleting Records
330//!
331//! ```rust,ignore
332//! // Delete single
333//! client
334//! .user()
335//! .delete()
336//! .r#where(user::id::equals(1))
337//! .exec()
338//! .await?;
339//!
340//! // Delete many
341//! let count = client
342//! .user()
343//! .delete_many()
344//! .r#where(user::active::equals(false))
345//! .exec()
346//! .await?;
347//! ```
348//!
349//! ### Including Relations
350//!
351//! ```rust,ignore
352//! // Include related records
353//! let user = client
354//! .user()
355//! .find_unique()
356//! .r#where(user::id::equals(1))
357//! .include(user::posts::include())
358//! .exec()
359//! .await?;
360//!
361//! // Nested includes
362//! let post = client
363//! .post()
364//! .find_unique()
365//! .r#where(post::id::equals(1))
366//! .include(post::author::include(
367//! user::posts::include()
368//! ))
369//! .exec()
370//! .await?;
371//!
372//! // Select specific fields
373//! let user = client
374//! .user()
375//! .find_unique()
376//! .r#where(user::id::equals(1))
377//! .select(user::select!(id, email, name))
378//! .exec()
379//! .await?;
380//! ```
381//!
382//! ## Transactions
383//!
384//! ```rust,ignore
385//! // Basic transaction
386//! let result = client.transaction(|tx| async move {
387//! let user = tx.user()
388//! .create(CreateData::new().set("email", "tx@example.com"))
389//! .exec()
390//! .await?;
391//!
392//! tx.post()
393//! .create(CreateData::new()
394//! .set("title", "My First Post")
395//! .set("authorId", user.id))
396//! .exec()
397//! .await?;
398//!
399//! Ok(user)
400//! }).await?;
401//!
402//! // Transaction with options
403//! let result = client
404//! .transaction_with_config(TransactionConfig::new()
405//! .isolation(IsolationLevel::Serializable)
406//! .timeout(Duration::from_secs(30)))
407//! .run(|tx| async move {
408//! // ... operations
409//! Ok(())
410//! })
411//! .await?;
412//! ```
413//!
414//! ## Raw SQL
415//!
416//! When you need to execute raw SQL:
417//!
418//! ```rust,ignore
419//! use prax::raw_query;
420//!
421//! // Type-safe raw query
422//! let users: Vec<User> = client
423//! .query_raw(raw_query!(
424//! "SELECT * FROM users WHERE email = {}",
425//! "test@example.com"
426//! ))
427//! .await?;
428//!
429//! // Execute raw SQL
430//! let affected = client
431//! .execute_raw(raw_query!(
432//! "UPDATE users SET verified = true WHERE id = {}",
433//! user_id
434//! ))
435//! .await?;
436//! ```
437//!
438//! ## Aggregations
439//!
440//! ```rust,ignore
441//! // Count
442//! let count = client
443//! .user()
444//! .count()
445//! .r#where(user::active::equals(true))
446//! .exec()
447//! .await?;
448//!
449//! // Aggregate functions
450//! let stats = client
451//! .post()
452//! .aggregate()
453//! .avg(post::views)
454//! .sum(post::views)
455//! .min(post::created_at)
456//! .max(post::created_at)
457//! .exec()
458//! .await?;
459//!
460//! // Group by
461//! let by_status = client
462//! .post()
463//! .group_by(post::status)
464//! .count()
465//! .having(count::gt(10))
466//! .exec()
467//! .await?;
468//! ```
469//!
470//! ## Multi-Tenant Applications
471//!
472//! Prax provides built-in support for multi-tenant applications:
473//!
474//! ```rust,ignore
475//! use prax::tenant::{TenantConfig, TenantMiddleware, IsolationStrategy};
476//!
477//! // Configure tenant isolation
478//! let config = TenantConfig::builder()
479//! .strategy(IsolationStrategy::RowLevel {
480//! tenant_column: "tenant_id".into(),
481//! })
482//! .build();
483//!
484//! // Add tenant middleware
485//! let client = client.with_middleware(TenantMiddleware::new(config));
486//!
487//! // Set tenant context for requests
488//! let client = client.with_tenant("tenant-123");
489//!
490//! // All queries are automatically scoped to this tenant
491//! let users = client.user().find_many().exec().await?;
492//! ```
493//!
494//! ## Middleware
495//!
496//! Extend Prax with custom middleware:
497//!
498//! ```rust,ignore
499//! use prax::middleware::{LoggingMiddleware, MetricsMiddleware, MiddlewareBuilder};
500//!
501//! let client = client.with_middleware(
502//! MiddlewareBuilder::new()
503//! .add(LoggingMiddleware::new())
504//! .add(MetricsMiddleware::new())
505//! .build()
506//! );
507//! ```
508//!
509//! ## CLI Commands
510//!
511//! Prax provides a CLI for common operations:
512//!
513//! ```bash
514//! # Initialize a new project
515//! prax init
516//!
517//! # Generate client code from schema
518//! prax generate
519//!
520//! # Create a new migration
521//! prax migrate create "add_users_table"
522//!
523//! # Apply pending migrations
524//! prax migrate deploy
525//!
526//! # Reset database
527//! prax migrate reset
528//!
529//! # Validate schema
530//! prax validate
531//!
532//! # Format schema files
533//! prax format
534//! ```
535//!
536//! ## Crate Features
537//!
538//! Enable features in your `Cargo.toml`:
539//!
540//! ```toml
541//! [dependencies]
542//! prax = { version = "0.1", features = ["postgres", "mysql", "sqlite"] }
543//! ```
544//!
545//! | Feature | Description |
546//! |------------|------------------------------------------------|
547//! | `postgres` | PostgreSQL support via `tokio-postgres` |
548//! | `mysql` | MySQL support via `mysql_async` |
549//! | `sqlite` | SQLite support via `tokio-rusqlite` |
550//! | `mongodb` | MongoDB support |
551//! | `sqlx` | Alternative backend with compile-time checks |
552//! | `tracing` | Integration with the `tracing` crate |
553//! | `serde` | Serialization support for schema types |
554//!
555//! ## Error Handling
556//!
557//! Prax provides detailed error types with actionable messages:
558//!
559//! ```rust,ignore
560//! use prax::error::{QueryError, ErrorCode};
561//!
562//! match client.user().find_unique().r#where(user::id::equals(1)).exec().await {
563//! Ok(user) => println!("Found: {:?}", user),
564//! Err(e) => {
565//! eprintln!("Error: {}", e);
566//! eprintln!("Code: {:?}", e.code());
567//! if let Some(suggestion) = e.suggestion() {
568//! eprintln!("Suggestion: {}", suggestion);
569//! }
570//! }
571//! }
572//! ```
573//!
574//! ## Performance
575//!
576//! Prax is designed for high performance:
577//!
578//! - **Connection Pooling**: Built-in connection pool with configurable limits
579//! - **Prepared Statement Caching**: Automatic caching of prepared statements
580//! - **Batch Operations**: Efficient bulk create, update, and delete
581//! - **Lazy Loading**: Relations are loaded only when requested
582//!
583//! ## Further Reading
584//!
585//! - [Schema Documentation](schema/index.html)
586//! - [Query API](query/index.html)
587//! - [Configuration](config/index.html)
588//! - [Migration Guide](migrate/index.html)
589//! - [Examples](https://github.com/pegasusheavy/prax/tree/main/examples)
590
591#![cfg_attr(docsrs, feature(doc_cfg))]
592#![deny(missing_docs)]
593#![deny(rustdoc::broken_intra_doc_links)]
594
595/// Schema parsing and AST types.
596///
597/// This module provides everything needed to work with Prax schema files:
598///
599/// - [`parse_schema`](schema::parse_schema) - Parse a schema string
600/// - [`parse_schema_file`](schema::parse_schema_file) - Parse a schema from a file
601/// - [`validate_schema`](schema::validate_schema) - Parse and validate a schema
602/// - [`PraxConfig`](schema::PraxConfig) - Configuration from `prax.toml`
603/// - [`Schema`](schema::Schema) - The parsed schema representation
604///
605/// ## Example
606///
607/// ```rust,ignore
608/// use prax::schema::{parse_schema, validate_schema, PraxConfig};
609///
610/// // Parse a schema
611/// let schema = parse_schema(r#"
612/// model User {
613/// id Int @id @auto
614/// email String @unique
615/// }
616/// "#)?;
617///
618/// // Access schema information
619/// println!("Models: {:?}", schema.model_names().collect::<Vec<_>>());
620///
621/// // Load configuration
622/// let config = PraxConfig::from_file("prax.toml")?;
623/// println!("Database: {}", config.database.provider);
624/// ```
625pub mod schema {
626 pub use prax_schema::*;
627}
628
629// Re-export proc macros
630pub use prax_codegen::Model;
631pub use prax_codegen::prax_schema;
632
633/// Prelude module for convenient imports.
634///
635/// Import everything you need with a single line:
636///
637/// ```rust,ignore
638/// use prax::prelude::*;
639/// ```
640///
641/// This includes:
642/// - Schema parsing functions
643/// - Configuration types
644/// - Common traits and types
645pub mod prelude {
646 pub use crate::schema::{PraxConfig, Schema, parse_schema, parse_schema_file};
647 pub use crate::{Model, prax_schema};
648}
649
650// Re-export key types at the crate root
651pub use schema::{Schema, SchemaError};
652
653/// Error types for the Prax ORM.
654///
655/// The main error type is [`SchemaError`] which covers all schema-related
656/// errors including parsing, validation, and file I/O.
657pub mod error {
658 pub use prax_schema::SchemaError;
659}