wrangler 0.0.0-e0d2f35 → 0.0.0-e1b8ac4

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 (160) hide show
  1. package/Cloudflare_CA.pem +18 -0
  2. package/README.md +27 -14
  3. package/bin/wrangler.js +48 -4
  4. package/kv-asset-handler.js +1 -0
  5. package/miniflare-dist/index.mjs +4886 -0
  6. package/package.json +58 -31
  7. package/pages/functions/buildPlugin.ts +105 -0
  8. package/pages/functions/buildWorker.ts +93 -4
  9. package/pages/functions/filepath-routing.test.ts +223 -28
  10. package/pages/functions/filepath-routing.ts +92 -131
  11. package/pages/functions/routes.ts +16 -18
  12. package/pages/functions/template-plugin.ts +147 -0
  13. package/pages/functions/template-worker.ts +24 -17
  14. package/src/__tests__/config-cache.test.ts +36 -0
  15. package/src/__tests__/configuration.test.ts +3548 -0
  16. package/src/__tests__/dev.test.tsx +1049 -65
  17. package/src/__tests__/guess-worker-format.test.ts +85 -0
  18. package/src/__tests__/{clipboardy-mock.js → helpers/clipboardy-mock.js} +0 -0
  19. package/src/__tests__/helpers/cmd-shim.d.ts +11 -0
  20. package/src/__tests__/helpers/faye-websocket.d.ts +6 -0
  21. package/src/__tests__/helpers/mock-account-id.ts +48 -0
  22. package/src/__tests__/helpers/mock-bin.ts +36 -0
  23. package/src/__tests__/{mock-cfetch.ts → helpers/mock-cfetch.ts} +50 -12
  24. package/src/__tests__/helpers/mock-console.ts +87 -0
  25. package/src/__tests__/helpers/mock-dialogs.ts +153 -0
  26. package/src/__tests__/helpers/mock-http-server.ts +46 -0
  27. package/src/__tests__/helpers/mock-istty.ts +27 -0
  28. package/src/__tests__/{mock-kv.ts → helpers/mock-kv.ts} +9 -16
  29. package/src/__tests__/helpers/mock-oauth-flow.ts +255 -0
  30. package/src/__tests__/helpers/mock-process.ts +24 -0
  31. package/src/__tests__/helpers/mock-stdin.ts +103 -0
  32. package/src/__tests__/helpers/mock-web-socket.ts +29 -0
  33. package/src/__tests__/{run-in-tmp.ts → helpers/run-in-tmp.ts} +11 -1
  34. package/src/__tests__/helpers/run-wrangler.ts +16 -0
  35. package/src/__tests__/helpers/write-worker-source.ts +31 -0
  36. package/src/__tests__/helpers/write-wrangler-toml.ts +17 -0
  37. package/src/__tests__/https-options.test.ts +130 -0
  38. package/src/__tests__/index.test.ts +171 -338
  39. package/src/__tests__/init.test.ts +1992 -0
  40. package/src/__tests__/jest.setup.ts +86 -8
  41. package/src/__tests__/kv.test.ts +745 -449
  42. package/src/__tests__/logger.test.ts +141 -0
  43. package/src/__tests__/package-manager.test.ts +353 -0
  44. package/src/__tests__/pages.test.ts +754 -0
  45. package/src/__tests__/parse.test.ts +330 -0
  46. package/src/__tests__/publish.test.ts +5631 -903
  47. package/src/__tests__/r2.test.ts +233 -0
  48. package/src/__tests__/route.test.ts +45 -0
  49. package/src/__tests__/secret.test.ts +370 -95
  50. package/src/__tests__/tail.test.ts +690 -0
  51. package/src/__tests__/user.test.ts +132 -0
  52. package/src/__tests__/whoami.test.tsx +58 -20
  53. package/src/abort.d.ts +3 -0
  54. package/src/bundle.ts +204 -0
  55. package/src/cfetch/index.ts +30 -20
  56. package/src/cfetch/internal.ts +101 -30
  57. package/src/cli.ts +6 -2
  58. package/src/config/README.md +107 -0
  59. package/src/config/config.ts +243 -0
  60. package/src/config/diagnostics.ts +80 -0
  61. package/src/config/environment.ts +420 -0
  62. package/src/config/index.ts +225 -0
  63. package/src/config/validation-helpers.ts +584 -0
  64. package/src/config/validation.ts +1580 -0
  65. package/src/config-cache.ts +45 -0
  66. package/src/create-worker-preview.ts +185 -0
  67. package/src/create-worker-upload-form.ts +254 -0
  68. package/src/dev/dev-vars.ts +36 -0
  69. package/src/dev/dev.tsx +399 -0
  70. package/src/dev/local.tsx +321 -0
  71. package/src/dev/remote.tsx +242 -0
  72. package/src/dev/use-esbuild.ts +112 -0
  73. package/src/dialogs.tsx +66 -8
  74. package/src/durable.ts +102 -0
  75. package/src/entry.ts +322 -0
  76. package/src/environment-variables.ts +35 -0
  77. package/src/errors.ts +11 -0
  78. package/src/generate-auth-url.ts +33 -0
  79. package/src/generate-random-state.ts +16 -0
  80. package/src/https-options.ts +123 -0
  81. package/src/index.tsx +1848 -1019
  82. package/src/inspect.ts +87 -20
  83. package/src/intl-polyfill.d.ts +139 -0
  84. package/src/{kv.tsx → kv.ts} +199 -67
  85. package/src/logger.ts +73 -0
  86. package/src/miniflare-cli/README.md +30 -0
  87. package/src/miniflare-cli/enum-keys.ts +17 -0
  88. package/src/miniflare-cli/index.ts +41 -0
  89. package/src/miniflare-cli/request-context.ts +40 -0
  90. package/src/module-collection.ts +229 -34
  91. package/src/open-in-browser.ts +17 -0
  92. package/src/package-manager.ts +174 -0
  93. package/src/pages.tsx +1145 -95
  94. package/src/parse.ts +222 -0
  95. package/src/paths.ts +26 -0
  96. package/src/proxy.ts +180 -74
  97. package/src/publish.ts +626 -310
  98. package/src/r2.ts +50 -0
  99. package/src/selfsigned.d.ts +29 -0
  100. package/src/sites.tsx +187 -59
  101. package/src/tail/filters.ts +279 -0
  102. package/src/tail/index.ts +299 -0
  103. package/src/tail/printing.ts +67 -0
  104. package/src/update-check.ts +19 -0
  105. package/src/user.tsx +359 -176
  106. package/src/whoami.tsx +5 -3
  107. package/src/{api/worker.ts → worker.ts} +66 -40
  108. package/src/zones.ts +73 -0
  109. package/templates/gitignore +171 -0
  110. package/templates/new-worker-scheduled.js +17 -0
  111. package/templates/new-worker-scheduled.ts +32 -0
  112. package/templates/new-worker.js +15 -0
  113. package/templates/new-worker.ts +30 -0
  114. package/{static-asset-facade.js → templates/static-asset-facade.js} +3 -7
  115. package/templates/tsconfig.json +105 -0
  116. package/wrangler-dist/cli.js +112889 -110835
  117. package/src/__tests__/logout.test.ts +0 -64
  118. package/src/__tests__/mock-console.ts +0 -34
  119. package/src/__tests__/mock-dialogs.ts +0 -65
  120. package/src/__tests__/mock-user.ts +0 -27
  121. package/src/__tests__/run-wrangler.ts +0 -8
  122. package/src/api/form_data.ts +0 -135
  123. package/src/api/preview.ts +0 -128
  124. package/src/config.ts +0 -641
  125. package/src/dev.tsx +0 -830
  126. package/src/tail.tsx +0 -73
  127. package/vendor/@cloudflare/kv-asset-handler/CHANGELOG.md +0 -332
  128. package/vendor/@cloudflare/kv-asset-handler/LICENSE_APACHE +0 -176
  129. package/vendor/@cloudflare/kv-asset-handler/LICENSE_MIT +0 -25
  130. package/vendor/@cloudflare/kv-asset-handler/README.md +0 -245
  131. package/vendor/@cloudflare/kv-asset-handler/dist/index.d.ts +0 -32
  132. package/vendor/@cloudflare/kv-asset-handler/dist/index.js +0 -354
  133. package/vendor/@cloudflare/kv-asset-handler/dist/mocks.d.ts +0 -13
  134. package/vendor/@cloudflare/kv-asset-handler/dist/mocks.js +0 -148
  135. package/vendor/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV.d.ts +0 -1
  136. package/vendor/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV.js +0 -436
  137. package/vendor/@cloudflare/kv-asset-handler/dist/test/mapRequestToAsset.d.ts +0 -1
  138. package/vendor/@cloudflare/kv-asset-handler/dist/test/mapRequestToAsset.js +0 -40
  139. package/vendor/@cloudflare/kv-asset-handler/dist/test/serveSinglePageApp.d.ts +0 -1
  140. package/vendor/@cloudflare/kv-asset-handler/dist/test/serveSinglePageApp.js +0 -42
  141. package/vendor/@cloudflare/kv-asset-handler/dist/types.d.ts +0 -26
  142. package/vendor/@cloudflare/kv-asset-handler/dist/types.js +0 -31
  143. package/vendor/@cloudflare/kv-asset-handler/package.json +0 -52
  144. package/vendor/@cloudflare/kv-asset-handler/src/index.ts +0 -296
  145. package/vendor/@cloudflare/kv-asset-handler/src/mocks.ts +0 -136
  146. package/vendor/@cloudflare/kv-asset-handler/src/test/getAssetFromKV.ts +0 -464
  147. package/vendor/@cloudflare/kv-asset-handler/src/test/mapRequestToAsset.ts +0 -33
  148. package/vendor/@cloudflare/kv-asset-handler/src/test/serveSinglePageApp.ts +0 -42
  149. package/vendor/@cloudflare/kv-asset-handler/src/types.ts +0 -39
  150. package/vendor/wrangler-mime/CHANGELOG.md +0 -289
  151. package/vendor/wrangler-mime/LICENSE +0 -21
  152. package/vendor/wrangler-mime/Mime.js +0 -97
  153. package/vendor/wrangler-mime/README.md +0 -187
  154. package/vendor/wrangler-mime/cli.js +0 -46
  155. package/vendor/wrangler-mime/index.js +0 -4
  156. package/vendor/wrangler-mime/lite.js +0 -4
  157. package/vendor/wrangler-mime/package.json +0 -52
  158. package/vendor/wrangler-mime/types/other.js +0 -1
  159. package/vendor/wrangler-mime/types/standard.js +0 -1
  160. package/wrangler-dist/cli.js.map +0 -7
