Pagination
Spring Data’s Page is great inside a service but awkward on the wire: it’s an interface,
its JSON shape shifted between versions and clients would need Spring types to deserialize
it. PageableResult<T> (in commons-rest-api) is the wire-format replacement — a small,
serializable DTO with a stable shape.
The JSON shape
Section titled “The JSON shape”{ "content": [ { "id": "5dc3...", "name": "rocketbase" } ], "page": 0, "pageSize": 25, "totalElements": 100, "totalPages": 4}page is 0-indexed. The shape never changes, no matter which Spring Data version the server
runs — clients (including the generated TypeScript clients) can rely
on it.
Creating results
Section titled “Creating results”// from a Spring Data page, converting each entityPageableResult<CompanyRead> result = PageableResult.fromPage(page, converter::fromEntity);
// content already converted, page for the numbersPageableResult<CompanyRead> result = PageableResult.contentPage(converter.fromEntities(page.getContent()), page);
// fixed lists & empty resultsPageableResult<String> single = PageableResult.of(List.of("a", "b"));PageableResult<String> none = PageableResult.empty();Working with results
Section titled “Working with results”result.hasNextPage();result.hasPreviousPage();result.map(read -> new Summary(read)); // convert content, keep paging numbersresult.toPage(); // back to a Spring Data PageImplfor (CompanyRead read : result) { ... } // IterableA paged endpoint
Section titled “A paged endpoint”Use Spring’s Pageable for the input side — commons-rest only standardizes the output:
@GetMapping(path = "/company", produces = APPLICATION_JSON_VALUE)PageableResult<CompanyRead> find( @ParameterObject @SortDefault(sort = "id", direction = Sort.Direction.ASC) Pageable pageable);@ParameterObject (springdoc) flattens the Pageable into documented page / size /
sort query parameters in your OpenAPI doc; @SortDefault gives deterministic ordering.
Attaching metadata
Section titled “Attaching metadata”PageableResultWithMeta<T, M> adds a typed meta member — handy for aggregations, summaries
or GeoJSON bounds that belong to the result set as a whole:
PageableResult<InvoiceRead> page = PageableResult.fromPage(invoices, converter::fromEntity);PageableResultWithMeta<InvoiceRead, InvoiceStats> result = PageableResultWithMeta.withMeta(page, new InvoiceStats(openAmount, overdueCount));{ "content": [ ... ], "page": 0, "pageSize": 25, "totalElements": 13, "totalPages": 1, "meta": { "openAmount": 4211.50, "overdueCount": 3 }}On the TypeScript side
Section titled “On the TypeScript side”The @rocketbase/commons-rest-client runtime
ships matching types plus helpers for infinite queries:
import { PageableResult, infiniteItems, infiniteTotalElements } from '@rocketbase/commons-rest-client';
const page: PageableResult<CompanyRead> = await api.find({ page: 0, pageSize: 25 });