You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Each method returns a `Window` containing the page content, a `hasNext` flag, and navigation tokens (`nextScrollable()`, `previousScrollable()`) for sequential traversal. For REST APIs, `Window` also provides `nextCursor()` and `previousCursor()` to serialize the scroll position as an opaque string, and `Scrollable.fromCursor(key, cursor)` to reconstruct a `Scrollable` from a cursor string. See [Repositories: Scrolling](repositories.md#scrolling) for the full API, including sort overloads, filtering, and Ref variants.
485
+
Each method returns a `Window` containing the page content and navigation cursors for sequential traversal. The `hasNext()` and `hasPrevious()` flags reflect whether additional rows existed at query time, but they are not prerequisites for calling `nextScrollable()` or `previousScrollable()`. Both methods return a non-null `Scrollable` whenever the window contains at least one element, and return `null` only when the window is empty. This means you can always follow the cursor if you choose to; for example, new rows may have been inserted after the original query. For REST APIs, `Window` also provides `nextCursor()` and `previousCursor()` to serialize the scroll position as an opaque string, and `Scrollable.fromCursor(key, cursor)` to reconstruct a `Scrollable` from a cursor string. See [Repositories: Scrolling](repositories.md#scrolling) for the full API, including sort overloads, filtering, and Ref variants.
Copy file name to clipboardExpand all lines: docs/glossary.md
+2-2Lines changed: 2 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -41,7 +41,7 @@ A lightweight identifier (`Ref<T>`) that carries only the record type and primar
41
41
An interface that provides database access methods for an entity or projection type. `EntityRepository<E, ID>` offers built-in CRUD operations; `ProjectionRepository<P, ID>` offers read-only operations. Custom repositories extend these interfaces with domain-specific query methods. See [Repositories](repositories.md).
42
42
43
43
**Scrollable**
44
-
A scroll request that captures cursor state for fetching a window of results. The scrolling counterpart of `Pageable`. Created via `Scrollable.of(key, size)` or obtained from `Window.nextScrollable()` / `Window.previousScrollable()`. Supports cursor serialization for REST APIs via `toCursor()` / `Scrollable.fromCursor(key, cursor)`. See [Pagination and Scrolling: Scrolling](pagination-and-scrolling.md#scrolling).
44
+
A scroll request that captures cursor state for fetching a window of results. The scrolling counterpart of `Pageable`. Created via `Scrollable.of(key, size)` or obtained from `Window.nextScrollable()` / `Window.previousScrollable()`, which are always non-null when the window has content. Supports cursor serialization for REST APIs via `toCursor()` / `Scrollable.fromCursor(key, cursor)`. See [Pagination and Scrolling: Scrolling](pagination-and-scrolling.md#scrolling).
45
45
46
46
**SQL Template**
47
47
Storm's template engine that uses string interpolation to embed entity types, metamodel fields, and parameter values into SQL text. Types expand to column lists, metamodel fields to column names, and values to parameterized placeholders. SQL Templates are the foundation of all Storm queries, including those generated by repositories. See [SQL Templates](sql-templates.md).
@@ -53,4 +53,4 @@ See [Metamodel](#metamodel) above.
53
53
A configuration object (`StormConfig`) that controls runtime behavior for features like dirty checking mode, entity cache retention, and template cache size. All settings have sensible defaults, so configuration is optional. See [Configuration](configuration.md).
54
54
55
55
**Window**
56
-
A window of query results from a scrolling operation. A `Window<R>` contains the result list (`content`), `hasNext` and `hasPrevious` flags, and navigation tokens (`nextScrollable()`, `previousScrollable()`) for sequential traversal. Also provides `nextCursor()` / `previousCursor()` for REST API cursor strings. See [Pagination and Scrolling: Scrolling](pagination-and-scrolling.md#scrolling).
56
+
A window of query results from a scrolling operation. A `Window<R>` contains the result list (`content`), informational `hasNext` and `hasPrevious` flags (a snapshot at query time), and navigation tokens (`nextScrollable()`, `previousScrollable()`) for sequential traversal. The navigation tokens are always non-null when the window has content; `hasNext` and `hasPrevious` are not prerequisites for accessing them, since new data may appear after the query. Also provides `nextCursor()` / `previousCursor()` for REST API cursor strings. See [Pagination and Scrolling: Scrolling](pagination-and-scrolling.md#scrolling).
Copy file name to clipboardExpand all lines: docs/pagination-and-scrolling.md
+37-35Lines changed: 37 additions & 35 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -159,7 +159,7 @@ For the full `Page` and `Pageable` API reference, see [Repositories: Offset-Base
159
159
160
160
## Scrolling
161
161
162
-
Scrolling navigates sequentially using a cursor and returns a `Window<T>`. A `Window` represents a portion of the result set: it contains the data, a flag indicating whether more results exist, and `Scrollable<T>` navigation tokens for sequential traversal, but no total count or page number.
162
+
Scrolling navigates sequentially using a cursor and returns a `Window<T>`. A `Window` represents a portion of the result set: it contains the data, informational flags (`hasNext`, `hasPrevious`) that indicate whether adjacent results existed at query time, and `Scrollable<T>` navigation tokens for sequential traversal, but no total count or page number. The navigation tokens `nextScrollable()` and `previousScrollable()` are always available when the window has content, regardless of whether `hasNext` or `hasPrevious` is `true`. This allows the developer to decide whether to follow a cursor, since new data may appear after the query was executed.
163
163
164
164
Under the hood, scrolling uses keyset pagination: it remembers the last value seen on the current page and asks the database for rows after (or before) that value. This avoids the performance cliff of `OFFSET` on large tables, because the database can seek directly to the cursor position using an index.
165
165
@@ -172,10 +172,10 @@ The `scroll` method is available directly on repositories and on the query build
172
172
| Field / Method | Description |
173
173
|-------|-------------|
174
174
|`content()`| The list of results for this window. |
175
-
|`hasNext()`|`true` if more results exist beyond this window in the scroll direction. |
176
-
|`hasPrevious()`|`true` if a previous window exists before this one. |
177
-
|`nextScrollable()`| Returns a `Scrollable<T>` for the next window, or `null` if there is no next window. |
178
-
|`previousScrollable()`| Returns a `Scrollable<T>` for the previous window, or `null` if this is the first window. |
175
+
|`hasNext()`|`true` if more results existed beyond this window at query time. |
176
+
|`hasPrevious()`|`true` if this window was fetched with a cursor position (i.e., not the first page). |
177
+
|`nextScrollable()`| Returns a `Scrollable<T>` for the next window, or `null` if the window is empty. |
178
+
|`previousScrollable()`| Returns a `Scrollable<T>` for the previous window, or `null` if the window is empty. |
179
179
180
180
Create a `Scrollable` using the factory methods, or obtain one from a `Window`:
181
181
@@ -192,7 +192,7 @@ The extra row (`size+1`) is used internally to determine the value of `hasNext`,
192
192
193
193
**No total count.** Unlike pagination, scrolling does not include a total element count. A separate `COUNT(*)` query must execute the same joins, filters, and conditions as the main query, which can be expensive on large or complex result sets. Total counts are also inherently unstable: rows may be inserted or deleted while a user navigates through pages, so the count can become stale between requests. Scrolling is designed for sequential "load more" or infinite-scroll patterns where a total is rarely needed. If you do need a total count (for example, for a UI label like "showing 10 of 4,827 results"), call the `count` (Kotlin) or `getCount()` (Java) method on the query builder separately, keeping in mind that the value is a snapshot that may drift as the underlying data changes.
194
194
195
-
**REST cursor support.** For REST APIs that need to pass scroll state as an opaque string (for example, as a query parameter), `Window` provides `nextCursor()` and `previousCursor()` methods that serialize the scroll position to a cursor string. To reconstruct a `Scrollable` from a cursor string, use `Scrollable.fromCursor(key, cursor)`. For details on supported cursor types, security considerations, and custom codec registration, see [Cursor Serialization](cursors.md).
195
+
**REST cursor support.** For REST APIs that need to pass scroll state as an opaque string (for example, as a query parameter), `Window` provides `nextCursor()` and `previousCursor()` methods that serialize the scroll position to a cursor string. These convenience methods are gated by the informational flags: `nextCursor()` returns `null` when `hasNext()` is `false`, and `previousCursor()` returns `null` when `hasPrevious()` is `false`. This makes them safe to use directly in REST responses without additional checks. The underlying `nextScrollable()` and `previousScrollable()` methods remain available whenever the window has content, so server-side code can still follow a cursor even when the flags indicate no more results were seen at query time. To reconstruct a `Scrollable` from a cursor string, use `Scrollable.fromCursor(key, cursor)`. For details on supported cursor types, security considerations, and custom codec registration, see [Cursor Serialization](cursors.md).
196
196
197
197
<TabsgroupId="language">
198
198
<TabItemvalue="kotlin"label="Kotlin"default>
@@ -297,21 +297,19 @@ The single-key `Scrollable.of(key, size)` uses the cursor column as both the sor
// Next page (cursor values are captured in the Scrollable automatically)
326
-
if (window.hasNext()) {
323
+
// Next page (cursor values are captured in the Scrollable automatically).
324
+
// nextScrollable() is non-null whenever the window has content.
325
+
// You can check hasNext() if you only want to proceed when more results
326
+
// were known to exist at query time, or follow the cursor unconditionally
327
+
// to pick up data that may have arrived after the query.
328
+
var nextScrollable = window.nextScrollable();
329
+
if (nextScrollable !=null) {
327
330
var next = postRepository.select()
328
-
.scroll(window.nextScrollable());
331
+
.scroll(nextScrollable);
329
332
}
330
333
331
334
// Previous page
332
-
if (window.hasPrevious()) {
335
+
var previousScrollable = window.previousScrollable();
336
+
if (previousScrollable !=null) {
333
337
var prev = postRepository.select()
334
-
.scroll(window.previousScrollable());
338
+
.scroll(previousScrollable);
335
339
}
336
340
```
337
341
@@ -391,7 +395,7 @@ See [Manual Key Wrapping](metamodel.md#manual-key-wrapping) for more details.
391
395
392
396
When calling `scroll` on the query builder directly (rather than through a repository), the return type is `MappedWindow<R, T>` where `R` is the result type and `T` is the entity type from the FROM clause. For entity queries where `R` and `T` are the same type, `MappedWindow` carries `Scrollable<T>` navigation tokens and works the same as `Window<T>`. Repository convenience methods return `Window<T>` directly.
393
397
394
-
For queries where the result type differs from the entity type (for example, selecting into a data class that combines columns from multiple sources), `MappedWindow` does not carry navigation tokens because Storm cannot extract cursor values from a result type it does not know how to navigate. In this case, `nextScrollable()` and `previousScrollable()` return `null`, but`hasNext()` still works correctly. To continue scrolling, construct the next `Scrollable` manually using cursor values from your result:
398
+
For queries where the result type differs from the entity type (for example, selecting into a data class that combines columns from multiple sources), `MappedWindow` does not carry navigation tokens because Storm cannot extract cursor values from a result type it does not know how to navigate. In this case, `nextScrollable()` and `previousScrollable()` return `null` (even when the window has content), and`hasNext()` still works correctly as an informational flag. To continue scrolling, check `hasNext()` and construct the next `Scrollable` manually using cursor values from your result:
395
399
396
400
<TabsgroupId="language">
397
401
<TabItemvalue="kotlin"label="Kotlin"default>
@@ -406,13 +410,12 @@ val window: MappedWindow<OrderSummary, Order> = orm.selectFrom(Order::class, Ord
406
410
.scroll(Scrollable.of(Order_.city.key(), 20))
407
411
408
412
// Navigation tokens are null because OrderSummary != Order.
409
-
// Construct the next scrollable manually from the last result:
410
-
if (window.hasNext()) {
411
-
val lastCity = window.content.last().city.id()
412
-
val next:MappedWindow<OrderSummary, Order> = orm.selectFrom(Order::class, OrderSummary::class) { ... }
0 commit comments