package/src/inspect.ts CHANGED
@@ -1,12 +1,17 @@
1
- import assert from "assert";
2
- import type { MessageEvent } from "ws";
3
- import WebSocket, { WebSocketServer } from "ws";
4
- import type { IncomingMessage, Server, ServerResponse } from "http";
5
- import { createServer } from "http";
1
+ import assert from "node:assert";
2
+ import { createServer } from "node:http";
3
+ import os from "node:os";
4
+ import { URL } from "node:url";
5
+
6
+ import open from "open";
6
7
  import { useEffect, useRef, useState } from "react";
8
+ import WebSocket, { WebSocketServer } from "ws";
7
9
  import { version } from "../package.json";
8
-
10
+ import { logger } from "./logger";
11
+ import { waitForPortToBeAvailable } from "./proxy";
9
12
  import type Protocol from "devtools-protocol";
13
+ import type { IncomingMessage, Server, ServerResponse } from "node:http";
14
+ import type { MessageEvent } from "ws";
10
15
 
11
16
  /**
12
17
  * `useInspector` is a hook for debugging Workers applications
@@ -40,7 +45,7 @@ interface InspectorProps {
40
45
  /**
41
46
  * The websocket URL exposed by Workers that the inspector should connect to.
42
47
  */
43
- inspectorUrl: undefined | string;
48
+ inspectorUrl: string | undefined;
44
49
  /**
45
50
  * Whether console statements and exceptions should be logged to the terminal.
46
51
  * (We don't log them in local mode because they're already getting
@@ -132,7 +137,7 @@ export default function useInspector(props: InspectorProps) {
132
137
  wsServer.on("connection", (ws: WebSocket) => {
133
138
  if (wsServer.clients.size > 1) {
134
139
  /** We only want to have one active Devtools instance at a time. */
