prax-orm 0.11.0

A next-generation, type-safe ORM for Rust inspired by Prisma
Documentation
---
import DocsLayout from '../../layouts/DocsLayout.astro';
import CodeBlock from '../../components/CodeBlock.astro';

const basicExample = `use prax_query::middleware::{
    BoxFuture, LoggingMiddleware, LogLevel, MetricsMiddleware, MiddlewareResult,
    MiddlewareStack, QueryContext, QueryResponse, TimingMiddleware,
};

// Create a middleware stack
let stack = MiddlewareStack::new()
    .with(LoggingMiddleware::new().with_level(LogLevel::Debug))
    .with(TimingMiddleware::new())
    .with(MetricsMiddleware::in_memory().0);

// There is no PraxClient::with_middleware — middleware composes at the
// prax-query level: run the stack around engine calls with a terminal
// handler that forwards the (possibly rewritten) query to the engine.
let response = stack
    .execute(
        QueryContext::new("SELECT * FROM users", vec![]),
        |ctx| -> BoxFuture<'_, MiddlewareResult<QueryResponse>> {
            Box::pin(async move {
                let rows = engine
                    .query_many::<User>(ctx.sql(), ctx.params().to_vec())
                    .await?;
                Ok(QueryResponse::new(serde_json::json!({ "rows": rows.len() })))
            })
        },
    )
    .await?;`;

const loggingMiddleware = `use prax_query::middleware::{LogLevel, LoggingMiddleware};

// Builder-style setup
let logging = LoggingMiddleware::new()
    .with_level(LogLevel::Debug)
    .with_params(true)  // Log query parameters
    .with_slow_threshold(500_000);  // 500ms slow query warning

// Note: a LoggingConfig struct exists internally (with a
// LoggingMiddleware::with_config(..) constructor) but the type is not
// currently re-exported from prax_query::middleware — use the with_*
// methods above.`;

const metricsMiddleware = `use prax_query::middleware::MetricsMiddleware;

// Create an in-memory collector + middleware pair.
// (InMemoryMetricsCollector itself is not re-exported from
// prax_query::middleware — in_memory() is the way in.)
let (metrics, collector) = MetricsMiddleware::in_memory();

// Use the middleware...

// Later, get metrics
let stats = collector.get_metrics();
println!("Total queries: {}", stats.total_queries);
println!("Success rate: {:.2}%", stats.success_rate() * 100.0);
println!("Average time: {}μs", stats.avg_time_us);
println!("Slow queries: {}", stats.slow_queries);
println!("Cache hits: {}", stats.cache_hits);

// Queries by type
for (query_type, count) in &stats.queries_by_type {
    println!("  {}: {}", query_type, count);
}`;

const retryMiddleware = `use prax_query::middleware::{RetryConfig, RetryMiddleware};
use std::time::Duration;

// Basic retry with defaults (3 retries, exponential backoff)
let retry = RetryMiddleware::default_config();

// Custom retry configuration
let retry = RetryMiddleware::new(
    RetryConfig::new()
        .max_retries(5)
        .initial_delay(Duration::from_millis(100))
        .max_delay(Duration::from_secs(10))
        .backoff_multiplier(2.0)
        .jitter(true)  // Add randomness to prevent thundering herd
);

// (RetryConfig::retry_on(..) accepts a RetryPredicate, but
// RetryPredicate / RetryableError are not currently re-exported from
// prax_query::middleware.)`;

const customMiddleware = `use prax_query::middleware::{
    Middleware, QueryContext, QueryResponse, Next,
    MiddlewareResult, BoxFuture
};

struct AuthMiddleware {
    tenant_id: String,
}

impl Middleware for AuthMiddleware {
    fn handle<'a>(
        &'a self,
        mut ctx: QueryContext,
        next: Next<'a>,
    ) -> BoxFuture<'a, MiddlewareResult<QueryResponse>> {
        Box::pin(async move {
            // Add tenant context to all queries
            ctx.metadata_mut().tenant_id = Some(self.tenant_id.clone());

            // Modify SQL to add tenant filter (example)
            if ctx.query_type().is_read() {
                let sql = ctx.sql().to_string();
                // Add tenant filtering logic...
            }

            // Continue to next middleware
            next.run(ctx).await
        })
    }

    fn name(&self) -> &'static str {
        "AuthMiddleware"
    }
}`;

