Unverified Commit 47e36f55 authored by Hong Minhee's avatar Hong Minhee
Browse files

Simplify KvStore.list() API to take optional prefix directly



Change the list() method signature from list(options: KvStoreListOptions)
to list(prefix?: KvKey) for a cleaner API. The KvStoreListOptions interface
is removed as it only contained a single prefix field.

When prefix is omitted or undefined, the method returns all entries in the
store.

Co-Authored-By: default avatarClaude Opus 4.5 <noreply@anthropic.com>
parent 8f1faf76
Loading
Loading
Loading
Loading
+5 −4
Original line number Diff line number Diff line
@@ -37,11 +37,12 @@ To be released.
        result, and the verification method used.

 -  Added optional `list()` method to the `KvStore` interface for enumerating
    entries by key prefix.  This enables efficient prefix scanning which is
    useful for implementing features like distributed trace storage, cache
    invalidation by prefix, and listing related entries.  [[#498]]
    entries by key prefix.  This method takes an optional `prefix` parameter;
    when omitted or empty, it returns all entries.  This enables efficient
    prefix scanning which is useful for implementing features like distributed
    trace storage, cache invalidation by prefix, and listing related entries.
    [[#498]]

     -  Added `KvStoreListOptions` interface.
     -  Added `KvStoreListEntry` interface.
     -  Implemented in `MemoryKvStore`.

+6 −11
Original line number Diff line number Diff line
@@ -419,7 +419,6 @@ import type {
  KvStore,
  KvKey,
  KvStoreSetOptions,
  KvStoreListOptions,
  KvStoreListEntry,
} from "@fedify/fedify";

@@ -454,9 +453,7 @@ class MyCustomKvStore implements KvStore {
    // ---cut-end---
  }

  async *list(
    options: KvStoreListOptions
  ): AsyncIterable<KvStoreListEntry> {
  async *list(prefix?: KvKey): AsyncIterable<KvStoreListEntry> {
    // Implement list logic if needed
  }
}
@@ -706,7 +703,7 @@ batch operations or iterating over related entries.

~~~~ typescript twoslash
import type { KvStore, KvKey, KvStoreSetOptions } from "@fedify/fedify";
import type { KvStoreListOptions, KvStoreListEntry } from "@fedify/fedify";
import type { KvStoreListEntry } from "@fedify/fedify";
/**
 * A hypothetical storage interface.
 */
@@ -740,10 +737,8 @@ class MyCustomKvStore implements KvStore {
  ): Promise<void> { }
  async delete(key: KvKey): Promise<void> { }
// ---cut-before---
async *list(
  options: KvStoreListOptions
): AsyncIterable<KvStoreListEntry> {
  const serializedPrefix = this.serializeKey(options.prefix);
async *list(prefix?: KvKey): AsyncIterable<KvStoreListEntry> {
  const serializedPrefix = prefix ? this.serializeKey(prefix) : "";
  for await (const { key, value } of this.storage.scan(serializedPrefix)) {
    yield {
      key: this.deserializeKey(key),
@@ -755,8 +750,8 @@ async *list(
}
~~~~

The `~KvStore.list()` method takes a `KvStoreListOptions` object with a
`~KvStoreListOptions.prefix` property specifying the key prefix to filter by.
The `~KvStore.list()` method takes an optional `prefix` parameter specifying
the key prefix to filter by.  If omitted or empty, it returns all entries.
It returns an `AsyncIterable` of `KvStoreListEntry` objects, each containing
a `~KvStoreListEntry.key` and a `~KvStoreListEntry.value`.

+3 −7
Original line number Diff line number Diff line
@@ -49,7 +49,7 @@ describe("WorkersKvStore", {
    await store.set(["other", "x"], "value-x");

    const entries: { key: readonly unknown[]; value: unknown }[] = [];
    for await (const entry of store.list!({ prefix: ["prefix"] })) {
    for await (const entry of store.list!(["prefix"])) {
      entries.push({ key: entry.key, value: entry.value });
    }

@@ -66,7 +66,7 @@ describe("WorkersKvStore", {
    await store.set(["b"], "value-b");

    const entries: { key: readonly unknown[]; value: unknown }[] = [];
    for await (const entry of store.list!({ prefix: ["a"] })) {
    for await (const entry of store.list!(["a"])) {
      entries.push({ key: entry.key, value: entry.value });
    }

@@ -85,11 +85,7 @@ describe("WorkersKvStore", {
    await store.set(["d", "e", "f"], "value-def");

    const entries: { key: readonly unknown[]; value: unknown }[] = [];
    for await (
      const entry of store.list!({
        prefix: [] as unknown as readonly [string, ...string[]],
      })
    ) {
    for await (const entry of store.list!()) {
      entries.push({ key: entry.key, value: entry.value });
    }

+5 −8
Original line number Diff line number Diff line
@@ -17,7 +17,6 @@ import type {
  KvKey,
  KvStore,
  KvStoreListEntry,
  KvStoreListOptions,
  KvStoreSetOptions,
  MessageQueue,
  MessageQueueEnqueueOptions,
@@ -90,10 +89,8 @@ export class WorkersKvStore implements KvStore {
   * {@inheritDoc KvStore.list}
   * @since 1.10.0
   */
  async *list(
    options: KvStoreListOptions,
  ): AsyncIterable<KvStoreListEntry> {
    if (options.prefix.length === 0) {
  async *list(prefix?: KvKey): AsyncIterable<KvStoreListEntry> {
    if (prefix == null || prefix.length === 0) {
      // Empty prefix: list all entries
      // JSON encoded keys start with '[', so prefix with '[' to match all arrays
      let cursor: string | undefined;
@@ -123,8 +120,8 @@ export class WorkersKvStore implements KvStore {
      // Keys are JSON encoded: '["prefix","a"]'
      // Pattern to match keys starting with prefix: '["prefix",' matches children
      // Also check for exact match: '["prefix"]'
      const exactKey = this.#encodeKey(options.prefix);
      const childrenPattern = JSON.stringify(options.prefix).slice(0, -1) + ",";
      const exactKey = this.#encodeKey(prefix);
      const childrenPattern = JSON.stringify(prefix).slice(0, -1) + ",";

      // First, check if the exact prefix key exists
      const { value, metadata } = await this.#namespace.getWithMetadata(
@@ -137,7 +134,7 @@ export class WorkersKvStore implements KvStore {
          (metadata as KvMetadata).expires! >= Date.now())
      ) {
        yield {
          key: options.prefix,
          key: prefix,
          value,
        };
      }
+3 −7
Original line number Diff line number Diff line
@@ -41,7 +41,7 @@ Deno.test("DenoKvStore", async (t) => {
    await store.set(["other", "x"], "value-x");

    const entries: { key: Deno.KvKey; value: unknown }[] = [];
    for await (const entry of store.list!({ prefix: ["prefix"] })) {
    for await (const entry of store.list!(["prefix"])) {
      entries.push(entry);
    }

@@ -63,7 +63,7 @@ Deno.test("DenoKvStore", async (t) => {
    await store.set(["b"], "value-b");

    const entries: { key: Deno.KvKey; value: unknown }[] = [];
    for await (const entry of store.list!({ prefix: ["a"] })) {
    for await (const entry of store.list!(["a"])) {
      entries.push(entry);
    }

@@ -84,11 +84,7 @@ Deno.test("DenoKvStore", async (t) => {
    await store.set(["d", "e", "f"], "value-def");

    const entries: { key: Deno.KvKey; value: unknown }[] = [];
    for await (
      const entry of store.list!({
        prefix: [] as unknown as readonly [string, ...string[]],
      })
    ) {
    for await (const entry of store.list!()) {
      entries.push(entry);
    }

Loading