asobi_ops_page (asobi v0.72.6)

View Source

The 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

Run Query as one page and wrap it in the envelope.

Wrap an in-memory row list in the same envelope, sorting it first.

Functions

list/3

-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.

slice/4

-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.