const queryContext = `use prax_query::middleware::{QueryContext, QueryMetadata, QueryType};

// Query context provides information about the current query
fn inspect_context(ctx: &QueryContext) {
    // Get SQL and parameters
    println!("SQL: {}", ctx.sql());
    println!("Params: {:?}", ctx.params());

    // Query type detection
    match ctx.query_type() {
        QueryType::Select => println!("This is a SELECT query"),
        QueryType::Insert => println!("This is an INSERT query"),
        QueryType::Update => println!("This is an UPDATE query"),
        QueryType::Delete => println!("This is a DELETE query"),
        QueryType::Count => println!("This is a COUNT query"),
        _ => println!("Other query type"),
    }

    // Check query characteristics
    if ctx.is_read() {
        println!("Read operation - safe for replicas");
    }
    if ctx.is_write() {
        println!("Write operation - use primary");
    }

    // Timing
    println!("Elapsed: {:?}", ctx.elapsed());
}

// Query metadata for tracing
let metadata = QueryMetadata::new()
    .with_model("User")
    .with_operation("findMany")
    .with_request_id("req-abc123")
    .with_user_id("user-456")
    .with_tenant_id("tenant-789")
    .with_tag("env", "production");`;
---

<DocsLayout title="Middleware System - 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">Middleware System</h1>
      <p class="text-xl text-muted">
        Prax provides a powerful middleware system for intercepting queries before and after execution.
        Use middleware for logging, metrics, authentication, caching, retry logic, and more.
      </p>
    </header>

    <nav class="mb-8 p-4 bg-surface rounded-lg border border-border">
      <h3 class="text-sm font-semibold text-muted mb-2">On this page</h3>
      <ul class="space-y-1 text-sm">
        <li><a href="#overview" class="text-primary-400 hover:text-primary-300">Overview</a></li>
        <li><a href="#logging" class="text-primary-400 hover:text-primary-300">Logging Middleware</a></li>
        <li><a href="#metrics" class="text-primary-400 hover:text-primary-300">Metrics Middleware</a></li>
        <li><a href="#retry" class="text-primary-400 hover:text-primary-300">Retry Middleware</a></li>
        <li><a href="#custom" class="text-primary-400 hover:text-primary-300">Custom Middleware</a></li>
        <li><a href="#context" class="text-primary-400 hover:text-primary-300">Query Context</a></li>
      </ul>
    </nav>

    <div class="space-y-12">

    <section id="overview" class="mb-12">
      <h2 class="text-2xl font-semibold mb-4">Overview</h2>
      <p class="text-muted mb-4">
        The middleware system allows you to intercept every query before and after execution.
        Middleware can modify queries, add context, collect metrics, implement caching, and more.
      </p>

      <CodeBlock code={basicExample} lang="rust" filename="Basic Setup" />

      <div class="mt-6 grid grid-cols-1 md:grid-cols-2 gap-4">
        <div class="p-4 bg-surface rounded-lg">
          <h4 class="font-semibold text-primary-400 mb-2">Built-in Middleware</h4>
          <ul class="text-sm space-y-1 text-muted">
            <li>• <code>LoggingMiddleware</code> - Query logging with levels</li>
            <li>• <code>MetricsMiddleware</code> - Performance metrics</li>
            <li>• <code>TimingMiddleware</code> - Execution time tracking</li>
            <li>• <code>RetryMiddleware</code> - Automatic retry with backoff</li>
            <li>• <code>TenantMiddleware</code> - Row-level tenant SQL filtering</li>
          </ul>
        </div>
        <div class="p-4 bg-surface rounded-lg">
          <h4 class="font-semibold text-primary-400 mb-2">Use Cases</h4>
          <ul class="text-sm space-y-1 text-muted">
            <li>• Query logging and debugging</li>
            <li>• Performance monitoring</li>
            <li>• Multi-tenant data isolation</li>
            <li>• Query caching</li>
            <li>• Circuit breaking</li>
          </ul>
        </div>
      </div>

      <div class="mt-4 p-4 bg-surface rounded-lg border border-border">
        <h4 class="font-semibold text-primary-400 mb-2">TenantMiddleware (v0.11)</h4>
        <p class="text-sm text-muted">
          <code>TenantMiddleware</code> rewrites every query to enforce tenant isolation at the SQL
          level. Tenant values are validated against the declared column type before interpolation,
          an existing <code>WHERE</code> predicate is parenthesized so <code>OR</code> branches cannot
          bypass the filter, fail-closed statement shapes (CTE-wrapped <code>SELECT</code>,
          <code>MERGE</code>, <code>REPLACE</code>, …) are rejected loudly, <code>INSERT</code> gets the
          tenant column auto-injected, and <code>UPDATE</code>/<code>DELETE</code> are validated to
          reference the tenant column. Task-local resolution (<code>with_tenant</code>) takes
          precedence over the shared middleware slot. See the
          <a href="/advanced/multitenancy" class="text-primary-400 hover:text-primary-300">Multi-Tenancy page</a>
          for details.
        </p>
      </div>
    </section>

    <section id="logging" class="mb-12">
      <h2 class="text-2xl font-semibold mb-4">Logging Middleware</h2>
      <p class="text-muted mb-4">
        Log all queries with configurable detail levels. Automatically detect slow queries and log warnings.
      </p>

      <CodeBlock code={loggingMiddleware} lang="rust" filename="Logging Configuration" />

      <div class="mt-6">
        <h4 class="font-semibold mb-3">Log Levels</h4>
        <table class="w-full text-sm">
          <thead>
            <tr class="border-b border-border">
              <th class="text-left py-2 text-muted">Level</th>
              <th class="text-left py-2 text-muted">Description</th>
            </tr>
          </thead>
          <tbody class="text-muted">
            <tr class="border-b border-border/50">
              <td class="py-2"><code>Off</code></td>
              <td>No logging</td>
            </tr>
            <tr class="border-b border-border/50">
              <td class="py-2"><code>Error</code></td>
              <td>Only errors</td>
            </tr>
            <tr class="border-b border-border/50">
              <td class="py-2"><code>Warn</code></td>
              <td>Errors and slow queries</td>
            </tr>
            <tr class="border-b border-border/50">
              <td class="py-2"><code>Info</code></td>
              <td>All queries (default)</td>
            </tr>
            <tr class="border-b border-border/50">
              <td class="py-2"><code>Debug</code></td>
              <td>Queries with parameters</td>
            </tr>
            <tr class="border-b border-border/50">
              <td class="py-2"><code>Trace</code></td>
              <td>Everything including responses</td>
            </tr>
          </tbody>
        </table>
      </div>
    </section>

    <section id="metrics" class="mb-12">
      <h2 class="text-2xl font-semibold mb-4">Metrics Middleware</h2>
      <p class="text-muted mb-4">
        Collect query performance metrics for monitoring and optimization.
      </p>

      <CodeBlock code={metricsMiddleware} lang="rust" filename="Metrics Collection" />

      <div class="mt-6">
        <h4 class="font-semibold mb-3">Available Metrics</h4>
        <div class="grid grid-cols-2 md:grid-cols-3 gap-3 text-sm">
          <div class="p-3 bg-surface rounded">
            <code class="text-primary-400">total_queries</code>
            <p class="text-muted text-xs mt-1">Total query count</p>
          </div>
          <div class="p-3 bg-surface rounded">
            <code class="text-primary-400">successful_queries</code>
            <p class="text-muted text-xs mt-1">Successful count</p>
          </div>
          <div class="p-3 bg-surface rounded">
            <code class="text-primary-400">failed_queries</code>
            <p class="text-muted text-xs mt-1">Failed count</p>
          </div>
          <div class="p-3 bg-surface rounded">
            <code class="text-primary-400">avg_time_us</code>
            <p class="text-muted text-xs mt-1">Average time (μs)</p>
          </div>
          <div class="p-3 bg-surface rounded">
            <code class="text-primary-400">slow_queries</code>
            <p class="text-muted text-xs mt-1">Slow query count</p>
          </div>
          <div class="p-3 bg-surface rounded">
            <code class="text-primary-400">cache_hits</code>
            <p class="text-muted text-xs mt-1">Cache hit count</p>
          </div>
        </div>
      </div>
    </section>

    <section id="retry" class="mb-12">
      <h2 class="text-2xl font-semibold mb-4">Retry Middleware</h2>
      <p class="text-muted mb-4">
        Automatically retry failed queries with exponential backoff. Perfect for handling transient errors.
      </p>

      <CodeBlock code={retryMiddleware} lang="rust" filename="Retry Configuration" />
    </section>

    <section id="custom" class="mb-12">
      <h2 class="text-2xl font-semibold mb-4">Custom Middleware</h2>
      <p class="text-muted mb-4">
        Create your own middleware by implementing the <code>Middleware</code> trait.
      </p>

      <CodeBlock code={customMiddleware} lang="rust" filename="Custom Middleware Example" />
    </section>

    <section id="context">
      <h2 class="text-2xl font-semibold mb-4">Query Context</h2>
      <p class="text-muted mb-4">
        The <code class="px-2 py-1 bg-surface-elevated rounded">QueryContext</code> provides information about the current query and allows modification.
      </p>

      <CodeBlock code={queryContext} lang="rust" filename="Query Context" />
    </section>
    </div>
  </article>
</DocsLayout>