Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/wise-pens-walk.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"wrangler": minor
---

Accept a JSON file of the format `{ name: string }[]` in `wrangler kv bulk delete`, as well as the current `string[]` format.
70 changes: 69 additions & 1 deletion packages/wrangler/src/__tests__/kv.local.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ describe("wrangler", () => {
`);
});

it("should delete local bulk kv storage", async () => {
it("should delete local bulk kv storage (string)", async () => {
const keyValues = [
{
key: "hello",
Expand Down Expand Up @@ -268,6 +268,74 @@ describe("wrangler", () => {
`);
});

it("should delete local bulk kv storage ({ name })", async () => {
const keyValues = [
{
key: "hello",
value: "world",
},
{
key: "test",
value: "value",
},
];
writeFileSync("./keys.json", JSON.stringify(keyValues));
await runWrangler(
`kv bulk put keys.json --namespace-id bulk-namespace-id --local`
);
await runWrangler(`kv key list --namespace-id bulk-namespace-id --local`);
expect(std.out).toMatchInlineSnapshot(`
"Success!
[
{
\\"name\\": \\"hello\\"
},
{
\\"name\\": \\"test\\"
}
]"
`);
const keys = [
{
name: "hello",
},
{
name: "test",
},
];
writeFileSync("./keys.json", JSON.stringify(keys));
await runWrangler(
`kv bulk delete keys.json --namespace-id bulk-namespace-id --local --force`
);
expect(std.out).toMatchInlineSnapshot(`
"Success!
[
{
\\"name\\": \\"hello\\"
},
{
\\"name\\": \\"test\\"
}
]
Success!"
`);

await runWrangler(`kv key list --namespace-id bulk-namespace-id --local`);
expect(std.out).toMatchInlineSnapshot(`
"Success!
[
{
\\"name\\": \\"hello\\"
},
{
\\"name\\": \\"test\\"
}
]
Success!
[]"
`);
});

it("should follow persist-to for local kv storage", async () => {
await runWrangler(
`kv:key put val value --namespace-id some-namespace-id --local`
Expand Down
24 changes: 22 additions & 2 deletions packages/wrangler/src/__tests__/kv.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1629,7 +1629,7 @@ describe("wrangler", () => {
return requests;
}

it("should delete the keys parsed from a file", async () => {
it("should delete the keys parsed from a file (string)", async () => {
const keys = ["someKey1", "ns:someKey2"];
writeFileSync("./keys.json", JSON.stringify(keys));
mockConfirm({
Expand All @@ -1646,6 +1646,26 @@ describe("wrangler", () => {
expect(std.err).toMatchInlineSnapshot(`""`);
});

it("should delete the keys parsed from a file ({ name })", async () => {
const keys = [{ name: "someKey1" }, { name: "ns:someKey2" }];
writeFileSync("./keys.json", JSON.stringify(keys));
mockConfirm({
text: `Are you sure you want to delete all the keys read from "keys.json" from kv-namespace with id "some-namespace-id"?`,
result: true,
});
const requests = mockDeleteRequest(
"some-namespace-id",
keys.map((k) => k.name)
);
await runWrangler(
`kv bulk delete --namespace-id some-namespace-id keys.json`
);
expect(requests.count).toEqual(1);
expect(std.out).toMatchInlineSnapshot(`"Success!"`);
expect(std.warn).toMatchInlineSnapshot(`""`);
expect(std.err).toMatchInlineSnapshot(`""`);
});

it("should delete the keys in batches of 5000 parsed from a file", async () => {
const keys = new Array(12000).fill("some-key");
writeFileSync("./keys.json", JSON.stringify(keys));
Expand Down Expand Up @@ -1754,7 +1774,7 @@ describe("wrangler", () => {
)
).rejects.toThrowErrorMatchingInlineSnapshot(`
[Error: Unexpected JSON input from "keys.json".
Expected an array of strings.
Expected an array of strings or objects with a "name" key.
The item at index 1 is type: "number" - 12354
The item at index 2 is type: "object" - {"key":"someKey"}
The item at index 3 is type: "object" - null]
Expand Down
23 changes: 15 additions & 8 deletions packages/wrangler/src/kv/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -815,7 +815,10 @@ export const kvBulkDeleteCommand = createCommand({
}
}

const content = parseJSON(readFileSync(filename), filename) as string[];
const content = parseJSON(readFileSync(filename), filename) as (
| string
| { name: string }
)[];

if (!Array.isArray(content)) {
throw new UserError(
Expand All @@ -825,21 +828,25 @@ export const kvBulkDeleteCommand = createCommand({
}

const errors: string[] = [];
for (let i = 0; i < content.length; i++) {
const key = content[i];

const keysToDelete: string[] = [];
for (const [index, item] of content.entries()) {
const key = typeof item !== "string" ? item?.name : item;

if (typeof key !== "string") {
errors.push(
`The item at index ${i} is type: "${typeof key}" - ${JSON.stringify(
key
`The item at index ${index} is type: "${typeof item}" - ${JSON.stringify(
item
)}`
);
}
keysToDelete.push(key);
}

if (errors.length > 0) {
throw new UserError(
`Unexpected JSON input from "${filename}".\n` +
`Expected an array of strings.\n` +
`Expected an array of strings or objects with a "name" key.\n` +
errors.join("\n")
);
}
Expand All @@ -851,7 +858,7 @@ export const kvBulkDeleteCommand = createCommand({
config.configPath,
namespaceId,
async (namespace) => {
for (const key of content) {
for (const key of keysToDelete) {
await namespace.delete(key);
}
}
Expand All @@ -861,7 +868,7 @@ export const kvBulkDeleteCommand = createCommand({
} else {
const accountId = await requireAuth(config);

await deleteKVBulkKeyValue(accountId, namespaceId, content);
await deleteKVBulkKeyValue(accountId, namespaceId, keysToDelete);
metricEvent = "delete kv key-values (bulk)";
}

Expand Down
Loading