Unverified Commit 8f1faf76 authored by Hong Minhee's avatar Hong Minhee
Browse files

Support empty prefix in KvStore.list() method

parent 3f60b861
Loading
Loading
Loading
Loading
+22 −0
Original line number Diff line number Diff line
@@ -73,6 +73,28 @@ describe("WorkersKvStore", {
    strictEqual(entries.length, 1);
    strictEqual(entries[0].value, "value-a");
  });

  test("list() - empty prefix", async (t) => {
    const { env } = t as unknown as {
      env: Record<string, KVNamespace<string>>;
    };
    const store = new WorkersKvStore(env.KV1);

    await store.set(["a"], "value-a");
    await store.set(["b", "c"], "value-bc");
    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[]],
      })
    ) {
      entries.push({ key: entry.key, value: entry.value });
    }

    strictEqual(entries.length, 3);
  });
});

describe("WorkersMessageQueue", {
+69 −41
Original line number Diff line number Diff line
@@ -93,6 +93,33 @@ export class WorkersKvStore implements KvStore {
  async *list(
    options: KvStoreListOptions,
  ): AsyncIterable<KvStoreListEntry> {
    if (options.prefix.length === 0) {
      // Empty prefix: list all entries
      // JSON encoded keys start with '[', so prefix with '[' to match all arrays
      let cursor: string | undefined;
      do {
        const result = await this.#namespace.list<KvMetadata>({
          prefix: "[",
          cursor,
        });
        cursor = result.list_complete ? undefined : result.cursor;

        for (const keyInfo of result.keys) {
          const metadata = keyInfo.metadata as KvMetadata | undefined;
          if (metadata?.expires != null && metadata.expires < Date.now()) {
            continue;
          }

          const value = await this.#namespace.get(keyInfo.name, "json");
          if (value == null) continue;

          yield {
            key: JSON.parse(keyInfo.name) as KvKey,
            value,
          };
        }
      } while (cursor != null);
    } else {
      // Keys are JSON encoded: '["prefix","a"]'
      // Pattern to match keys starting with prefix: '["prefix",' matches children
      // Also check for exact match: '["prefix"]'
@@ -141,6 +168,7 @@ export class WorkersKvStore implements KvStore {
      } while (cursor != null);
    }
  }
}

/**
 * Implementation of the {@link MessageQueue} interface for Cloudflare
+25 −0
Original line number Diff line number Diff line
@@ -75,6 +75,31 @@ Deno.test("DenoKvStore", async (t) => {
    await store.delete(["b"]);
  });

  await t.step("list() - empty prefix", async () => {
    // Cleanup from previous tests
    await store.delete(["foo", "bar"]);

    await store.set(["a"], "value-a");
    await store.set(["b", "c"], "value-bc");
    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[]],
      })
    ) {
      entries.push(entry);
    }

    assertEquals(entries.length, 3);

    // Cleanup
    await store.delete(["a"]);
    await store.delete(["b", "c"]);
    await store.delete(["d", "e", "f"]);
  });

  kv.close();
});

+23 −0
Original line number Diff line number Diff line
@@ -88,4 +88,27 @@ test("MemoryKvStore", async (t) => {

    await store.delete(["expired", "valid"]);
  });

  await t.step("list() with empty prefix", async () => {
    // Cleanup from previous tests
    await store.delete(["foo", "bar"]);

    // Setup: add entries with various key structures
    await store.set(["a"], "value-a");
    await store.set(["b", "c"], "value-bc");
    await store.set(["d", "e", "f"], "value-def");

    // Test: empty prefix should return all entries
    const entries: { key: KvKey; value: unknown }[] = [];
    for await (const entry of store.list!({ prefix: [] as unknown as KvKey })) {
      entries.push(entry);
    }

    assertEquals(entries.length, 3);

    // Cleanup
    await store.delete(["a"]);
    await store.delete(["b", "c"]);
    await store.delete(["d", "e", "f"]);
  });
});
+28 −0
Original line number Diff line number Diff line
@@ -221,4 +221,32 @@ test(
  },
);

test(
  "PostgresKvStore.list() - empty prefix",
  { skip: dbUrl == null },
  async () => {
    if (dbUrl == null) return; // Bun does not support skip option
    const { sql, store } = getStore();
    try {
      await store.set(["a"], "value-a");
      await store.set(["b", "c"], "value-bc");
      await store.set(["d", "e", "f"], "value-def");

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

      assert.strictEqual(entries.length, 3);
    } finally {
      await store.drop();
      await sql.end();
    }
  },
);

// cSpell: ignore regclass
Loading