Limits
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];
}
| Member | Default | Effect |
|---|---|---|
limits() | [10, 25, 50] | The values of search.limit. Another value gives The selected search.limit is invalid. |
$defaultLimit | 50 | The page size of a search without limit |
$includeLimit | 10 | The rows of each parent row in an include of this resource without limit |
$maxTotal | null | The 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 leavestotal_cappedout.$maxTotalmust be at least the largest value oflimits(), or the first read of the limits throwsInvalidResourceException.- 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$defaultLimitof the resource; - for each to-many include, its
limit, or the$includeLimitof its resource, for each parent row. Nested includes multiply; - a to-one include counts one row for each parent, and a
MorphToinclude 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:
{
"search": {
"includes": [
{"relation": "tickets", "includes": [{"relation": "tags"}, {"relation": "labels"}]}
]
}
}
{
"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
limitof an include, or itsrelationwhen the include takes the default limit, orsearch.limit, orsearchwhen the page takes$defaultLimit. - The
responseblock of a mutation counts one record for each entry ofmutate, with its includes. Its error goes underresponse, such asresponse.includes.0.relation. Nothing is written. - The pages of a text search on a search engine count the same way, and so do
page()andrender()ofRest::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
| Key | Default | Over the limit |
|---|---|---|
depths.filters | 1 | nested inside a nested group gives a 422 on its own nested key |
depths.includes | 3 | The search.includes.0.includes.0.includes.0.includes field is prohibited. |
depths.mutations | 3 | The 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.tagstakes three. A deeper one givesThe search.includes.0.relation field goes deeper than the limit of 3 include levels. - A dotted filter field goes through at most
depths.includesrelations, apivotsegment 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
| Key | Default | Over the limit |
|---|---|---|
totals.filters | 500 | The search.includes.0.filters field goes over the limit of 500 filters in one search. |
totals.entries | 1000 | The search.selects field goes over the limit of 1000 list entries in one request. |
totals.relations | 20 | The search.filters field goes over the limit of 20 relation filters in one search. |
totals.patterns | 10 | The 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.
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.Perimeter
perimeterQuery() sets the rows that exist for a client, on every endpoint and at every level of a request. Learn where it applies and how to write it.
Search overview
The search endpoint reads the rows of a resource with filters, sorts, includes, aggregates and pagination, inside the perimeter of the user.