wrangler 0.0.0-e3cab74 → 0.0.0-e3ce82b

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/Cloudflare_CA.pem +18 -0
  2. package/README.md +27 -14
  3. package/bin/wrangler.js +49 -5
  4. package/miniflare-dist/index.mjs +4886 -0
  5. package/package.json +43 -28
  6. package/pages/functions/buildPlugin.ts +105 -0
  7. package/pages/functions/buildWorker.ts +93 -4
  8. package/pages/functions/filepath-routing.test.ts +136 -25
  9. package/pages/functions/filepath-routing.ts +14 -3
  10. package/pages/functions/routes.ts +6 -1
  11. package/pages/functions/template-plugin.ts +147 -0
  12. package/pages/functions/template-worker.ts +21 -8
  13. package/src/__tests__/config-cache.test.ts +36 -0
  14. package/src/__tests__/configuration.test.ts +1713 -411
  15. package/src/__tests__/dev.test.tsx +954 -84
  16. package/src/__tests__/guess-worker-format.test.ts +37 -18
  17. package/src/__tests__/helpers/mock-account-id.ts +22 -4
  18. package/src/__tests__/helpers/mock-cfetch.ts +8 -4
  19. package/src/__tests__/helpers/mock-console.ts +35 -10
  20. package/src/__tests__/helpers/mock-dialogs.ts +67 -2
  21. package/src/__tests__/helpers/mock-http-server.ts +46 -0
  22. package/src/__tests__/helpers/mock-istty.ts +27 -0
  23. package/src/__tests__/helpers/mock-kv.ts +9 -16
  24. package/src/__tests__/helpers/mock-oauth-flow.ts +255 -0
  25. package/src/__tests__/helpers/mock-stdin.ts +103 -0
  26. package/src/__tests__/helpers/run-in-tmp.ts +10 -0
  27. package/src/__tests__/helpers/write-worker-source.ts +31 -0
  28. package/src/__tests__/https-options.test.ts +130 -0
  29. package/src/__tests__/index.test.ts +138 -747
  30. package/src/__tests__/init.test.ts +1637 -0
  31. package/src/__tests__/jest.setup.ts +28 -8
  32. package/src/__tests__/kv.test.ts +598 -258
  33. package/src/__tests__/logger.test.ts +115 -0
  34. package/src/__tests__/package-manager.test.ts +154 -7
  35. package/src/__tests__/pages.test.ts +754 -0
  36. package/src/__tests__/parse.test.ts +8 -0
  37. package/src/__tests__/publish.test.ts +4374 -1025
  38. package/src/__tests__/r2.test.ts +69 -34
  39. package/src/__tests__/route.test.ts +45 -0
  40. package/src/__tests__/secret.test.ts +228 -73
  41. package/src/__tests__/tail.test.ts +146 -20
  42. package/src/__tests__/user.test.ts +131 -0
  43. package/src/__tests__/whoami.test.tsx +60 -7
  44. package/src/abort.d.ts +3 -0
  45. package/src/bundle.ts +75 -11
  46. package/src/cfetch/index.ts +21 -4
  47. package/src/cfetch/internal.ts +29 -20
  48. package/src/cli.ts +5 -11
  49. package/src/config/README.md +7 -7
  50. package/src/config/config.ts +69 -100
  51. package/src/config/environment.ts +119 -4
  52. package/src/config/index.ts +177 -4
  53. package/src/config/validation-helpers.ts +237 -23
  54. package/src/config/validation.ts +722 -191
  55. package/src/config-cache.ts +45 -0
  56. package/src/{api/preview.ts → create-worker-preview.ts} +65 -27
  57. package/src/{api/form_data.ts → create-worker-upload-form.ts} +55 -18
  58. package/src/dev/dev-vars.ts +36 -0
  59. package/src/dev/dev.tsx +401 -0
  60. package/src/dev/local.tsx +322 -0
  61. package/src/dev/remote.tsx +237 -0
  62. package/src/dev/use-esbuild.ts +118 -0
  63. package/src/dialogs.tsx +64 -6
  64. package/src/durable.ts +102 -0
  65. package/src/entry.ts +279 -14
  66. package/src/environment-variables.ts +3 -1
  67. package/src/errors.ts +11 -0
  68. package/src/https-options.ts +123 -0
  69. package/src/index.tsx +1386 -867
  70. package/src/inspect.ts +64 -11
  71. package/src/intl-polyfill.d.ts +139 -0
  72. package/src/{kv.tsx → kv.ts} +195 -62
  73. package/src/logger.ts +66 -0
  74. package/src/miniflare-cli/README.md +30 -0
  75. package/src/miniflare-cli/enum-keys.ts +17 -0
  76. package/src/miniflare-cli/index.ts +41 -0
  77. package/src/miniflare-cli/request-context.ts +40 -0
  78. package/src/module-collection.ts +94 -6
  79. package/src/open-in-browser.ts +9 -3
  80. package/src/package-manager.ts +59 -11
  81. package/src/pages.tsx +1123 -75
  82. package/src/parse.ts +26 -6
  83. package/src/proxy.ts +159 -57
  84. package/src/publish.ts +581 -228
  85. package/src/selfsigned.d.ts +29 -0
  86. package/src/sites.tsx +130 -45
  87. package/src/tail/index.ts +2 -2
  88. package/src/tail/printing.ts +13 -8
  89. package/src/update-check.ts +19 -0
  90. package/src/user.tsx +275 -110
  91. package/src/whoami.tsx +5 -3
  92. package/src/{api/worker.ts → worker.ts} +24 -29
  93. package/src/zones.ts +73 -0
  94. package/templates/gitignore +171 -0
  95. package/templates/new-worker-scheduled.js +17 -0
  96. package/templates/new-worker-scheduled.ts +32 -0
  97. package/templates/new-worker.ts +16 -1
  98. package/templates/static-asset-facade.js +1 -5
  99. package/wrangler-dist/cli.js +104803 -118375
  100. package/src/__tests__/helpers/mock-user.ts +0 -27
  101. package/src/__tests__/logout.test.ts +0 -50
  102. package/src/__tests__/sentry.test.ts +0 -146
  103. package/src/dev.tsx +0 -961
  104. package/src/guess-worker-format.ts +0 -68
  105. package/src/reporting.ts +0 -115
  106. package/vendor/@cloudflare/kv-asset-handler/CHANGELOG.md +0 -332
  107. package/vendor/@cloudflare/kv-asset-handler/LICENSE_APACHE +0 -176
  108. package/vendor/@cloudflare/kv-asset-handler/LICENSE_MIT +0 -25
  109. package/vendor/@cloudflare/kv-asset-handler/README.md +0 -245
  110. package/vendor/@cloudflare/kv-asset-handler/dist/index.d.ts +0 -32
  111. package/vendor/@cloudflare/kv-asset-handler/dist/index.js +0 -354
  112. package/vendor/@cloudflare/kv-asset-handler/dist/mocks.d.ts +0 -13
  113. package/vendor/@cloudflare/kv-asset-handler/dist/mocks.js +0 -148
  114. package/vendor/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV.d.ts +0 -1
  115. package/vendor/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV.js +0 -436
  116. package/vendor/@cloudflare/kv-asset-handler/dist/test/mapRequestToAsset.d.ts +0 -1
  117. package/vendor/@cloudflare/kv-asset-handler/dist/test/mapRequestToAsset.js +0 -40
  118. package/vendor/@cloudflare/kv-asset-handler/dist/test/serveSinglePageApp.d.ts +0 -1
  119. package/vendor/@cloudflare/kv-asset-handler/dist/test/serveSinglePageApp.js +0 -42
  120. package/vendor/@cloudflare/kv-asset-handler/dist/types.d.ts +0 -26
  121. package/vendor/@cloudflare/kv-asset-handler/dist/types.js +0 -31
  122. package/vendor/@cloudflare/kv-asset-handler/package.json +0 -52
  123. package/vendor/@cloudflare/kv-asset-handler/src/index.ts +0 -296
  124. package/vendor/@cloudflare/kv-asset-handler/src/mocks.ts +0 -136
  125. package/vendor/@cloudflare/kv-asset-handler/src/test/getAssetFromKV.ts +0 -464
  126. package/vendor/@cloudflare/kv-asset-handler/src/test/mapRequestToAsset.ts +0 -33
  127. package/vendor/@cloudflare/kv-asset-handler/src/test/serveSinglePageApp.ts +0 -42
  128. package/vendor/@cloudflare/kv-asset-handler/src/types.ts +0 -39
  129. package/vendor/wrangler-mime/CHANGELOG.md +0 -289
  130. package/vendor/wrangler-mime/LICENSE +0 -21
  131. package/vendor/wrangler-mime/Mime.js +0 -97
  132. package/vendor/wrangler-mime/README.md +0 -187
  133. package/vendor/wrangler-mime/cli.js +0 -46
  134. package/vendor/wrangler-mime/index.js +0 -4
  135. package/vendor/wrangler-mime/lite.js +0 -4
  136. package/vendor/wrangler-mime/package.json +0 -52
  137. package/vendor/wrangler-mime/types/other.js +0 -1
  138. package/vendor/wrangler-mime/types/standard.js +0 -1
  139. package/wrangler-dist/cli.js.map +0 -7
