Skip to content

Listing Items

The List family of APIs are one of the most useful APIs in StatelyDB. While APIs like Put, Get, and Delete allow you to operate on individual Items by their full key path, List lets you fetch multiple Items from the same Group in one go (i.e. Items that share the same Group Key). This can be useful for fetching collections of Items such as a customer’s order history, or a list of messages in a conversation. It’s especially useful for implementing features like infinite scrolling lists, where you want to keep fetching more Items as the user scrolls down.

Beginning a Paginated List

You start a list by calling BeginList with a key path prefix and an optional limit to get an initial result set. StatelyDB will return all the Items whose key paths have the given prefix.

Your initial result set might not contain all the Items under that prefix, if you set a page size limit. This initial result set is your first “page” of data - imagine it as the first several screens full of emails in your email app.

For this example we’ll use the schema defined in Example: Movies Schema, which defines these key paths (among others):

Item TypeKey Path Template
Movie/movie-:id
Character/movie-:movieId/role-:role/name-:name

Note how both Movie and Character share the same /movie-:id prefix.

15 collapsed lines
1
package main
2
3
import (
4
"context"
5
"fmt"
6
"os"
7
"slices"
8
"strconv"
9
"time"
10
11
"github.com/StatelyCloud/go-sdk/stately"
12
// This is the code you generated from schema
13
"github.com/StatelyCloud/stately/go-sdk-sample/schema"
14
)
15
16
func sampleList(
17
ctx context.Context,
18
client stately.Client,
19
movieID []byte,
20
) (*stately.ListToken, error) {
21
iter, err := client.BeginList(
22
ctx,
23
// This key path is a prefix of BOTH Movie and Character.
24
"/movie-"+stately.ToKeyID(movieID),
25
stately.ListOptions{Limit: 10},
26
)
27
if err != nil {
28
return nil, err
29
}
30
31
for iter.Next() {
32
item := iter.Value()
33
switch v := item.(type) {
34
case *schema.Movie:
35
fmt.Printf("Movie Title: %s\n", v.GetTitle())
36
case *schema.Character:
37
fmt.Printf("Character Name: %s\n", v.GetName())
38
}
39
}
40
// When we've exhausted the iterator, we'll get a token that we
41
// can use to fetch the next page of items.
42
return iter.Token()
43
}

Using the List Token to Continue

The result from BeginList includes a token which you can save for later, or use right away. Only the tokenData part of the token needs to be saved. There is also a canContinue property which indicates whether there are more pages still available, and canSync which indicates whether SyncList is supported for this list.

You can pass the token to ContinueList, which lets you fetch more results for your result set, continuing from where you left off. For example, you might call ContinueList to get the next few screens of emails when the user scrolls down in their inbox. Or, you could call it in the background to eventually pull the entire result set into a local database. All you need is the token—the original arguments to BeginList are saved with it.

The token keeps track of the state of the result set, and ContinueList allows you to expand the window of results that you have retrieved. Every time you call ContinueList, you’ll get a new token back, and you can use that token for the next ContinueList or SyncList call, and so on. This is also known as pagination, and it allows you to quickly show results to your users without having to get all the data at once, while still having the ability to grab the next results consistently.

For many applications, you’ll only need to call BeginList once, to set up the initial token, and from then on they’ll call ContinueList (as a user scrolls through results) or SyncList (whenever the user opens or focuses the application, to check for new and updated items).

In this example we’ve passed the token from the first example back into ContinueList to keep getting more Items:

15 collapsed lines
1
package main
2
3
import (
4
"context"
5
"fmt"
6
"os"
7
"slices"
8
"strconv"
9
"time"
10
11
"github.com/StatelyCloud/go-sdk/stately"
12
// This is the code you generated from schema
13
"github.com/StatelyCloud/stately/go-sdk-sample/schema"
14
)
15
16
func sampleContinueList(
17
ctx context.Context,
18
client stately.Client,
19
token *stately.ListToken,
20
) (*stately.ListToken, error) {
21
iter, err := client.ContinueList(ctx, token.Data)
22
if err != nil {
23
return nil, err
24
}
25
for iter.Next() {
26
item := iter.Value()
27
switch v := item.(type) {
28
case *schema.Character:
29
fmt.Printf("Character Name: %s\n", v.GetName())
30
case *schema.Movie:
31
fmt.Printf("Movie Title: %s\n", v.GetTitle())
32
}
33
}
34
// You could save the token to call ContinueList later.
35
return iter.Token()
36
}

Sort Direction

By default, the items returned in a List are sorted by their key paths, in descending order. Namespaces, string IDs, and byte IDs are sorted lexicographically, while number IDs are sorted numerically. For example:

  • /customer-1234
  • /customer-1234/order-9
  • /customer-1234/order-10
  • /customer-1234/order-10/li-abc
  • /customer-1234/order-10/li-bcd

You can specify a SortDirection option to reverse this order (from the default of Ascending to Descending).

Listing All Groups

Right now there is no API in StatelyDB for listing out your top level Groups. So, for example, if your Groups were based on customers, there would not be an API to list all customers. Because of the way StatelyDB distributes Groups amongst partitions within the database (and eventually, across different databases around the world), listing all of the top level Groups would be slow and expensive. Most applications will not need to perform an operation like this if the Group Key is chosen well - for example it is not frequently necessary to list out all users of an application in one place. However, we can help if you are looking to figure out how to work around this limitation.