Page Token Extension
Capability URI: https://specs.serverlessinbox.com/page-token
Status: Draft
Background
Section titled “Background”Standard JMAP query pagination (RFC 8620 §5.5) relies on position (a zero-based
integer offset) and anchor (an object id to start from). Both require the server to
locate an item within an ordered result set by its absolute or relative index.
This is not possible on backends such as DynamoDB that use opaque cursor tokens for
pagination and do not expose index positions. This extension adds a pageToken argument
that replaces position and anchor for those backends.
When this proposal is mature enough it will be submitted to the JMAP working group, since the problem applies to any JMAP implementation over a NoSQL key-value store.
Capability object
Section titled “Capability object”A server that supports this extension includes the capability URI in its session
capabilities object. No additional capability properties are defined at this time.
{ "capabilities": { "https://specs.serverlessinbox.com/page-token": {} }}Extension to */query methods
Section titled “Extension to */query methods”This extension applies to all JMAP */query methods (e.g. Email/query,
Mailbox/query, Thread/query).
New argument: pageToken
Section titled “New argument: pageToken”| Property | Type | Description |
|---|---|---|
pageToken | string | null | Opaque cursor returned by a previous query response. Pass null or omit to start from the beginning of the result set. |
pageToken is mutually exclusive with position and anchor. If pageToken is
provided alongside either of those arguments the server MUST return an
invalidArguments error.
New response property: pageToken
Section titled “New response property: pageToken”| Property | Type | Description |
|---|---|---|
pageToken | string | null | Opaque cursor for the next page. null means this is the last page. |
The token is opaque to clients. Its format and lifetime are server-defined. Clients MUST NOT construct, modify, or cache tokens beyond a single pagination sequence.
Behaviour
Section titled “Behaviour”- The server applies
filterandsortexactly as it would without this extension. limitcontinues to control the maximum number of ids returned per page.calculateTotalMAY be supported; servers that cannot calculate a total without a full scan SHOULD omittotalfrom the response even whencalculateTotalistrue, and MUST NOT return an error in that case.collapseThreads(onEmail/query) works as normal.positionin the response SHOULD be0when the backend cannot determine the absolute offset of the first returned item.
Example
Section titled “Example”First page request:
["Email/query", { "accountId": "u1", "filter": { "inMailbox": "inbox" }, "sort": [{ "property": "receivedAt", "isAscending": false }], "limit": 50}, "r1"]First page response:
["Email/query/reply", { "accountId": "u1", "queryState": "...", "canCalculateChanges": false, "position": 0, "ids": ["e1", "e2", "..."], "pageToken": "eyJsYXN0S2V5IjoiZTUwIn0"}, "r1"]Next page request — pass the pageToken from the response as pageToken in the next request:
["Email/query", { "accountId": "u1", "filter": { "inMailbox": "inbox" }, "sort": [{ "property": "receivedAt", "isAscending": false }], "limit": 50, "pageToken": "eyJsYXN0S2V5IjoiZTUwIn0"}, "r2"]Error conditions
Section titled “Error conditions”| Error type | Condition |
|---|---|
invalidArguments | pageToken is provided together with position or anchor. |
invalidArguments | pageToken is not a string or is structurally invalid. |
serverFail | The token has expired or no longer points to a valid cursor position. |
Relationship to queryChanges
Section titled “Relationship to queryChanges”pageToken cursors are not intended for use with */queryChanges. Clients that
need to sync incremental changes should use the standard sinceQueryState mechanism.
A token-paginated query where canCalculateChanges is false does not support
*/queryChanges.