Deno KVのcursorを調べる
Deno KVでpagenationをどう実装するか。
SaaSKitでどうなっているかというと、cursorをbase64にして渡している。
cursorのAPI Docはここ。
Deno KVの場合、次のページがあるかどうかを取得するため一つ多めに取る、って聞いたことがあったけど、今それ有効なのかな?saaskitではやってなさそうに見えるが…
公式blogにもpagenationの例があった。
DenoにSaaSKitのメンテナがissueを立てていた。やはりlimit + 1で取得することを推奨している。その場合cursorは戻ってきたものをそのまま使うだとまずそうな気がするが…
ともかく使ってみよう。
const kv = await Deno.openKv("test")
async function insert(num: number){
for (let i = 0; i < num; i++) {
await kv.set(["users", i], "hello")
}
}
// 100個set
await insert(100)
// 取得
const users = await kv.list<string>({prefix: ["users"]}, {limit: 10})
for await (const user of users){
console.log(user)
}
console.log("cursor:", users.cursor)
cursor: IcAiAAAAAAAA
あ、これもともとbase64表現で入るのか
んん?
limit: 100だとcursorが帰って来るが
const users = await kv.list<string>({prefix: ["users"]}, {limit: 100})
cursor: IcBYwAAAAAAA
limit: 101だと空白が返ってくる
const users = await kv.list<string>({prefix: ["users"]}, {limit: 101})
cursor:
つまりlimit: 100なのに101個目を走査するのはコストが余分にかかるが、limit: 101なら終端であることを返せるのか。であるならissueのコメント通り。
ということは、100個入っているDBでRead moreを出さないようにするには、101個走査しないといけない。ただしmaxの個数をiteratorで読みきらずに、最後から一つ前のcursorを返してあげないと、次のページに行ったときのindexがずれてしまう。
こんな感じにしてみた。
limit = 9なら9個取ったあとにもう一つ読めるcursorを返す。
limit = 10ならcursor=""を返す。
const kv = await Deno.openKv("test")
async function insert(num: number){
for (let i = 0; i < num; i++) {
await kv.set(["users", i], "hello")
}
}
// 100個set
await insert(10)
const limit = 9
// 取得
const iter = await kv.list<string>({prefix: ["users"]}, {limit: limit + 1})
let cursor = ""
const result = []
for(let i = 0; i < limit ; i++){
const item = await iter.next()
if(item.done) break
result.push(item)
cursor = iter.cursor
// 末尾なら一つ先まで読み込む
if(i === limit - 1){
const next = await iter.next()
// ひとつ先がなければcursorは空で返す
if(next.done) {
cursor = ""
}
break
}
}
console.log("result", result)
console.log("cursor", cursor)
console.log("次が取れるか?")
const nextResult = await kv.list({prefix: ["users"]}, {limit: 10, cursor: cursor})
console.log(await nextResult.next())
なんかもうちょっと短く書けないのかな…
ちなみにこれを調べてる動機は、KV explorer extensionにページネーションをつけようとしているため。
まあでも、スクロールをトリガーにした自動fetchをするのであれば、この挙動を実装しなくてもいいかもしれない。fetchもほとんど一瞬で終わるだろうし。
kiviでは面倒になって結局実装しなかった。いきなり100件fetchしてるからあんまりread moreしないだろうとの判断でそこは放っておいた。やるなら何かしらのDBフレームワーク内で処理されたほうがいいような感じだなあ。