dollarshaveclub / cloudworker

Run Cloudflare Worker scripts locally
MIT License
516 stars 89 forks source link

[Feature Request] Add KV list method #130

Open GregBrimble opened 4 years ago

GregBrimble commented 4 years ago

KV namespaces now support the list() method which returns a paginated list of keys: https://developers.cloudflare.com/workers/reference/storage/listing-keys/.

This method is missing from the KeyValueStore class.

GregBrimble commented 4 years ago

I've personally being using this shim:

// namespace.test.ts

/* eslint-disable @typescript-eslint/camelcase */
import Cloudworker from '@dollarshaveclub/cloudworker'

const LIST_MAX_LIMIT = 1000

const namespace = new (Cloudworker as any).KeyValueStore()
namespace.list = async function(
  this,
  {
    limit = 1000,
    prefix = ``,
    cursor = `-1`,
  }: {
    prefix?: string
    limit?: number
    cursor?: string
  }
): Promise<{
  keys: {
    name: string
    expiration?: number
  }[]
  list_complete: boolean
  cursor: string
}> {
  if (limit > LIST_MAX_LIMIT) limit = LIST_MAX_LIMIT
  if (limit < 0) limit = 0

  const { store } = this

  let keys = Array.from(store.keys() as string[]).filter((key: string) =>
    key.startsWith(prefix)
  )

  keys = keys.slice(+cursor + 1)

  const list_complete = keys.length <= limit

  keys = keys.slice(0, limit)

  const nextCursor = (
    +cursor + (list_complete ? keys.length : limit)
  ).toString()

  return Promise.resolve({
    keys: keys.map(key => ({
      name: key,
    })),
    list_complete,
    cursor: nextCursor,
  })
}

export { namespace }

If there is interest, I'm more than happy to write up a PR. The only concern I have is with the expiration part of the response. This hasn't been implemented in KeyValueStore yet, so I've just been dropping it from the response.

I also don't know how Cloudflare are generating the cursors they use (e.g. 6Ck1la0VxJ0djhidm1MdX2FyD), so I've been using the index of the (ordered) map, which seems to work for testing.

iameli commented 4 years ago

This looks solid!

I also don't know how Cloudflare are generating the cursors they use (e.g. 6Ck1la0VxJ0djhidm1MdX2FyD), so I've been using the index of the (ordered) map, which seems to work for testing.

The main thing to verify here would be the behavior of the cursors when there's lots of insertions in between list operations. I slightly suspect that Cloudflare's cursors use the last key of the previous "page" as a starting point for the next page rather than its global order, but I haven't verified this experimentally.