Lint rules
Catch unbounded reads and unscoped deletes in the editor, with ESLint or oxlint.
better-supabase/lint is a lint plugin written against the ESLint rule API.
It has no dependencies and loads in ESLint's flat config and in oxlint's JS
plugins.
ESLint
import betterSupabase from "better-supabase/lint";
export default [betterSupabase.configs.recommended];oxlint
{
"jsPlugins": ["better-supabase/lint"],
"rules": {
"better-supabase/no-delete-many-without-where": "error",
"better-supabase/no-unbounded-find-many": "warn",
"better-supabase/max-limit": ["warn", { "max": 500 }],
"better-supabase/require-order-by": "warn",
"better-supabase/unbounded-read": ["warn", { "tables": ["public.events"] }]
}
}Rules
no-unbounded-find-many
findMany() or findMany({ ... }) without limit. Recommended: warn.
no-delete-many-without-where
deleteMany() without where. The repository refuses this at runtime too;
the rule catches it before it ships. Recommended: error.
max-limit
A literal limit above max (default 1000) in findMany, findFirst or
paginate. Recommended: warn.
require-order-by
findMany with offset but no orderBy, which returns pages in no stable
order. Recommended: warn.
require-max-affected
updateMany or deleteMany without
maxAffected, or with a
literal maxAffected above max (default 1000). Not in recommended; turn
it on where a bulk write that matches too many rows would hurt:
export default [
betterSupabase.configs.recommended,
{
rules: { "better-supabase/require-max-affected": ["error", { max: 500 }] },
},
];unbounded-read
db.<table>.findMany without limit on a large table. PostgREST cuts such a
read off at db-max-rows without an error (see
row caps). Recommended: warn, but
it only reports once it knows which tables are large:
tables: the large tables.largeTables(snapshot)reads them fromsupabase/snapshot.json, wheregenmarks every table Postgres estimates at 10,000 rows or more. Re-rungenagainst a database with realistic data (or production) to update the marks.strict: true: report on every table, likeno-unbounded-find-many.
import betterSupabase, { largeTables } from "better-supabase/lint";
import snapshot from "./supabase/snapshot.json" with { type: "json" };
export default [
betterSupabase.configs.recommended,
{
rules: {
"better-supabase/unbounded-read": [
"warn",
{ tables: largeTables(snapshot) },
],
},
},
];Table names match regardless of casing: order_items, orderItems and
public.order_items are the same table.
How calls are matched
Rules match calls by method name with an inline object literal. When the
argument is a variable or contains a spread, the rule skips it instead of
guessing, so there are no false positives from code it can't see. The
runtime rules() plugin checks what the linter
can't.
Last updated on