Resources

Limits

Page sizes, include limits, capped totals, and the limits of rest.limits on lists, depths, totals, values, response rows, action jobs and metric groups.

A resource bounds the work of each request in two ways: its page sizes, declared on the resource, and the request limits of config/rest.php, shared by every resource.

Page sizes

limits() returns the values that search.limit accepts. $defaultLimit sizes a page sent without limit:

public int $defaultLimit = 25;

public function limits(): array
{
    return [10, 25, 50, 100];
}
MemberDefaultEffect
limits()[10, 25, 50]The values of search.limit. Another value gives The selected search.limit is invalid.
$defaultLimit50The page size of a search without limit
$includeLimit10The rows of each parent row in an include of this resource without limit
$maxTotalnullThe most rows that the count of a search reads, see Capped totals

$defaultLimit and $includeLimit must be values of limits(). Otherwise the first read of the limits in a request throws Lomkit\Rest\Exceptions\InvalidResourceException:

The resource [App\Rest\Resources\PostResource] sets $defaultLimit to 32, which is not one of its limits() [10, 25, 50]. Return 32 from limits(), or set $defaultLimit to one of them.

A resource that returns other values from limits() keeps 50 and 10 in the list, or sets its $defaultLimit and $includeLimit to values of the list.

The page number

search.page is an integer of at least 1. It is also bounded so that the offset of the page stays an integer: at most intdiv(PHP_INT_MAX, L) + 1, L being the largest value of limits(). With the default limits():

The search.page field must not be greater than 184467440737095517.

Include limits

Each to-many include returns at most a set number of rows for each parent row: its limit, or the $includeLimit of the related resource. The rows kept are the first ones in the order of the include: its sorts, or the defaultOrderBy() of its resource.

{"search": {"includes": [{"relation": "tickets", "limit": 25}]}}

A row whose level includes a to-many relation lists under @more the includes that have more rows than they returned:

{"id": 1, "name": "Billing", "tickets": [{"id": 12, "title": "Invoice missing"}], "@more": ["tickets"]}

BelongsTo and MorphTo includes load one row per parent: they take no limit. See Includes and aggregates.

Capped totals

A resource over a large table caps the count of its searches with $maxTotal:

public ?int $maxTotal = 10000;

The count reads at most 10,001 rows. When more rows match, a page answers "total": 10000 and "total_capped": true, and last_page follows that total. Otherwise it answers the exact total and "total_capped": false.

For a resource with limits() of [5, 10], a $defaultLimit of 10, $maxTotal set to 20 and 35 matching rows, the first page of a search with "limit": 10 answers, besides its data:

{
  "current_page": 1,
  "from": 1,
  "last_page": 2,
  "per_page": 10,
  "to": 10,
  "total": 20,
  "total_capped": true,
  "meta": {}
}
  • null, the default, counts every row and leaves total_capped out.
  • $maxTotal must be at least the largest value of limits(), or the first read of the limits throws InvalidResourceException.
  • A page past the capped total still answers its rows.
  • A text search on a search engine keeps the total of the engine.

The default pagination also skips the count when the page itself gives the total: when the first page is not full, the total is the rows of the page.

Actions

Every action is capped by its $maxResources property, 1,000 rows by default. An action whose search matches more rows answers a 422 before it runs on any row. See Actions.

Request limits

The limits block of config/rest.php bounds the lists, the depths, the totals and the values of every request, at every level, and the rows, jobs and groups that one request produces. Every key, with its default, is listed in Configuration.

Sizes

Each list accepts at most limits.sizes.<list> entries. A list over its limit gets one 422 on its own key, and the package does not validate its entries:

{
  "message": "The search.filters field must not have more than 100 items.",
  "errors": {
    "search.filters": ["The search.filters field must not have more than 100 items."]
  }
}

destroy, restore and forceDestroy accept at most limits.sizes.resources keys: The resources field must not have more than 1000 items.

Each key that a delete entry of a deletable() relation sends counts as one entry of limits.sizes.relation_entries: The mutate.0.relations.lines field goes over the limit of 100 entries, each deleted key counting as one entry.

Response rows

sizes.rows bounds the rows that one search page renders, included rows counted, 10,000 by default. The package counts them from the limits that the request asks for, before any query:

  • the page limit: search.limit, or the $defaultLimit of the resource;
  • for each to-many include, its limit, or the $includeLimit of its resource, for each parent row. Nested includes multiply;
  • a to-one include counts one row for each parent, and a MorphTo include counts the type with the most rows.

