Unverified Commit ab18fcf5 authored by Hong Minhee's avatar Hong Minhee
Browse files

Implement list() method in WorkersKvStore

parent 8cde5f39
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -72,6 +72,10 @@ To be released.

 -  Implemented `list()` method in `DenoKvStore`.  [[#498]]

### @fedify/cfworkers

 -  Implemented `list()` method in `WorkersKvStore`.  [[#498]]


Version 1.9.2
-------------
+37 −0
Original line number Diff line number Diff line
@@ -36,6 +36,43 @@ describe("WorkersKvStore", {
    await store.delete(["foo", "bar"]);
    strictEqual(await store.get(["foo", "bar"]), undefined);
  });

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

    await store.set(["prefix", "a"], "value-a");
    await store.set(["prefix", "b"], "value-b");
    await store.set(["prefix", "nested", "c"], "value-c");
    await store.set(["other", "x"], "value-x");

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

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

  test("list() - single element key", 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"], "value-b");

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

    strictEqual(entries.length, 1);
    strictEqual(entries[0].value, "value-a");
  });
});

describe("WorkersMessageQueue", {
+57 −0
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@ import type {
import type {
  KvKey,
  KvStore,
  KvStoreListEntry,
  KvStoreListOptions,
  KvStoreSetOptions,
  MessageQueue,
  MessageQueueEnqueueOptions,
@@ -83,6 +85,61 @@ export class WorkersKvStore implements KvStore {
  delete(key: KvKey): Promise<void> {
    return this.#namespace.delete(this.#encodeKey(key));
  }

  /**
   * {@inheritDoc KvStore.list}
   * @since 1.10.0
   */
  async *list(
    options: KvStoreListOptions,
  ): AsyncIterable<KvStoreListEntry> {
    // 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) + ",";

    // First, check if the exact prefix key exists
    const { value, metadata } = await this.#namespace.getWithMetadata(
      exactKey,
      "json",
    );
    if (
      value != null &&
      (metadata == null || (metadata as KvMetadata).expires == null ||
        (metadata as KvMetadata).expires! >= Date.now())
    ) {
      yield {
        key: options.prefix,
        value,
      };
    }

    // Then list all keys starting with prefix
    let cursor: string | undefined;
    do {
      const result = await this.#namespace.list<KvMetadata>({
        prefix: childrenPattern,
        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);
  }
}

/**