Skip to content

Commit ff8603b

Browse files
jspspikemrbbot
andauthored
Add --local option to kv commands (#3684)
* Add --local to kv commands * Add tests for kv --local commands * Move `node:stream/consumers` types to `src` This where Wrangler stores its other `.d.ts` files. --------- Co-authored-by: bcoll <[email protected]>
1 parent 2d82ef7 commit ff8603b

File tree

6 files changed

+523
-40
lines changed

6 files changed

+523
-40
lines changed

.changeset/eight-rice-raise.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"wrangler": minor
3+
---
4+
5+
Added --local option for kv commands to interact with local persisted kv entries

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

+282-8
Original file line numberDiff line numberDiff line change
@@ -568,7 +568,9 @@ describe("wrangler", () => {
568568
--ttl Time for which the entries should be visible [number]
569569
--expiration Time since the UNIX epoch after which the entry expires [number]
570570
--metadata Arbitrary JSON that is associated with a key [string]
571-
--path Read value from the file at a given path [string]"
571+
--path Read value from the file at a given path [string]
572+
--local Interact with local storage [boolean]
573+
--persist-to Directory for local persistance [string]"
572574
`);
573575
expect(std.err).toMatchInlineSnapshot(`
574576
"X [ERROR] Not enough non-option arguments: got 0, need at least 1
@@ -608,7 +610,9 @@ describe("wrangler", () => {
608610
--ttl Time for which the entries should be visible [number]
609611
--expiration Time since the UNIX epoch after which the entry expires [number]
610612
--metadata Arbitrary JSON that is associated with a key [string]
611-
--path Read value from the file at a given path [string]"
613+
--path Read value from the file at a given path [string]
614+
--local Interact with local storage [boolean]
615+
--persist-to Directory for local persistance [string]"
612616
`);
613617
expect(std.err).toMatchInlineSnapshot(`
614618
"X [ERROR] Exactly one of the arguments binding and namespace-id is required
@@ -648,7 +652,9 @@ describe("wrangler", () => {
648652
--ttl Time for which the entries should be visible [number]
649653
--expiration Time since the UNIX epoch after which the entry expires [number]
650654
--metadata Arbitrary JSON that is associated with a key [string]
651-
--path Read value from the file at a given path [string]"
655+
--path Read value from the file at a given path [string]
656+
--local Interact with local storage [boolean]
657+
--persist-to Directory for local persistance [string]"
652658
`);
653659
expect(std.err).toMatchInlineSnapshot(`
654660
"X [ERROR] Arguments binding and namespace-id are mutually exclusive
@@ -688,7 +694,9 @@ describe("wrangler", () => {
688694
--ttl Time for which the entries should be visible [number]
689695
--expiration Time since the UNIX epoch after which the entry expires [number]
690696
--metadata Arbitrary JSON that is associated with a key [string]
691-
--path Read value from the file at a given path [string]"
697+
--path Read value from the file at a given path [string]
698+
--local Interact with local storage [boolean]
699+
--persist-to Directory for local persistance [string]"
692700
`);
693701
expect(std.err).toMatchInlineSnapshot(`
694702
"X [ERROR] Exactly one of the arguments value and path is required
@@ -728,7 +736,9 @@ describe("wrangler", () => {
728736
--ttl Time for which the entries should be visible [number]
729737
--expiration Time since the UNIX epoch after which the entry expires [number]
730738
--metadata Arbitrary JSON that is associated with a key [string]
731-
--path Read value from the file at a given path [string]"
739+
--path Read value from the file at a given path [string]
740+
--local Interact with local storage [boolean]
741+
--persist-to Directory for local persistance [string]"
732742
`);
733743
expect(std.err).toMatchInlineSnapshot(`
734744
"X [ERROR] Arguments value and path are mutually exclusive
@@ -1102,7 +1112,9 @@ describe("wrangler", () => {
11021112
--binding The name of the namespace to get from [string]
11031113
--namespace-id The id of the namespace to get from [string]
11041114
--preview Interact with a preview namespace [boolean] [default: false]
1105-
--text Decode the returned value as a utf8 string [boolean] [default: false]"
1115+
--text Decode the returned value as a utf8 string [boolean] [default: false]
1116+
--local Interact with local storage [boolean]
1117+
--persist-to Directory for local persistance [string]"
11061118
`);
11071119
expect(std.err).toMatchInlineSnapshot(`
11081120
"X [ERROR] Not enough non-option arguments: got 0, need at least 1
@@ -1137,7 +1149,9 @@ describe("wrangler", () => {
11371149
--binding The name of the namespace to get from [string]
11381150
--namespace-id The id of the namespace to get from [string]
11391151
--preview Interact with a preview namespace [boolean] [default: false]
1140-
--text Decode the returned value as a utf8 string [boolean] [default: false]"
1152+
--text Decode the returned value as a utf8 string [boolean] [default: false]
1153+
--local Interact with local storage [boolean]
1154+
--persist-to Directory for local persistance [string]"
11411155
`);
11421156
expect(std.err).toMatchInlineSnapshot(`
11431157
"X [ERROR] Exactly one of the arguments binding and namespace-id is required
@@ -1173,7 +1187,9 @@ describe("wrangler", () => {
11731187
--binding The name of the namespace to get from [string]
11741188
--namespace-id The id of the namespace to get from [string]
11751189
--preview Interact with a preview namespace [boolean] [default: false]
1176-
--text Decode the returned value as a utf8 string [boolean] [default: false]"
1190+
--text Decode the returned value as a utf8 string [boolean] [default: false]
1191+
--local Interact with local storage [boolean]
1192+
--persist-to Directory for local persistance [string]"
11771193
`);
11781194
expect(std.err).toMatchInlineSnapshot(`
11791195
"X [ERROR] Arguments binding and namespace-id are mutually exclusive
@@ -1689,6 +1705,264 @@ describe("wrangler", () => {
16891705
});
16901706
});
16911707
});
1708+
describe("local", () => {
1709+
it("should put local kv storage", async () => {
1710+
await runWrangler(
1711+
`kv:key get val --namespace-id some-namespace-id --local --text`
1712+
);
1713+
expect(std.out).toMatchInlineSnapshot(`"Value not found"`);
1714+
1715+
await runWrangler(
1716+
`kv:key put val value --namespace-id some-namespace-id --local`
1717+
);
1718+
expect(std.out).toMatchInlineSnapshot(`
1719+
"Value not found
1720+
Writing the value \\"value\\" to key \\"val\\" on namespace some-namespace-id."
1721+
`);
1722+
1723+
await runWrangler(
1724+
`kv:key get val --namespace-id some-namespace-id --local --text`
1725+
);
1726+
expect(std.out).toMatchInlineSnapshot(`
1727+
"Value not found
1728+
Writing the value \\"value\\" to key \\"val\\" on namespace some-namespace-id.
1729+
value"
1730+
`);
1731+
});
1732+
1733+
it("should list local kv storage", async () => {
1734+
await runWrangler(`kv:key list --namespace-id some-namespace-id --local`);
1735+
expect(std.out).toMatchInlineSnapshot(`
1736+
"{
1737+
\\"keys\\": [],
1738+
\\"list_complete\\": true
1739+
}"
1740+
`);
1741+
1742+
await runWrangler(
1743+
`kv:key put val value --namespace-id some-namespace-id --local`
1744+
);
1745+
1746+
await runWrangler(`kv:key list --namespace-id some-namespace-id --local`);
1747+
expect(std.out).toMatchInlineSnapshot(`
1748+
"{
1749+
\\"keys\\": [],
1750+
\\"list_complete\\": true
1751+
}
1752+
Writing the value \\"value\\" to key \\"val\\" on namespace some-namespace-id.
1753+
{
1754+
\\"keys\\": [
1755+
{
1756+
\\"name\\": \\"val\\"
1757+
}
1758+
],
1759+
\\"list_complete\\": true
1760+
}"
1761+
`);
1762+
});
1763+
1764+
it("should delete local kv storage", async () => {
1765+
await runWrangler(
1766+
`kv:key put val value --namespace-id some-namespace-id --local`
1767+
);
1768+
await runWrangler(
1769+
`kv:key get val --namespace-id some-namespace-id --local --text`
1770+
);
1771+
expect(std.out).toMatchInlineSnapshot(`
1772+
"Writing the value \\"value\\" to key \\"val\\" on namespace some-namespace-id.
1773+
value"
1774+
`);
1775+
await runWrangler(
1776+
`kv:key delete val --namespace-id some-namespace-id --local`
1777+
);
1778+
expect(std.out).toMatchInlineSnapshot(`
1779+
"Writing the value \\"value\\" to key \\"val\\" on namespace some-namespace-id.
1780+
value
1781+
Deleting the key \\"val\\" on namespace some-namespace-id."
1782+
`);
1783+
1784+
await runWrangler(
1785+
`kv:key get val --namespace-id some-namespace-id --local --text`
1786+
);
1787+
expect(std.out).toMatchInlineSnapshot(`
1788+
"Writing the value \\"value\\" to key \\"val\\" on namespace some-namespace-id.
1789+
value
1790+
Deleting the key \\"val\\" on namespace some-namespace-id.
1791+
Value not found"
1792+
`);
1793+
});
1794+
1795+
it("should put local bulk kv storage", async () => {
1796+
await runWrangler(`kv:key list --namespace-id bulk-namespace-id --local`);
1797+
expect(std.out).toMatchInlineSnapshot(`
1798+
"{
1799+
\\"keys\\": [],
1800+
\\"list_complete\\": true
1801+
}"
1802+
`);
1803+
1804+
const keyValues = [
1805+
{
1806+
key: "hello",
1807+
value: "world",
1808+
},
1809+
{
1810+
key: "test",
1811+
value: "value",
1812+
},
1813+
];
1814+
writeFileSync("./keys.json", JSON.stringify(keyValues));
1815+
await runWrangler(
1816+
`kv:bulk put keys.json --namespace-id bulk-namespace-id --local`
1817+
);
1818+
expect(std.out).toMatchInlineSnapshot(`
1819+
"{
1820+
\\"keys\\": [],
1821+
\\"list_complete\\": true
1822+
}
1823+
Success!"
1824+
`);
1825+
1826+
await runWrangler(
1827+
`kv:key get test --namespace-id bulk-namespace-id --local --text`
1828+
);
1829+
expect(std.out).toMatchInlineSnapshot(`
1830+
"{
1831+
\\"keys\\": [],
1832+
\\"list_complete\\": true
1833+
}
1834+
Success!
1835+
value"
1836+
`);
1837+
1838+
await runWrangler(`kv:key list --namespace-id bulk-namespace-id --local`);
1839+
expect(std.out).toMatchInlineSnapshot(`
1840+
"{
1841+
\\"keys\\": [],
1842+
\\"list_complete\\": true
1843+
}
1844+
Success!
1845+
value
1846+
{
1847+
\\"keys\\": [
1848+
{
1849+
\\"name\\": \\"hello\\"
1850+
},
1851+
{
1852+
\\"name\\": \\"test\\"
1853+
}
1854+
],
1855+
\\"list_complete\\": true
1856+
}"
1857+
`);
1858+
});
1859+
1860+
it("should delete local bulk kv storage", async () => {
1861+
const keyValues = [
1862+
{
1863+
key: "hello",
1864+
value: "world",
1865+
},
1866+
{
1867+
key: "test",
1868+
value: "value",
1869+
},
1870+
];
1871+
writeFileSync("./keys.json", JSON.stringify(keyValues));
1872+
await runWrangler(
1873+
`kv:bulk put keys.json --namespace-id bulk-namespace-id --local`
1874+
);
1875+
await runWrangler(`kv:key list --namespace-id bulk-namespace-id --local`);
1876+
expect(std.out).toMatchInlineSnapshot(`
1877+
"Success!
1878+
{
1879+
\\"keys\\": [
1880+
{
1881+
\\"name\\": \\"hello\\"
1882+
},
1883+
{
1884+
\\"name\\": \\"test\\"
1885+
}
1886+
],
1887+
\\"list_complete\\": true
1888+
}"
1889+
`);
1890+
const keys = ["hello", "test"];
1891+
writeFileSync("./keys.json", JSON.stringify(keys));
1892+
await runWrangler(
1893+
`kv:bulk delete keys.json --namespace-id bulk-namespace-id --local --force`
1894+
);
1895+
expect(std.out).toMatchInlineSnapshot(`
1896+
"Success!
1897+
{
1898+
\\"keys\\": [
1899+
{
1900+
\\"name\\": \\"hello\\"
1901+
},
1902+
{
1903+
\\"name\\": \\"test\\"
1904+
}
1905+
],
1906+
\\"list_complete\\": true
1907+
}
1908+
Success!"
1909+
`);
1910+
1911+
await runWrangler(`kv:key list --namespace-id bulk-namespace-id --local`);
1912+
expect(std.out).toMatchInlineSnapshot(`
1913+
"Success!
1914+
{
1915+
\\"keys\\": [
1916+
{
1917+
\\"name\\": \\"hello\\"
1918+
},
1919+
{
1920+
\\"name\\": \\"test\\"
1921+
}
1922+
],
1923+
\\"list_complete\\": true
1924+
}
1925+
Success!
1926+
{
1927+
\\"keys\\": [],
1928+
\\"list_complete\\": true
1929+
}"
1930+
`);
1931+
});
1932+
1933+
it("should follow persist-to for local kv storage", async () => {
1934+
await runWrangler(
1935+
`kv:key put val value --namespace-id some-namespace-id --local`
1936+
);
1937+
1938+
await runWrangler(
1939+
`kv:key put val persistValue --namespace-id some-namespace-id --local --persist-to ./persistdir`
1940+
);
1941+
expect(std.out).toMatchInlineSnapshot(`
1942+
"Writing the value \\"value\\" to key \\"val\\" on namespace some-namespace-id.
1943+
Writing the value \\"persistValue\\" to key \\"val\\" on namespace some-namespace-id."
1944+
`);
1945+
1946+
await runWrangler(
1947+
`kv:key get val --namespace-id some-namespace-id --local --text`
1948+
);
1949+
expect(std.out).toMatchInlineSnapshot(`
1950+
"Writing the value \\"value\\" to key \\"val\\" on namespace some-namespace-id.
1951+
Writing the value \\"persistValue\\" to key \\"val\\" on namespace some-namespace-id.
1952+
value"
1953+
`);
1954+
1955+
await runWrangler(
1956+
`kv:key get val --namespace-id some-namespace-id --local --text --persist-to ./persistdir`
1957+
);
1958+
expect(std.out).toMatchInlineSnapshot(`
1959+
"Writing the value \\"value\\" to key \\"val\\" on namespace some-namespace-id.
1960+
Writing the value \\"persistValue\\" to key \\"val\\" on namespace some-namespace-id.
1961+
value
1962+
persistValue"
1963+
`);
1964+
});
1965+
});
16921966
});
16931967

16941968
function writeWranglerConfig() {

packages/wrangler/src/kv/helpers.ts

+21
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
import { URLSearchParams } from "node:url";
2+
import path from "path";
3+
import {
4+
KVGateway,
5+
NoOpLog,
6+
createFileStorage,
7+
sanitisePath,
8+
defaultTimers,
9+
} from "miniflare";
210
import { FormData } from "undici";
311
import { fetchListResult, fetchResult, fetchKVGetValue } from "../cfetch";
12+
import { getLocalPersistencePath } from "../dev/get-local-persistence-path";
413
import { logger } from "../logger";
514
import type { Config } from "../config";
615

@@ -431,3 +440,15 @@ export function isValidKVNamespaceBinding(
431440
typeof binding === "string" && /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(binding)
432441
);
433442
}
443+
444+
export function localGateway(
445+
persistTo: string | undefined,
446+
configPath: string | undefined,
447+
namespaceId: string
448+
): KVGateway {
449+
const persist = getLocalPersistencePath(persistTo, configPath);
450+
const sanitisedNamespace = sanitisePath(namespaceId);
451+
const persistPath = path.join(persist, "v3/kv", sanitisedNamespace);
452+
const storage = createFileStorage(persistPath);
453+
return new KVGateway(new NoOpLog(), storage, defaultTimers);
454+
}

0 commit comments

Comments
 (0)