package/src/inspect.ts CHANGED
@@ -1,10 +1,14 @@
1
1
  import assert from "node:assert";
2
2
  import { createServer } from "node:http";
3
+ import os from "node:os";
3
4
  import { URL } from "node:url";
4
5
 
6
+ import open from "open";
5
7
  import { useEffect, useRef, useState } from "react";
6
8
  import WebSocket, { WebSocketServer } from "ws";
7
9
  import { version } from "../package.json";
10
+ import { logger } from "./logger";
11
+ import { waitForPortToBeAvailable } from "./proxy";
8
12
  import type Protocol from "devtools-protocol";
9
13
  import type { IncomingMessage, Server, ServerResponse } from "node:http";
10
14
  import type { MessageEvent } from "ws";
@@ -41,7 +45,7 @@ interface InspectorProps {
41
45
  /**
42
46
  * The websocket URL exposed by Workers that the inspector should connect to.
43
47
  */
44
- inspectorUrl: undefined | string;
48
+ inspectorUrl: string | undefined;
45
49
  /**
46
50
  * Whether console statements and exceptions should be logged to the terminal.
47
51
  * (We don't log them in local mode because they're already getting
@@ -133,7 +137,7 @@ export default function useInspector(props: InspectorProps) {
133
137
  wsServer.on("connection", (ws: WebSocket) => {
134
138
  if (wsServer.clients.size > 1) {
135
139
  /** We only want to have one active Devtools instance at a time. */
136
- console.error(
140
+ logger.error(
137
141
  "Tried to open a new devtools window when a previous one was already open."
138
142
  );
139
143
  ws.close(1013, "Too many clients; only one can be connected at a time");
@@ -153,12 +157,26 @@ export default function useInspector(props: InspectorProps) {
153
157
  * of the component lifecycle. Convenient.
154
158
  */
155
159
  useEffect(() => {
156
- server.listen(props.port);
160
+ const abortController = new AbortController();
161
+ async function startInspectorProxy() {
162
+ await waitForPortToBeAvailable(props.port, {
163
+ retryPeriod: 200,
164
+ timeout: 2000,
165
+ abortSignal: abortController.signal,
166
+ });
167
+ server.listen(props.port);
168
+ }
169
+ startInspectorProxy().catch((err) => {
170
+ if ((err as { code: string }).code !== "ABORT_ERR") {
171
+ logger.error("Failed to start inspector:", err);
172
+ }
173
+ });
157
174
  return () => {
158
175
  server.close();
159
176
  // Also disconnect any open websockets/devtools connections
160
177
  wsServer.clients.forEach((ws) => ws.close());
161
178
  wsServer.close();
179
+ abortController.abort();
162
180
  };
163
181
  }, [props.port, server, wsServer]);
164
182
 
@@ -239,9 +257,7 @@ export default function useInspector(props: InspectorProps) {
239
257
  const evt = JSON.parse(event.data);
240
258
  if (evt.method === "Runtime.exceptionThrown") {
241
259
  const params = evt.params as Protocol.Runtime.ExceptionThrownEvent;
242
- console.error(
243
- "🚨", // cheesy, but it works
244
- // maybe we could use color here too.
260
+ logger.error(
245
261
  params.exceptionDetails.text,
246
262
  params.exceptionDetails.exception?.description ?? ""
247
263
  );
@@ -253,7 +269,7 @@ export default function useInspector(props: InspectorProps) {
253
269
  }
254
270
  } else {
255
271
  // We should never get here, but who know is 2022...
256
- console.error("unrecognised devtools event:", event);
272
+ logger.error("Unrecognised devtools event:", event);
257
273
  }
258
274
  });
259
275
  }
@@ -272,7 +288,7 @@ export default function useInspector(props: InspectorProps) {
272
288
  });
273
289
 
274
290
  ws.on("unexpected-response", () => {
275
- console.log("waiting for connection...");
291
+ logger.log("Waiting for connection...");
276
292
  /**
277
293
  * This usually means the worker is not "ready" yet
278
294
  * so we'll just retry the connection process
@@ -378,7 +394,7 @@ export default function useInspector(props: InspectorProps) {
378
394
  * happens on the first request. Maybe we should buffer
379
395
  * these messages too?
380
396
  */
381
- console.error(e);
397
+ logger.error(e);
382
398
  }
383
399
  }
384
400
  }
