How to get cursor for each item returned by a query?
Closed this issue · 3 comments
Describe the bug
My project is using GraphQL and cursor-based pagination (connections). As part of implementing this kind of pagination, I want to return each item from DynamoDB with a cursor specific to that item. This is so that users can paginate starting at a specific cursor.
A GraphQL query using cursor-based pagination would look something like this:
query {
friends(first: 2, after: "<cursor of friend 3>") { # Return a list of 2 friends, starting after friend 3
edges {
node {
name
}
cursor # Cursor specific to this friend
}
pageInfo {
endCursor # Cursor specific to the last friend in this connection
hasNextPage
}
}
}
ElectroDB's query
operation will return a single cursor (I believe this is the LastEvaluatedKey
from DynamoDB that is then copied, stringified, and base64 encoded) for the entire item collection from DynamoDB. However, I'm trying to create a cursor for each item in the collection.
How can I do this? Looking at this util, it looks like I need the primary key and values of each entity, which I can't easily get.
I think I could use .go({ data: "raw" })
but I'd like to stay away from that if possible, since I'd need to convert back to the "nice" format of my data that ElectroDB helps with with. I could also use .go({ data: "includeKeys" })
, but I don't get any type safety on the keys returned (for example, pk
doesn't exist on the entity, and I'm having to use a // @ts-expect-error
when referring to that, which I'd also like to stay away from).
ElectroDB Version
2.13.0
Expected behavior
Just brainstorming on potential API options here, while trying to keep in mind backwards compatibility...
It'd be great to have some sort of utility function on the entity like:
const { cursor, data } = await MyEntity.query.all({ id: "blah" }).go()
const edges = data.forEach(entity => ({
node: entity,
cursor: MyEntity.createCursor(entity), // Returns a correctly formatted cursor for this entity
}))
return {
edges,
pageInfo: {
endCursor: edges[edges.length - 1].cursor,
hasNextPage: Boolean(cursor),
}
}
Another option could be an addCursorForEachItem
execution option:
const { cursor, data } = await MyEntity.query.all({ id: "blah" }).go({
addCursorForEachItem: true
})
// data: [
// { item: { ...item... }, cursor: "..." },
// { item: { ...item... }, cursor: "..." },
// { item: { ...item... }, cursor: "..." },
// ]
// compared to
const { cursor, data } = await MyEntity.query.all({ id: "blah" }).go({
addCursorForEachItem: false
})
// data: [
// { ...item... },
// { ...item... },
// { ...item... },
// ]
(writing this code out from hand, hopefully it makes sense)
I had the same problem and ended up doing it manually in the past. I haven't tried this yet, but there is a utility called conversions that looks to convert an item to a cursor.
This is perfect @CaveSeal (and @tywalch for having this in the first place 😄) thank you!
I'm able to do the following for my example above:
const { cursor, data } = await MyEntity.query.all({ id: "blah" }).go()
const edges = data.forEach(entity => ({
node: entity,
cursor: MyEntity.conversions.byAccessPattern.all.fromComposite.toCursor(entity, { strict: "all" })
}))
...