135
- console.error(
140
+ logger.error(
136
141
  "Tried to open a new devtools window when a previous one was already open."
137
142
  );
138
143
  ws.close(1013, "Too many clients; only one can be connected at a time");
@@ -152,12 +157,26 @@ export default function useInspector(props: InspectorProps) {
152
157
  * of the component lifecycle. Convenient.
153
158
  */
154
159
  useEffect(() => {
155
- 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
+ });
156
174
  return () => {
157
175
  server.close();
158
176
  // Also disconnect any open websockets/devtools connections
159
177
  wsServer.clients.forEach((ws) => ws.close());
160
178
  wsServer.close();
179
+ abortController.abort();
161
180
  };
162
181
  }, [props.port, server, wsServer]);
163
182
 
@@ -238,9 +257,7 @@ export default function useInspector(props: InspectorProps) {
238
257
  const evt = JSON.parse(event.data);
239
258
  if (evt.method === "Runtime.exceptionThrown") {
240
259
  const params = evt.params as Protocol.Runtime.ExceptionThrownEvent;
241
- console.error(
242
- "🚨", // cheesy, but it works
243
- // maybe we could use color here too.
260
+ logger.error(
244
261
  params.exceptionDetails.text,
245
262
  params.exceptionDetails.exception?.description ?? ""
246
263
  );
@@ -252,7 +269,7 @@ export default function useInspector(props: InspectorProps) {
252
269
  }
253
270
  } else {
254
271
  // We should never get here, but who know is 2022...
255
- console.error("unrecognised devtools event:", event);
272
+ logger.error("Unrecognised devtools event:", event);
256
273
  }
257
274
  });