@@ -603,7 +619,44 @@ function logConsoleMessage(evt: Protocol.Runtime.ConsoleAPICalledEvent): void {
603
619
  break;
604
620
  }
605
621
  } else {
606
- console.warn(`Unsupported console method: ${method}`);
607
- console.log("console event:", evt);
622
+ logger.warn(`Unsupported console method: ${method}`);
623
+ logger.warn("console event:", evt);
608
624
  }
609
625
  }
626
+
627
+ /**
628
+ * Opens the chrome debugger
629
+ */
630
+ export const openInspector = async (inspectorPort: number) => {
631
+ const url = `https://built-devtools.pages.dev/js_app?experiments=true&v8only=true&ws=localhost:${inspectorPort}/ws`;
632
+ const errorMessage =
633
+ "Failed to open inspector.\nInspector depends on having a Chromium-based browser installed, maybe you need to install one?";
634
+
635
+ // see: https://github.com/sindresorhus/open/issues/177#issue-610016699
636
+ let braveBrowser: string;
637
+ switch (os.platform()) {
638
+ case "darwin":
639
+ case "win32":
640
+ braveBrowser = "Brave";
641
+ break;
642
+ default:
643
+ braveBrowser = "brave";
644
+ }
645
+
646
+ const childProcess = await open(url, {
647
+ app: [
648
+ {
649
+ name: open.apps.chrome,
650
+ },
651
+ {
652
+ name: braveBrowser,
653
+ },
654
+ {
655
+ name: open.apps.edge,
656
+ },
657
+ ],
658
+ });
659
+ childProcess.on("error", () => {
660
+ logger.warn(errorMessage);
661
+ });
662
+ };
@@ -0,0 +1,139 @@
1
+ /**
2
+ * TODO Remove once https://github.com/microsoft/TypeScript/pull/47254 lands
3
+ */
4
+ declare namespace Intl {
5
+ interface DateTimeFormatOptions {
6
+ formatMatcher?: "basic" | "best fit" | "best fit" | undefined;
7
+ dateStyle?: "full" | "long" | "medium" | "short" | undefined;
8
+ timeStyle?: "full" | "long" | "medium" | "short" | undefined;
9
+ dayPeriod?: "narrow" | "short" | "long" | undefined;
10
+ fractionalSecondDigits?: 0 | 1 | 2 | 3 | undefined;
11
+ }
12
+
13
+ interface ResolvedDateTimeFormatOptions {
14
+ formatMatcher?: "basic" | "best fit" | "best fit";
15
+ dateStyle?: "full" | "long" | "medium" | "short";
16
+ timeStyle?: "full" | "long" | "medium" | "short";
17
+ hourCycle?: "h11" | "h12" | "h23" | "h24";
18
+ dayPeriod?: "narrow" | "short" | "long";
19
+ fractionalSecondDigits?: 0 | 1 | 2 | 3;
20
+ }
21
+
22
+ interface NumberFormat {
23
+ formatRange(startDate: number | bigint, endDate: number | bigint): string;
24
+ formatRangeToParts(
25
+ startDate: number | bigint,
26
+ endDate: number | bigint
27
+ ): NumberFormatPart[];
28
+ }
29
+
30
+ /**
31
+ * The locale matching algorithm to use.
32
+ *
33
+ * [MDN](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Intl#Locale_negotiation).
34
+ */
35
+ type ListFormatLocaleMatcher = "lookup" | "best fit";
36
+
37
+ /**
38
+ * The format of output message.
39
+ *
40
+ * [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/ListFormat/ListFormat#parameters).
41
+ */
42
+ type ListFormatType = "conjunction" | "disjunction" | "unit";
43
+
44
+ /**
45
+ * The length of the formatted message.
46
+ *
47
+ * [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/ListFormat/ListFormat#parameters).
48
+ */
49
+ type ListFormatStyle = "long" | "short" | "narrow";
50
+
51
+ /**
52
+ * An object with some or all properties of the `Intl.ListFormat` constructor `options` parameter.
53
+ *
54
+ * [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/ListFormat/ListFormat#parameters).
55
+ */
56
+ interface ListFormatOptions {
57
+ /** The locale matching algorithm to use. For information about this option, see [Intl page](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Intl#Locale_negotiation). */
58
+ localeMatcher?: ListFormatLocaleMatcher;
59
+ /** The format of output message. */
60
+ type?: ListFormatType;
61
+ /** The length of the internationalized message. */
62
+ style?: ListFormatStyle;
63
+ }
64
+
65
+ interface ListFormat {
66
+ /**
67
+ * Returns a string with a language-specific representation of the list.
68
+ *
69
+ * @param list - An iterable object, such as an [Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array).
70
+ *
71
+ * @throws `TypeError` if `list` includes something other than the possible values.
72
+ *
73
+ * @returns {string} A language-specific formatted string representing the elements of the list.
74
+ *
75
+ * [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/ListFormat/format).
76
+ */
77
+ format(list: Iterable<string>): string;
78
+
79
+ /**
80
+ * Returns an Array of objects representing the different components that can be used to format a list of values in a locale-aware fashion.
81
+ *
82
+ * @param list - An iterable object, such as an [Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array), to be formatted according to a locale.
83
+ *
84
+ * @throws `TypeError` if `list` includes something other than the possible values.
85
+ *
86
+ * @returns {{ type: "element" | "literal", value: string; }[]} An Array of components which contains the formatted parts from the list.
87
+ *
88
+ * [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/ListFormat/formatToParts).
89
+ */
90
+ formatToParts(
91
+ list: Iterable<string>
92
+ ): { type: "element" | "literal"; value: string }[];
93
+ }
94
+
95
+ const ListFormat: {
96
+ prototype: ListFormat;
97
+
98
+ /**
99
+ * Creates [Intl.ListFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/ListFormat) objects that
100
+ * enable language-sensitive list formatting.
101
+ *
102
+ * @param locales - A string with a [BCP 47 language tag](http://tools.ietf.org/html/rfc5646), or an array of such strings.
103
+ * For the general form and interpretation of the `locales` argument,
104
+ * see the [`Intl` page](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl#Locale_identification_and_negotiation).
105
+ *
106
+ * @param options - An [object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/ListFormat/ListFormat#parameters)
107
+ * with some or all options of `ListFormatOptions`.
108
+ *
109
+ * @returns [Intl.ListFormatOptions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/ListFormat) object.
110
+ *
111
+ * [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/ListFormat).
112
+ */
113
+ new (
114
+ locales?: BCP47LanguageTag | BCP47LanguageTag[],
115
+ options?: ListFormatOptions
116
+ ): ListFormat;
117
+
118
+ /**
119
+ * Returns an array containing those of the provided locales that are
120
+ * supported in list formatting without having to fall back to the runtime's default locale.
121
+ *
122
+ * @param locales - A string with a [BCP 47 language tag](http://tools.ietf.org/html/rfc5646), or an array of such strings.
123
+ * For the general form and interpretation of the `locales` argument,
124
+ * see the [`Intl` page](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl#Locale_identification_and_negotiation).
125
+ *
126
+ * @param options - An [object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/ListFormat/supportedLocalesOf#parameters).
127
+ * with some or all possible options.
128
+ *
129
+ * @returns An array of strings representing a subset of the given locale tags that are supported in list
130
+ * formatting without having to fall back to the runtime's default locale.
131
+ *
132
+ * [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/ListFormat/supportedLocalesOf).
133
+ */
134
+ supportedLocalesOf(
135
+ locales: BCP47LanguageTag | BCP47LanguageTag[],
136
+ options?: Pick<ListFormatOptions, "localeMatcher">
137
+ ): BCP47LanguageTag[];
138
+ };
139
+ }
@@ -1,11 +1,17 @@
1
1
  import { URLSearchParams } from "node:url";
