Prisma Next is in early access.Read the docs

Built-in: budgets

The budgets middleware caps row counts and makes over-latency queries visible.

The budgets middleware puts a cost ceiling on every query: it rejects or warns on queries that would read more rows than you allow, counts rows while they stream, and reports queries that run longer than your latency budget.

Register it on the runtime with the row and latency limits you want to enforce:

src/prisma/db.ts
import postgres from '@prisma-next/postgres/runtime';
import { budgets } from '@prisma-next/sql-runtime';
import type { Contract } from './contract.d';
import contractJson from './contract.json' with { type: 'json' };

export const db = postgres<Contract>({
  contractJson,
  url: process.env['DATABASE_URL']!,
  middleware: [
    budgets({
      maxRows: 10_000,
      defaultTableRows: 10_000,
      tableRows: { user: 10_000, post: 10_000 },
      maxLatencyMs: 1_000,
    }),
  ],
});

budgets is SQL-family middleware (it declares familyId: 'sql'), so it registers on PostgreSQL runtimes. It ships with @prisma-next/sql-runtime, which comes in through @prisma-next/postgres; you do not install anything extra.

Options

All options are optional.

OptionTypeDefaultWhat it controls
maxRowsnumber10_000The most rows a single query may produce, checked against both the pre-execution estimate and the observed row stream
defaultTableRowsnumber10_000Assumed row count for tables not listed in tableRows, used by the estimator
tableRowsRecord<string, number>{}Per-table row-count assumptions for the estimator, keyed by table name
maxLatencyMsnumber1_000Latency budget for one query, checked after execution
severities.rowCount'warn' | 'error''error'Whether pre-execution row-budget violations block the query or log a warning in permissive mode. Strict mode always blocks
severities.latency'warn' | 'error'(none)Accepted by the options type but not read by the middleware in v0.14; latency behavior follows the runtime mode (see below)

The runtime defaults to strict mode. In strict mode, over-budget latency throws BUDGET.TIME_EXCEEDED; in permissive mode, the same event is logged as a warning. The latency check happens after execution, so it is a visibility tool rather than a cancellation mechanism. Setting severities.latency does not change this in v0.14: the key exists on the options type, but the middleware does not consult it yet.

How it enforces the budget

The middleware checks at three points in the query lifecycle:

  1. Before execution, it inspects the query plan's AST. A SELECT with no LIMIT (and no aggregation that collapses the result) is treated as unbounded, and a bounded query whose estimated rows exceed maxRows is over budget. The estimate comes from tableRows and defaultTableRows, so tune those to your data. Violations raise BUDGET.ROWS_EXCEEDED before the driver runs, or warn in permissive mode when severities.rowCount is set to 'warn'.
  2. While rows stream, it counts what the database actually returns. If the observed count passes maxRows, it throws BUDGET.ROWS_EXCEEDED and aborts the stream, which protects you when the estimate was wrong.
  3. After execution, it compares the measured latencyMs against maxLatencyMs and reports BUDGET.TIME_EXCEEDED when the query ran too long. This check runs after the work is done, so it cannot save the slow query itself; it exists to make slow queries loud instead of invisible.

What you see when a budget trips

A blocked query rejects with a runtime error carrying the budget code and details:

Row budget error
BUDGET.ROWS_EXCEEDED: Unbounded SELECT query exceeds budget
  { source: 'ast', maxRows: 10000 }

The source field tells you whether the violation came from the pre-execution estimate ('ast') or the observed row stream ('observed'). In strict mode, latency violations carry the measured and allowed values:

Latency budget error
BUDGET.TIME_EXCEEDED: Query latency exceeds budget
  { latencyMs: 2481, maxLatencyMs: 1000 }

In permissive mode, the same information is emitted through the runtime logger as a warning. To fix an unbounded-select violation, add a limit to the query (take(...) on the ORM API, .limit(...) on the SQL query builder) or raise the budget deliberately.

Common gotchas

See also

On this page