A team search with the default limits, 50 teams, that includes 10 tickets per team and the tags and labels of each ticket can render 50 + 500 + 5,000 + 5,000 rows:

POST /api/teams/search
{
  "search": {
    "includes": [
      {"relation": "tickets", "includes": [{"relation": "tags"}, {"relation": "labels"}]}
    ]
  }
}
422 Unprocessable Content
{
  "message": "The search.includes.0.includes.1.relation field takes the response over the limit of 10000 rows: it can render up to 10550 rows.",
  "errors": {
    "search.includes.0.includes.1.relation": ["The search.includes.0.includes.1.relation field takes the response over the limit of 10000 rows: it can render up to 10550 rows."]
  }
}
  • The error goes on the first key, in the order of the payload, that takes the count over the limit: the limit of an include, or its relation when the include takes the default limit, or search.limit, or search when the page takes $defaultLimit.
  • The response block of a mutation counts one record for each entry of mutate, with its includes. Its error goes under response, such as response.includes.0.relation. Nothing is written.
  • The pages of a text search on a search engine count the same way, and so do page() and render() of Rest::search().
  • The search of an action, the search of a metric and Rest::search()->lazy() do not count rows.
  • Ask for smaller limits, or raise sizes.rows, when a screen needs more rows at once.

Queued action jobs

sizes.jobs bounds the jobs that one run of a queued or batched action creates, 100 by default. When the rows need more jobs, each job takes the number of rows divided by sizes.jobs, rounded up, instead of the $chunkCount of the action: 250 rows with a $chunkCount of 2 give 84 jobs of 3 rows, not 125 jobs. Raise sizes.jobs when each job must keep $chunkCount rows. See Queued actions.

Metric groups

sizes.groups bounds a response rather than a request: a partition metric keeps its 100 largest parts, and a split trend its 100 largest splits, by default. The other groups fold into other. A metric that sets its own limit() keeps that limit instead: set limit() on a metric that needs more groups. See Metrics.

Depths

KeyDefaultOver the limit
depths.filters1nested inside a nested group gives a 422 on its own nested key
depths.includes3The search.includes.0.includes.0.includes.0.includes field is prohibited.
depths.mutations3The mutate.0.relations.products.0.relations.category.relations.products.0.relations field is prohibited.
  • Each relation of a dotted include counts as one level: team.tickets.tags takes three. A deeper one gives The search.includes.0.relation field goes deeper than the limit of 3 include levels.
  • A dotted filter field goes through at most depths.includes relations, a pivot segment aside: The search.filters.0.field field goes through more relations than the limit of 3.
  • An aggregate path follows the same limit: The search.aggregates.0.relation field goes deeper than the limit of 3 include levels.

Totals

KeyDefaultOver the limit
totals.filters500The search.includes.0.filters field goes over the limit of 500 filters in one search.
totals.entries1000The search.selects field goes over the limit of 1000 list entries in one request.
totals.relations20The search.filters field goes over the limit of 20 relation filters in one search.
totals.patterns10The search.filters field goes over the limit of 10 pattern filters in one search.

The error goes on the first list that does not fit. A later list that does not fit is refused without a second message. A list over both totals.relations and totals.patterns gets the relation message. A pattern filter is a like, not like, ilike or not ilike filter, and each relation filter runs a subquery: raise totals.relations or totals.patterns if your clients send more.

Lengths

lengths.values bounds each string value of a filter, and each string of a list, 1,000 characters by default, wherever a search takes filters: in a search, its includes and aggregates, the search of an action and the search of a metric. The error of a list item goes under its position, such as search.filters.0.value.1:

The search.filters.0.value field must not be greater than 1000 characters.

lengths.patterns bounds the value of a like, not like, ilike or not ilike filter, 255 characters by default. A pattern takes the smaller of lengths.patterns and lengths.values:

The search.filters.0.value field must not be greater than 255 characters.

What a refused list skips

The package does not check what a refused list holds: the viewAny check of a relation it names and the create check of a mutate entry in it do not run, and the request gets the 422 of the refused key. The checks of the other parts of the payload still run first, and a denied one still answers 403.

The package renders the size errors itself: a custom message keyed by the max rule of these lists does not apply, but Laravel's validation.max.array line does. The texts of the totals and of the depths are lines of the rest::validation file, which you can publish and edit. See Translations.