2
2
  import { fetchListResult, fetchResult, fetchKVGetValue } from "./cfetch";
3
- import type { Config, Environment } from "./config";
3
+ import { logger } from "./logger";
4
+ import type { Config } from "./config";
5
+
6
+ /** The largest number of kv items we can pass to the API in a single request. */
7
+ const API_MAX = 10000;
8
+ // The const below are halved from the API's true capacity to help avoid
9
+ // hammering it with large requests.
10
+ const BATCH_KEY_MAX = API_MAX / 2;
4
11
 
5
12
  type KvArgs = {
6
13
  binding?: string;
7
14
  "namespace-id"?: string;
8
- env?: string;
9
15
  preview?: boolean;
10
16
  };
11
17
 
@@ -14,7 +20,7 @@ type KvArgs = {
14
20
  *
15
21
  * @returns the generated id of the created namespace.
16
22
  */
17
- export async function createNamespace(
23
+ export async function createKVNamespace(
18
24
  accountId: string,
19
25
  title: string
20
26
  ): Promise<string> {
@@ -46,7 +52,7 @@ export interface KVNamespaceInfo {
46
52
  /**
47
53
  * Fetch a list of all the namespaces under the given `accountId`.
48
54
  */
49
- export async function listNamespaces(
55
+ export async function listKVNamespaces(
50
56
  accountId: string
51
57
  ): Promise<KVNamespaceInfo[]> {
52
58
  const pageSize = 100;
@@ -78,7 +84,7 @@ export interface NamespaceKeyInfo {
78
84
  metadata?: { [key: string]: unknown };
79
85
  }
80
86
 
81
- export async function listNamespaceKeys(
87
+ export async function listKVNamespaceKeys(
82
88
  accountId: string,
83
89
  namespaceId: string,
84
90
  prefix = ""
@@ -90,71 +96,217 @@ export async function listNamespaceKeys(
90
96
  );
91
97
  }
92
98
 
93
- export async function putKeyValue(
99
+ export async function deleteKVNamespace(
100
+ accountId: string,
101
+ namespaceId: string
102
+ ) {
103
+ return await fetchResult<{ id: string }>(
104
+ `/accounts/${accountId}/storage/kv/namespaces/${namespaceId}`,
105
+ { method: "DELETE" }
106
+ );
107
+ }
108
+
109
+ /**
110
+ * Information about a key-value pair, including its "metadata" fields.
111
+ */
112
+ export interface KeyValue {
113
+ key: string;
114
+ value: string;
115
+ expiration?: number;
116
+ expiration_ttl?: number;
117
+ metadata?: object;
118
+ base64?: boolean;
119
+ }
120
+
121
+ const KeyValueKeys = new Set([
122
+ "key",
123
+ "value",
124
+ "expiration",
125
+ "expiration_ttl",
126
+ "metadata",
127
+ "base64",
128
+ ]);
129
+
130
+ /**
131
+ * The object has the specified property.
132
+ */
133
+ function hasProperty<T extends object>(
134
+ obj: object,
135
+ property: keyof T
136
+ ): obj is T {
137
+ return property in obj;
138
+ }
139
+
140
+ /**
141
+ * The object has a required property of the specified type.
142
+ */
143
+ function hasTypedProperty<T extends object>(
144
+ obj: object,
145
+ property: keyof T,
146
+ type: string
147
+ ): obj is T {
148
+ return hasProperty(obj, property) && typeof obj[property] === type;
149
+ }
150
+
151
+ /**
152
+ * The object an optional property, of the specified type.
153
+ */
154
+ function hasOptionalTypedProperty<T extends object>(
155
+ obj: object,
156
+ property: keyof T,
157
+ type: string
158
+ ): obj is Omit<T, typeof property> | T {
159
+ return !hasProperty(obj, property) || typeof obj[property] === type;
160
+ }
161
+
162
+ /**
163
+ * Is the given object a valid `KeyValue` type?
164
+ */
165
+ export function isKVKeyValue(keyValue: unknown): keyValue is KeyValue {
166
+ if (
167
+ keyValue === null ||
168
+ typeof keyValue !== "object" ||
169
+ !hasTypedProperty(keyValue, "key", "string") ||
170
+ !hasTypedProperty(keyValue, "value", "string") ||
171
+ !hasOptionalTypedProperty(keyValue, "expiration", "number") ||
172
+ !hasOptionalTypedProperty(keyValue, "expiration_ttl", "number") ||
173
+ !hasOptionalTypedProperty(keyValue, "base64", "boolean") ||
174
+ !hasOptionalTypedProperty(keyValue, "metadata", "object")
175
+ ) {
176
+ return false;
177
+ }
178
+ return true;
179
+ }
180
+
181
+ /**
182
+ * Get all the properties on the `keyValue` that are not expected.
183
+ */
184
+ export function unexpectedKVKeyValueProps(keyValue: KeyValue): string[] {
185
+ const props = Object.keys(keyValue);
186
+ return props.filter((prop) => !KeyValueKeys.has(prop));
187
+ }
188
+
189
+ export async function putKVKeyValue(
94
190
  accountId: string,
95
191
  namespaceId: string,
96
- key: string,
97
- value: string,
98
- args?: { expiration?: number; expiration_ttl?: number }
192
+ keyValue: KeyValue
99
193
  ) {
100
194
  let searchParams: URLSearchParams | undefined;
101
- if (args) {
195
+ if (keyValue.expiration || keyValue.expiration_ttl) {
102
196
  searchParams = new URLSearchParams();
103
- if (args.expiration) {
104
- searchParams.set("expiration", `${args.expiration}`);
197
+ if (keyValue.expiration) {
198
+ searchParams.set("expiration", `${keyValue.expiration}`);
105
199
  }
106
- if (args.expiration_ttl) {
107
- searchParams.set("expiration_ttl", `${args.expiration_ttl}`);
200
+ if (keyValue.expiration_ttl) {
201
+ searchParams.set("expiration_ttl", `${keyValue.expiration_ttl}`);
108
202
  }
109
203
  }
110
204
  return await fetchResult(
111
- `/accounts/${accountId}/storage/kv/namespaces/${namespaceId}/values/${key}`,
112
- { method: "PUT", body: value },
205
+ `/accounts/${accountId}/storage/kv/namespaces/${namespaceId}/values/${encodeURIComponent(
206
+ keyValue.key
207
+ )}`,
208
+ { method: "PUT", body: keyValue.value },
113
209
  searchParams
114
210
  );
115
211
  }
116
212
 
117
- export async function getKeyValue(
213
+ export async function getKVKeyValue(
118
214
  accountId: string,
119
215
  namespaceId: string,
120
216
  key: string
121
217
  ): Promise<string> {
122
- return await fetchKVGetValue(accountId, namespaceId, key);
218
+ return await fetchKVGetValue(accountId, namespaceId, encodeURIComponent(key));
123
219
  }
124
220
 
125
- export async function putBulkKeyValue(
221
+ export async function deleteKVKeyValue(
126
222
  accountId: string,
127
223
  namespaceId: string,
128
- keyvalueStr: string
224
+ key: string
129
225
  ) {
130
226
  return await fetchResult(
131
- `/accounts/${accountId}/storage/kv/namespaces/${namespaceId}/bulk`,
132
- {
133
- method: "PUT",
134
- body: keyvalueStr,
135
- headers: { "Content-Type": "application/json" },
136
- }
227
+ `/accounts/${accountId}/storage/kv/namespaces/${namespaceId}/values/${encodeURIComponent(
228
+ key
229
+ )}`,
230
+ { method: "DELETE" }
137
231
  );
138
232
  }
139
233
 
140
- export async function deleteBulkKeyValue(
234
+ /**
235
+ * Formatter for converting e.g. 5328 --> 5,328
236
+ */
237
+ const formatNumber = new Intl.NumberFormat("en-US", {
238
+ notation: "standard",
239
+ }).format;
240
+
241
+ /**
242
+ * Helper function for bulk requests, logs ongoing output to console.
243
+ */
244
+ function logBulkProgress(
245
+ operation: "put" | "delete",
246
+ index: number,
247
+ total: number
248
+ ) {
249
+ logger.log(
250
+ `${operation === "put" ? "Uploaded" : "Deleted"} ${Math.floor(
251
+ (100 * index) / total
252
+ )}% (${formatNumber(index)} out of ${formatNumber(total)})`
253
+ );
254
+ }
255
+
256
+ export async function putKVBulkKeyValue(
141
257
  accountId: string,
142
258
  namespaceId: string,
143
- keyStr: string
259
+ keyValues: KeyValue[],
260
+ quiet = false
144
261
  ) {
145
- return await fetchResult(
146
- `/accounts/${accountId}/storage/kv/namespaces/${namespaceId}/bulk`,
147
- {
148
- method: "DELETE",
149
- body: keyStr,
150
- headers: { "Content-Type": "application/json" },
262
+ for (let index = 0; index < keyValues.length; index += BATCH_KEY_MAX) {
263
+ if (!quiet && keyValues.length > BATCH_KEY_MAX) {
264
+ logBulkProgress("put", index, keyValues.length);
151
265
  }
152
- );
266
+
267
+ await fetchResult(
268
+ `/accounts/${accountId}/storage/kv/namespaces/${namespaceId}/bulk`,
269
+ {
270
+ method: "PUT",
271
+ body: JSON.stringify(keyValues.slice(index, index + BATCH_KEY_MAX)),
272
+ headers: { "Content-Type": "application/json" },
273
+ }
274
+ );
275
+ }
276
+
277
+ if (!quiet && keyValues.length > BATCH_KEY_MAX) {
278
+ logBulkProgress("put", keyValues.length, keyValues.length);
279
+ }
153
280
  }
154
281
 
155
- export function getNamespaceId(
156
- { preview, binding, "namespace-id": namespaceId, env }: KvArgs,
157
- configOrEnv: Config | Environment | undefined
282
+ export async function deleteKVBulkKeyValue(
283
+ accountId: string,
284
+ namespaceId: string,
285
+ keys: string[],
286
+ quiet = false
287
+ ) {
288
+ for (let index = 0; index < keys.length; index += BATCH_KEY_MAX) {
289
+ if (!quiet && keys.length > BATCH_KEY_MAX) {
290
+ logBulkProgress("delete", index, keys.length);
291
+ }
292
+
293
+ await fetchResult(
294
+ `/accounts/${accountId}/storage/kv/namespaces/${namespaceId}/bulk`,
295
+ {
296
+ method: "DELETE",
297
+ body: JSON.stringify(keys.slice(index, index + BATCH_KEY_MAX)),
298
+ headers: { "Content-Type": "application/json" },
299
+ }
300
+ );
301
+ }
302
+ if (!quiet && keys.length > BATCH_KEY_MAX) {
303
+ logBulkProgress("delete", keys.length, keys.length);
304
+ }
305
+ }
306
+
307
+ export function getKVNamespaceId(
308
+ { preview, binding, "namespace-id": namespaceId }: KvArgs,
309
+ config: Config
158
310
  ): string {
159
311
  // nice
160
312
  if (namespaceId) {
@@ -164,45 +316,26 @@ export function getNamespaceId(
164
316
  // begin pre-flight checks
165
317
 
166
318
  // `--binding` is only valid if there's a wrangler configuration.
167
- if (binding && !configOrEnv) {
319
+ if (binding && !config) {
168
320
  throw new Error("--binding specified, but no config file was found.");
169
321
  }
170
322
 
171
323
  // there's no config. abort here
172
- if (!configOrEnv) {
324
+ if (!config) {
173
325
  throw new Error(
174
326
  "Failed to find a config file.\n" +
175
327
  "Either use --namespace-id to upload directly or create a configuration file with a binding."
176
328
  );
177
329
  }
178
330
 
179
- // they want to use an environment, actually
180
- if (env) {
181
- if (!("env" in configOrEnv) || !configOrEnv.env[env]) {
182
- throw new Error(
183
- `Failed to find environment "${env}" in configuration file!`
184
- );
185
- }
186
- return getNamespaceId(
187
- {
188
- binding,
189
- "namespace-id": namespaceId,
190
- preview,
191
- },
192
- configOrEnv.env[env]
193
- );
194
- }
195
-
196
331
  // there's no KV namespaces
197
- if (!configOrEnv.kv_namespaces || configOrEnv.kv_namespaces.length === 0) {
332
+ if (!config.kv_namespaces || config.kv_namespaces.length === 0) {
198
333
  throw new Error(
199
334
  "No KV Namespaces configured! Either use --namespace-id to upload directly or add a KV namespace to your wrangler config file."
200
335
  );
201
336
  }
202
337
 
203
- const namespace = configOrEnv.kv_namespaces.find(
204
- (ns) => ns.binding === binding
205
- );
338
+ const namespace = config.kv_namespaces.find((ns) => ns.binding === binding);
206
339
 
207
340
  // we couldn't find a namespace with that binding
208
341
  if (!namespace) {
@@ -266,7 +399,7 @@ export function getNamespaceId(
266
399
  /**
267
400
  * KV namespace binding names must be valid JS identifiers.
268
401
  */
269
- export function isValidNamespaceBinding(
402
+ export function isValidKVNamespaceBinding(
270
403
  binding: string | undefined
271
404
  ): binding is string {
272
405
  return (