Skip to content

Commit 14602d9

Browse files
feat(wrangler): Add Bulk Key Get command to KV (#8620)
* feat(wrangler): Add Bulk Key Get command to KV * chore(wrangler): Add unit tests for bulk get command in KV * chore(wrangler): Add e2e tests for bulk get command in KV * chore(wrangler): Add changeset * chore(wrangler): Removed refactoring of bulk get file. * chore(wrangler): Remove sendMetrics call from get bulk * chore(wrangler): Change binding arg description for KV commands * ˇ * Update packages/wrangler/src/kv/index.ts Co-authored-by: Carmen Popoviciu <[email protected]> * Update packages/wrangler/src/__tests__/kv.test.ts Co-authored-by: Carmen Popoviciu <[email protected]> * Update packages/wrangler/src/__tests__/kv.test.ts Co-authored-by: Carmen Popoviciu <[email protected]> * Update packages/wrangler/src/__tests__/kv.test.ts Co-authored-by: Carmen Popoviciu <[email protected]> * Update packages/wrangler/src/__tests__/kv.test.ts Co-authored-by: Carmen Popoviciu <[email protected]> * Update packages/wrangler/src/__tests__/kv.test.ts Co-authored-by: Carmen Popoviciu <[email protected]> * Update packages/wrangler/src/__tests__/kv.test.ts Co-authored-by: Carmen Popoviciu <[email protected]> --------- Co-authored-by: Carmen Popoviciu <[email protected]>
1 parent 9f25620 commit 14602d9

File tree

7 files changed

+361
-16
lines changed

7 files changed

+361
-16
lines changed

.changeset/modern-parts-check.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"wrangler": minor
3+
---
4+
5+
Add support for KV Bulk Gets in Wrangler

packages/wrangler/src/__tests__/index.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,7 @@ describe("wrangler", () => {
261261
Interact with multiple Workers KV key-value pairs at once
262262
263263
COMMANDS
264+
wrangler kv bulk get <filename> Gets multiple key-value pairs from a namespace [open-beta]
264265
wrangler kv bulk put <filename> Upload multiple key-value pairs to a namespace
265266
wrangler kv bulk delete <filename> Delete multiple key-value pairs from a namespace
266267

packages/wrangler/src/__tests__/kv.local.test.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,61 @@ describe("wrangler", () => {
353353
`);
354354
});
355355

356+
it("should get local bulk kv storage", async () => {
357+
const keyValues = [
358+
{
359+
key: "hello",
360+
value: "world",
361+
},
362+
{
363+
key: "test",
364+
value: "value",
365+
},
366+
];
367+
writeFileSync("./keys.json", JSON.stringify(keyValues));
368+
await runWrangler(
369+
`kv bulk put keys.json --namespace-id bulk-namespace-id`
370+
);
371+
await runWrangler(`kv key list --namespace-id bulk-namespace-id`);
372+
expect(std.out).toMatchInlineSnapshot(`
373+
"Success!
374+
[
375+
{
376+
\\"name\\": \\"hello\\"
377+
},
378+
{
379+
\\"name\\": \\"test\\"
380+
}
381+
]"
382+
`);
383+
const keys = ["hello", "test"];
384+
writeFileSync("./keys.json", JSON.stringify(keys));
385+
await runWrangler(
386+
`kv bulk get keys.json --namespace-id bulk-namespace-id`
387+
);
388+
expect(std.out).toMatchInlineSnapshot(`
389+
"Success!
390+
[
391+
{
392+
\\"name\\": \\"hello\\"
393+
},
394+
{
395+
\\"name\\": \\"test\\"
396+
}
397+
]
398+
{
399+
\\"hello\\": {
400+
\\"value\\": \\"world\\"
401+
},
402+
\\"test\\": {
403+
\\"value\\": \\"value\\"
404+
}
405+
}
406+
407+
Success!"
408+
`);
409+
});
410+
356411
it("should follow persist-to for local kv storage", async () => {
357412
await runWrangler(
358413
`kv key put val value --namespace-id some-namespace-id`

packages/wrangler/src/__tests__/kv.test.ts

Lines changed: 145 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -636,7 +636,7 @@ describe("wrangler", () => {
636636
-v, --version Show version number [boolean]
637637
638638
OPTIONS
639-
--binding The binding of the namespace to write to [string]
639+
--binding The binding name to the namespace to write to [string]
640640
--namespace-id The id of the namespace to write to [string]
641641
--preview Interact with a preview namespace [boolean]
642642
--ttl Time for which the entries should be visible [number]
@@ -679,7 +679,7 @@ describe("wrangler", () => {
679679
-v, --version Show version number [boolean]
680680
681681
OPTIONS
682-
--binding The binding of the namespace to write to [string]
682+
--binding The binding name to the namespace to write to [string]
683683
--namespace-id The id of the namespace to write to [string]
684684
--preview Interact with a preview namespace [boolean]
685685
--ttl Time for which the entries should be visible [number]
@@ -724,7 +724,7 @@ describe("wrangler", () => {
724724
-v, --version Show version number [boolean]
725725
726726
OPTIONS
727-
--binding The binding of the namespace to write to [string]
727+
--binding The binding name to the namespace to write to [string]
728728
--namespace-id The id of the namespace to write to [string]
729729
--preview Interact with a preview namespace [boolean]
730730
--ttl Time for which the entries should be visible [number]
@@ -767,7 +767,7 @@ describe("wrangler", () => {
767767
-v, --version Show version number [boolean]
768768
769769
OPTIONS
770-
--binding The binding of the namespace to write to [string]
770+
--binding The binding name to the namespace to write to [string]
771771
--namespace-id The id of the namespace to write to [string]
772772
--preview Interact with a preview namespace [boolean]
773773
--ttl Time for which the entries should be visible [number]
@@ -810,7 +810,7 @@ describe("wrangler", () => {
810810
-v, --version Show version number [boolean]
811811
812812
OPTIONS
813-
--binding The binding of the namespace to write to [string]
813+
--binding The binding name to the namespace to write to [string]
814814
--namespace-id The id of the namespace to write to [string]
815815
--preview Interact with a preview namespace [boolean]
816816
--ttl Time for which the entries should be visible [number]
@@ -855,7 +855,7 @@ describe("wrangler", () => {
855855
-v, --version Show version number [boolean]
856856
857857
OPTIONS
858-
--binding The binding of the namespace to write to [string]
858+
--binding The binding name to the namespace to write to [string]
859859
--namespace-id The id of the namespace to write to [string]
860860
--preview Interact with a preview namespace [boolean]
861861
--ttl Time for which the entries should be visible [number]
@@ -1240,7 +1240,7 @@ describe("wrangler", () => {
12401240
-v, --version Show version number [boolean]
12411241
12421242
OPTIONS
1243-
--binding The name of the namespace to get from [string]
1243+
--binding The binding name to the namespace to get from [string]
12441244
--namespace-id The id of the namespace to get from [string]
12451245
--preview Interact with a preview namespace [boolean] [default: false]
12461246
--text Decode the returned value as a utf8 string [boolean] [default: false]
@@ -1278,7 +1278,7 @@ describe("wrangler", () => {
12781278
-v, --version Show version number [boolean]
12791279
12801280
OPTIONS
1281-
--binding The name of the namespace to get from [string]
1281+
--binding The binding name to the namespace to get from [string]
12821282
--namespace-id The id of the namespace to get from [string]
12831283
--preview Interact with a preview namespace [boolean] [default: false]
12841284
--text Decode the returned value as a utf8 string [boolean] [default: false]
@@ -1317,7 +1317,7 @@ describe("wrangler", () => {
13171317
-v, --version Show version number [boolean]
13181318
13191319
OPTIONS
1320-
--binding The name of the namespace to get from [string]
1320+
--binding The binding name to the namespace to get from [string]
13211321
--namespace-id The id of the namespace to get from [string]
13221322
--preview Interact with a preview namespace [boolean] [default: false]
13231323
--text Decode the returned value as a utf8 string [boolean] [default: false]
@@ -1877,6 +1877,142 @@ describe("wrangler", () => {
18771877
expect(std.warn).toMatchInlineSnapshot(`""`);
18781878
});
18791879
});
1880+
1881+
describe("get", () => {
1882+
function mockGetRequest(
1883+
expectedNamespaceId: string,
1884+
expectedKeys: string[]
1885+
) {
1886+
const requests = { count: 0 };
1887+
msw.use(
1888+
http.post(
1889+
"*/accounts/:accountId/storage/kv/namespaces/:namespaceId/bulk/get",
1890+
async ({ request, params }) => {
1891+
requests.count++;
1892+
expect(params.accountId).toEqual("some-account-id");
1893+
expect(params.namespaceId).toEqual(expectedNamespaceId);
1894+
expect(request.headers.get("Content-Type")).toEqual(
1895+
"application/json"
1896+
);
1897+
expect(await request.json()).toEqual({
1898+
keys: expectedKeys,
1899+
});
1900+
1901+
// i.e. for [key1, key2] => { key1: "key1-value", key2: "key2-value" }
1902+
const result = expectedKeys.reduce(
1903+
(acc, curr) => {
1904+
acc[curr] = `${curr}-value`;
1905+
return acc;
1906+
},
1907+
{} as { [key: string]: string }
1908+
);
1909+
return HttpResponse.json(
1910+
createFetchResult({
1911+
values: result,
1912+
}),
1913+
{
1914+
status: 200,
1915+
}
1916+
);
1917+
}
1918+
)
1919+
);
1920+
return requests;
1921+
}
1922+
1923+
it("should get the keys parsed from a file (string)", async () => {
1924+
const keys = ["someKey1", "key2"];
1925+
writeFileSync("./keys.json", JSON.stringify(keys));
1926+
const requests = mockGetRequest("some-namespace-id", keys);
1927+
await runWrangler(
1928+
`kv bulk get --remote --namespace-id some-namespace-id keys.json`
1929+
);
1930+
expect(requests.count).toEqual(1);
1931+
expect(std.out).toMatchInlineSnapshot(`
1932+
"{
1933+
\\"someKey1\\": \\"someKey1-value\\",
1934+
\\"key2\\": \\"key2-value\\"
1935+
}
1936+
1937+
Success!"
1938+
`);
1939+
expect(std.warn).toMatchInlineSnapshot(`
1940+
"▲ [WARNING] 🚧 \`wrangler kv bulk get\` is an open-beta command. Please report any issues to https://github.com/cloudflare/workers-sdk/issues/new/choose
1941+
1942+
"
1943+
`);
1944+
expect(std.err).toMatchInlineSnapshot(`""`);
1945+
});
1946+
1947+
it("should get the keys parsed from a file ({ name })", async () => {
1948+
const keys = [{ name: "someKey1" }, { name: "ns:someKey2" }];
1949+
writeFileSync("./keys.json", JSON.stringify(keys));
1950+
const requests = mockGetRequest(
1951+
"some-namespace-id",
1952+
keys.map((k) => k.name)
1953+
);
1954+
await runWrangler(
1955+
`kv bulk get --remote --namespace-id some-namespace-id keys.json`
1956+
);
1957+
expect(requests.count).toEqual(1);
1958+
expect(std.out).toMatchInlineSnapshot(`
1959+
"{
1960+
\\"someKey1\\": \\"someKey1-value\\",
1961+
\\"ns:someKey2\\": \\"ns:someKey2-value\\"
1962+
}
1963+
1964+
Success!"
1965+
`);
1966+
expect(std.warn).toMatchInlineSnapshot(`
1967+
"▲ [WARNING] 🚧 \`wrangler kv bulk get\` is an open-beta command. Please report any issues to https://github.com/cloudflare/workers-sdk/issues/new/choose
1968+
1969+
"
1970+
`);
1971+
expect(std.err).toMatchInlineSnapshot(`""`);
1972+
});
1973+
1974+
it("should error if the file is not a JSON array", async () => {
1975+
const keys = 12354;
1976+
writeFileSync("./keys.json", JSON.stringify(keys));
1977+
await expect(
1978+
runWrangler(
1979+
`kv bulk get --remote --namespace-id some-namespace-id keys.json`
1980+
)
1981+
).rejects.toThrowErrorMatchingInlineSnapshot(`
1982+
[Error: Unexpected JSON input from "keys.json".
1983+
Expected an array of strings but got:
1984+
12354]
1985+
`);
1986+
expect(std.out).toMatchInlineSnapshot(`""`);
1987+
expect(std.warn).toMatchInlineSnapshot(`
1988+
"▲ [WARNING] 🚧 \`wrangler kv bulk get\` is an open-beta command. Please report any issues to https://github.com/cloudflare/workers-sdk/issues/new/choose
1989+
1990+
"
1991+
`);
1992+
});
1993+
1994+
it("should error if the file contains non-string items", async () => {
1995+
const keys = ["good", 12354, { key: "someKey" }, null];
1996+
writeFileSync("./keys.json", JSON.stringify(keys));
1997+
await expect(
1998+
runWrangler(
1999+
`kv bulk get --remote --namespace-id some-namespace-id keys.json`
2000+
)
2001+
).rejects.toThrowErrorMatchingInlineSnapshot(`
2002+
[Error: Unexpected JSON input from "keys.json".
2003+
Expected an array of strings or objects with a "name" key.
2004+
The item at index 1 is type: "number" - 12354
2005+
The item at index 2 is type: "object" - {"key":"someKey"}
2006+
The item at index 3 is type: "object" - null]
2007+
`);
2008+
expect(std.out).toMatchInlineSnapshot(`""`);
2009+
expect(std.warn).toMatchInlineSnapshot(`
2010+
"▲ [WARNING] 🚧 \`wrangler kv bulk get\` is an open-beta command. Please report any issues to https://github.com/cloudflare/workers-sdk/issues/new/choose
2011+
2012+
"
2013+
`);
2014+
});
2015+
});
18802016
});
18812017
});
18822018

packages/wrangler/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import { hyperdrive } from "./hyperdrive/index";
4141
import { initHandler, initOptions } from "./init";
4242
import {
4343
kvBulkDeleteCommand,
44+
kvBulkGetCommand,
4445
kvBulkNamespace,
4546
kvBulkPutCommand,
4647
kvKeyDeleteCommand,
@@ -545,6 +546,7 @@ export function createCLIParser(argv: string[]) {
545546
{ command: "wrangler kv key list", definition: kvKeyListCommand },
546547
{ command: "wrangler kv key get", definition: kvKeyGetCommand },
547548
{ command: "wrangler kv key delete", definition: kvKeyDeleteCommand },
549+
{ command: "wrangler kv bulk get", definition: kvBulkGetCommand },
548550
{ command: "wrangler kv bulk put", definition: kvBulkPutCommand },
549551
{ command: "wrangler kv bulk delete", definition: kvBulkDeleteCommand },
550552
]);

packages/wrangler/src/kv/helpers.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,32 @@ function logBulkProgress(
286286
);
287287
}
288288

289+
type BulkGetResponse = {
290+
values: {
291+
[key: string]: {
292+
value: string | object | null;
293+
metadata?: object;
294+
};
295+
};
296+
};
297+
298+
export async function getKVBulkKeyValue(
299+
accountId: string,
300+
namespaceId: string,
301+
keys: string[]
302+
) {
303+
const requestPayload = { keys };
304+
const result = await fetchResult<BulkGetResponse>(
305+
`/accounts/${accountId}/storage/kv/namespaces/${namespaceId}/bulk/get`,
306+
{
307+
method: "POST",
308+
body: JSON.stringify(requestPayload),
309+
headers: { "Content-Type": "application/json" },
310+
}
311+
);
312+
return result.values;
313+
}
314+
289315
export async function putKVBulkKeyValue(
290316
accountId: string,
291317
namespaceId: string,

0 commit comments

Comments
 (0)