Skip to content

Conversation

@vweevers
Copy link
Member

@vweevers vweevers added the documentation Improvements or additions to documentation label Dec 13, 2025

There's no operation that can skip N amount of keys. You could manually skip keys by repeatedly calling `iterator.next()` or `iterator.nextv()` but that's expensive because those calls do fetch the data of every entry. Or you could seek to a key, but determining that key either requires an index (built by you) or for your keys to be monotonically increasing numbers. In other words, if you strictly need offset-based pagination, you will have to build it.

Alternatively, use the concept of cursors (also known as continuation tokens). Where the input is not an offset but instead the last key visited - i.e. `cursor`. To fetch a page of say 10 entries, create an iterator with `db.iterator({ gt: cursor, limit: 10 })`. Then use the last key of that iterator as the cursor of the next page. This approach better aligns with the characteristics of `abstract-level`.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice summary, thanks. From an architectural perspective, the big challenge is: How and Where to store those cursors for each concurrent user?

And perhaps, if you could add links to other examples or libraries covering this issue, that would be great.

Copy link
Member Author

@vweevers vweevers Dec 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assuming the application is served over HTTP, it'd be a URL query parameter. As an example, check GitHub notifications. The "Prev" and "Next" buttons in that UI have URLs like https://github.com/notifications?before=Y3Vyc29yOjI2 and /notifications?after=Y3Vyc29yOjUw respectively.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the example @vweevers this is interesting, also challenging. How would you store cursor positions for each user session and where? Sounds some session-based short-term caching is required for this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They're not stored per se, in the same way you wouldn't store an offset and limit. Instead you embed them e.g. in the HTML served by your server (or similar). The process of generating such HTML is roughly:

// For the sake of brevity this:
// - Only implements going to a next page, not a previous
// - Ignores security, validation and escaping
// - Assumes the db uses utf8 key encoding

const requestUrl = '/foo?after=alice'
const queryParams = new URL(requestUrl).searchParams
const afterCursor = queryParams.get('after')
const limit = 10

let entries

if (afterCursor) {
  // Grab N entries after the given key
  entries = await db.iterator({ gt: afterCursor, limit }).all()
} else {
  // Start from the beginning
  entries = await db.iterator({ limit }).all()
}

// TODO: handle case where entries is empty
const lastKey = entries[entries.length - 1][0]

const html = `
  <p>This page has ${entries.length} entries</p>
  <p><a href="/foo?after=${lastKey}">Next page</a></p>
`

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah. Thanks. This made "click" in my head.

Unfortunately, my app is SPA and doesn't render HTML pages on server side. In that case, I'd still have to pass them (like offset and limit) over API which will complicate things.

Perhaps time for me to migrate to React server-side rendering (SSR) ... 🤷

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants