querypipe
Core concepts

Guards & safety

Mandatory whitelist, guard limits, and the no-RegExp rule — backend-safe by default.

Everything crossing the parse/validate boundary is checked against the schema and a set of guard limits before your database ever sees it. There is no schema-less parse and no opt-out.

The whitelist

Only declared fields exist. Anything else is UNKNOWN_FIELD:

import { createQuery, numberField } from "querypipe";

const q = createQuery({
  fields: {
    id: numberField(),
    price: numberField(),
    costPrice: numberField({ filterable: false, sortable: false }), // unreachable from the wire
  },
  stableBy: "id",
});

q.parse("sort=margin:desc");    // → UNKNOWN_FIELD
q.parse("sort=costPrice:desc"); // → NOT_SORTABLE (declared, but opted out)

{ sortable: false } and { filterable: false } narrow the types too — q.sort("costPrice") is a compile error, and the same rule is enforced at runtime for wire input.

Guard limits

GuardDefaultBreach error
maxSortColumns3MAX_SORT_EXCEEDED
maxFilters (counted as leaves)10MAX_FILTER_EXCEEDED
maxValueLength (UTF-16 length of a decoded value)256VALUE_TOO_LONG
maxInItems (items of an in/notIn list)100VALUE_TOO_LONG

Override per schema:

const limited = createQuery({
  fields: { id: numberField(), price: numberField() },
  stableBy: "id",
  guards: { maxSortColumns: 2, maxFilters: 5, maxValueLength: 64, maxInItems: 20 },
});

The injected stableBy tie-breaker does not count against maxSortColumns — it is added at execution time, never into the spec.

The no-RegExp rule

contains / startsWith / endsWith are always literal substring/prefix/suffix matches. User input is never compiled into a RegExp or any pattern language, so there is nothing to escape and no pattern-injection surface. (The v0.2 SQL and Mongo adapters carry the same obligation: LIKE metacharacters and $regex metacharacters are escaped by contract.)

Try the guard behavior live in the guards demo — a slider pushes the sort-column count past the limit and the parse flips to MAX_SORT_EXCEEDED.

On this page