258
275
  }
@@ -271,7 +288,7 @@ export default function useInspector(props: InspectorProps) {
271
288
  });
272
289
 
273
290
  ws.on("unexpected-response", () => {
274
- console.log("waiting for connection...");
291
+ logger.log("Waiting for connection...");
275
292
  /**
276
293
  * This usually means the worker is not "ready" yet
277
294
  * so we'll just retry the connection process
@@ -366,7 +383,10 @@ export default function useInspector(props: InspectorProps) {
366
383
  );
367
384
  remoteWebSocket.send(event.data);
368
385
  } catch (e) {
369
- if (e.message !== "WebSocket is not open: readyState 0 (CONNECTING)") {
386
+ if (
387
+ (e as Error).message !==
388
+ "WebSocket is not open: readyState 0 (CONNECTING)"
389
+ ) {
370
390
  /**
371
391
  * ^ this just means we haven't opened a websocket yet
372
392
  * usually happens until there's at least one request
@@ -374,7 +394,7 @@ export default function useInspector(props: InspectorProps) {
374
394
  * happens on the first request. Maybe we should buffer
375
395
  * these messages too?
376
396
  */
377
- console.error(e);
397
+ logger.error(e);
378
398
  }
379
399
  }
380
400
  }
@@ -586,10 +606,57 @@ function logConsoleMessage(evt: Protocol.Runtime.ConsoleAPICalledEvent): void {
586
606
  const method = mapConsoleAPIMessageTypeToConsoleMethod[evt.type];
587
607
 
588
608
  if (method in console) {
589
- // eslint-disable-next-line prefer-spread
590
- console[method].apply(console, args);
609
+ switch (method) {
610
+ case "dir":
611
+ console.dir(args);
612
+ break;
613
+ case "table":
614
+ console.table(args);
615
+ break;
616
+ default:
617
+ // eslint-disable-next-line prefer-spread
618
+ console[method].apply(console, args);
619
+ break;
620
+ }
591
621
  } else {
592
- console.warn(`Unsupported console method: ${method}`);
593
- console.log("console event:", evt);
622
+ logger.warn(`Unsupported console method: ${method}`);
623
+ logger.warn("console event:", evt);
594
624
  }
595
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,13 +1,18 @@
1
1
  import { URLSearchParams } from "node:url";
2
+ import { fetchListResult, fetchResult, fetchKVGetValue } from "./cfetch";
3
+ import { logger } from "./logger";
2
4
  import type { Config } from "./config";
3
- import { fetchListResult, fetchResult } from "./cfetch";
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
- config?: Config;
11
16
  };
12
17
 
13
18
  /**
@@ -15,7 +20,7 @@ type KvArgs = {
15
20
  *
16
21
  * @returns the generated id of the created namespace.
17
22
  */
18
- export async function createNamespace(
23
+ export async function createKVNamespace(
19
24
  accountId: string,
20
25
  title: string
21
26
  ): Promise<string> {
@@ -47,7 +52,7 @@ export interface KVNamespaceInfo {
47
52
  /**
48
53
  * Fetch a list of all the namespaces under the given `accountId`.
49
54
  */
50
- export async function listNamespaces(
55
+ export async function listKVNamespaces(
51
56
  accountId: string
52
57
  ): Promise<KVNamespaceInfo[]> {
53
58
  const pageSize = 100;
@@ -79,7 +84,7 @@ export interface NamespaceKeyInfo {
79
84
  metadata?: { [key: string]: unknown };
80
85
  }
81
86
 
82
- export async function listNamespaceKeys(
87
+ export async function listKVNamespaceKeys(
83
88
  accountId: string,
84
89
  namespaceId: string,
85
90
  prefix = ""
@@ -91,67 +96,218 @@ export async function listNamespaceKeys(
91
96
  );
92
97
  }
93
98
 
94
- 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 | Buffer;
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(
95
190
  accountId: string,
96
191
  namespaceId: string,
97
- key: string,
98
- value: string,
99
- args?: { expiration?: number; expiration_ttl?: number }
192
+ keyValue: KeyValue
100
193
  ) {
101
194
  let searchParams: URLSearchParams | undefined;
102
- if (args) {
195
+ if (keyValue.expiration || keyValue.expiration_ttl) {
103
196
  searchParams = new URLSearchParams();
104
- if (args.expiration) {
105
- searchParams.set("expiration", `${args.expiration}`);
197
+ if (keyValue.expiration) {
198
+ searchParams.set("expiration", `${keyValue.expiration}`);
106
199
  }
107
- if (args.expiration_ttl) {
108
- searchParams.set("expiration_ttl", `${args.expiration_ttl}`);
200
+ if (keyValue.expiration_ttl) {
201
+ searchParams.set("expiration_ttl", `${keyValue.expiration_ttl}`);
109
202
  }
110
203
  }
111
204
  return await fetchResult(
112
- `/accounts/${accountId}/storage/kv/namespaces/${namespaceId}/values/${key}`,
113
- { method: "PUT", body: value },
205
+ `/accounts/${accountId}/storage/kv/namespaces/${namespaceId}/values/${encodeURIComponent(
206
+ keyValue.key
207
+ )}`,
208
+ { method: "PUT", body: keyValue.value },
114
209
  searchParams
115
210
  );
116
211
  }
117
212
 
118
- export async function putBulkKeyValue(
213
+ export async function getKVKeyValue(
119
214
  accountId: string,
120
215
  namespaceId: string,
121
- keyvalueStr: string
216
+ key: string
217
+ ): Promise<ArrayBuffer> {
218
+ return await fetchKVGetValue(accountId, namespaceId, encodeURIComponent(key));
219
+ }
220
+
221
+ export async function deleteKVKeyValue(
222
+ accountId: string,
223
+ namespaceId: string,
224
+ key: string
122
225
  ) {
123
226
  return await fetchResult(
124
- `/accounts/${accountId}/storage/kv/namespaces/${namespaceId}/bulk`,
125
- {
126
- method: "PUT",
127
- body: keyvalueStr,
128
- headers: { "Content-Type": "application/json" },
129
- }
227
+ `/accounts/${accountId}/storage/kv/namespaces/${namespaceId}/values/${encodeURIComponent(
228
+ key
229
+ )}`,
230
+ { method: "DELETE" }
231
+ );
232
+ }
233
+
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)})`
130
253
  );
131
254
  }
132
255
 
133
- export async function deleteBulkKeyValue(
256
+ export async function putKVBulkKeyValue(
134
257
  accountId: string,
135
258
  namespaceId: string,
136
- keyStr: string
259
+ keyValues: KeyValue[],
260
+ quiet = false
137
261
  ) {
138
- return await fetchResult(
139
- `/accounts/${accountId}/storage/kv/namespaces/${namespaceId}/bulk`,
140
- {
141
- method: "DELETE",
142
- body: keyStr,
143
- 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);
144
265
  }
145
- );
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
+ }
146
280
  }
147
281
 
148
- export function getNamespaceId({
149
- preview,
150
- binding,
151
- config,
152
- "namespace-id": namespaceId,
153
- env,
154
- }: KvArgs): string {
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
310
+ ): string {
155
311
  // nice
156
312
  if (namespaceId) {
157
313
  return namespaceId;
@@ -159,7 +315,7 @@ export function getNamespaceId({
159
315
 
160
316
  // begin pre-flight checks
161
317
 
162
- // `--binding` is only valid if there's a wrangler configuration file.
318
+ // `--binding` is only valid if there's a wrangler configuration.
163
319
  if (binding && !config) {
164
320
  throw new Error("--binding specified, but no config file was found.");
165
321
  }
@@ -172,30 +328,6 @@ export function getNamespaceId({
172
328
  );
173
329
  }
174
330
 
175
- // they want to use an environment, actually
176
- if (env) {
177
- if (!config.env || !config.env[env]) {
178
- throw new Error(
179
- `Failed to find environment "${env}" in configuration file!`
180
- );
181
- }
182
-
183
- // TODO: either a bespoke arg type for this function to avoid `undefined`s or an `EnvOrConfig` type
184
- return getNamespaceId({
185
- binding,
186
- "namespace-id": namespaceId,
187
- env: undefined,
188
- preview,
189
- config: {
190
- env: undefined,
191
- build: undefined,
192
- name: undefined,
193
- account_id: undefined,
194
- ...config.env[env],
195
- },
196
- });
197
- }
198
-
199
331
  // there's no KV namespaces
200
332
  if (!config.kv_namespaces || config.kv_namespaces.length === 0) {
201
333
  throw new Error(
@@ -267,7 +399,7 @@ export function getNamespaceId({
267
399
  /**
268
400
  * KV namespace binding names must be valid JS identifiers.
269
401
  */
270
- export function isValidNamespaceBinding(
402
+ export function isValidKVNamespaceBinding(
271
403
  binding: string | undefined
272
404
  ): binding is string {
273
405
  return (