asobi_ops_page (asobi v0.72.6)
View SourceThe one list envelope every ops read endpoint returns.
#{data => [...], page => #{limit => 50, offset => 0, total => 137}}total is the point of the envelope: a console that cannot say how many rows
exist cannot render a pager, and every list endpoint that predates this module
made the caller guess. kura_paginator:paginate/3 runs the count and the page
as one unit, so the two can never describe different filters.
Project is applied to every row on the way out. It is the endpoint's field
allowlist, not a formatter - see the projections in asobi_ops_players and
asobi_ops_matches.
list/3 pages a database query; slice/4 pages rows already in hand, for
the reads the database cannot page - a queue held in a process, and a board
list whose one grouped aggregate has to run whole either way. Both produce
the identical envelope, so a console cannot tell them apart.
Summary
Functions
-spec list(#kura_query{from :: atom() | module() | undefined, select :: [atom() | term()] | {exprs, [term()]}, wheres :: [term()], joins :: [term()], order_bys :: [term()], group_bys :: [atom()], havings :: [term()], limit :: non_neg_integer() | undefined, offset :: non_neg_integer() | undefined, distinct :: boolean() | [atom()], lock :: binary() | undefined, prefix :: binary() | undefined, preloads :: [atom() | {atom(), list()}], ctes :: [{binary(), #kura_query{}}], combinations :: [{union | union_all | intersect | except, #kura_query{}}], include_deleted :: boolean()}, asobi_ops_params:page_spec(), fun((map()) -> map())) -> {ok, map()} | {error, term()}.
Run Query as one page and wrap it in the envelope.
Query must already carry a deterministic order; asobi_ops_params:sort/3
guarantees one. Without it the offset window is not stable between requests.
-spec slice([map()], asobi_ops_params:sort_spec(), asobi_ops_params:page_spec(), fun((map()) -> map())) -> map().
Wrap an in-memory row list in the same envelope, sorting it first.
Orders comes from asobi_ops_params:sort/4 exactly as it would for a
query, so it ends on a unique key and the window is stable between requests.
Rows sort by Erlang term order rather than a database collation - these row
sets are counts, timestamps and identifiers, never prose - and a missing key
sorts as undefined, so a row is never dropped for lacking one.
This never fails: the data is already read. Both sources it serves are
bounded (a queue by max_queue, a board set by its enumeration cap), so
sorting the whole list to return one page of it is not the cost that matters.