wrangler 2.1.14 → 2.2.0

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 (75) hide show
  1. package/miniflare-dist/index.mjs +3 -1
  2. package/package.json +2 -1
  3. package/src/__tests__/access.test.ts +25 -0
  4. package/src/__tests__/api-dev.test.ts +1 -1
  5. package/src/__tests__/api-devregistry.test.js +2 -2
  6. package/src/__tests__/configuration.test.ts +119 -2
  7. package/src/__tests__/d1.test.ts +2 -0
  8. package/src/__tests__/deployments.test.ts +22 -22
  9. package/src/__tests__/dev.test.tsx +167 -15
  10. package/src/__tests__/helpers/msw/handlers/access.ts +13 -0
  11. package/src/__tests__/helpers/msw/handlers/deployments.ts +22 -43
  12. package/src/__tests__/helpers/msw/handlers/zones.ts +22 -0
  13. package/src/__tests__/helpers/msw/index.ts +4 -0
  14. package/src/__tests__/index.test.ts +42 -33
  15. package/src/__tests__/init.test.ts +88 -4
  16. package/src/__tests__/jest.setup.ts +11 -0
  17. package/src/__tests__/kv.test.ts +400 -400
  18. package/src/__tests__/pages.test.ts +140 -28
  19. package/src/__tests__/publish.test.ts +1161 -647
  20. package/src/__tests__/pubsub.test.ts +3 -0
  21. package/src/__tests__/queues.test.ts +371 -0
  22. package/src/__tests__/r2.test.ts +57 -52
  23. package/src/__tests__/worker-namespace.test.ts +15 -10
  24. package/src/bundle-reporter.tsx +41 -2
  25. package/src/bundle.ts +59 -30
  26. package/src/cli.ts +0 -1
  27. package/src/config/environment.ts +50 -0
  28. package/src/config/index.ts +41 -0
  29. package/src/config/validation.ts +173 -0
  30. package/src/create-worker-preview.ts +10 -3
  31. package/src/create-worker-upload-form.ts +12 -0
  32. package/src/d1/backups.tsx +11 -5
  33. package/src/d1/execute.tsx +52 -47
  34. package/src/d1/index.ts +2 -1
  35. package/src/delete.ts +7 -10
  36. package/src/deployments.ts +73 -0
  37. package/src/deprecated/index.ts +9 -24
  38. package/src/dev/dev-vars.ts +11 -8
  39. package/src/dev/dev.tsx +12 -0
  40. package/src/dev/local.tsx +26 -0
  41. package/src/dev/remote.tsx +2 -0
  42. package/src/dev/start-server.ts +7 -0
  43. package/src/dev/use-esbuild.ts +12 -5
  44. package/src/dev.tsx +12 -9
  45. package/src/dispatch-namespace.ts +4 -3
  46. package/src/index.tsx +61 -45
  47. package/src/init.ts +4 -4
  48. package/src/inspect.ts +21 -1
  49. package/src/is-interactive.ts +4 -0
  50. package/src/kv/index.ts +5 -54
  51. package/src/logger.ts +12 -0
  52. package/src/pages/constants.ts +2 -0
  53. package/src/pages/upload.tsx +42 -15
  54. package/src/proxy.ts +38 -6
  55. package/src/publish/index.ts +11 -8
  56. package/src/publish/publish.ts +151 -30
  57. package/src/pubsub/pubsub-commands.tsx +3 -2
  58. package/src/queues/cli/commands/consumer/add.ts +71 -0
  59. package/src/queues/cli/commands/consumer/index.ts +22 -0
  60. package/src/queues/cli/commands/consumer/remove.ts +38 -0
  61. package/src/queues/cli/commands/create.ts +25 -0
  62. package/src/queues/cli/commands/delete.ts +26 -0
  63. package/src/queues/cli/commands/index.ts +33 -0
  64. package/src/queues/cli/commands/list.ts +25 -0
  65. package/src/queues/client.ts +135 -0
  66. package/src/secret/index.ts +14 -39
  67. package/src/tail/index.ts +5 -8
  68. package/src/user/access.ts +69 -0
  69. package/src/worker.ts +7 -0
  70. package/src/yargs-types.ts +15 -2
  71. package/src/zones.ts +31 -5
  72. package/templates/pages-template-plugin.ts +4 -0
  73. package/templates/pages-template-worker.ts +21 -4
  74. package/wrangler-dist/cli.d.ts +42 -0
  75. package/wrangler-dist/cli.js +4559 -3228
@@ -0,0 +1,33 @@
1
+ import { type BuilderCallback } from "yargs";
2
+ import { type CommonYargsOptions } from "../../../yargs-types";
3
+ import { consumers } from "./consumer";
4
+
5
+ import { options as createOptions, handler as createHandler } from "./create";
6
+ import { options as deleteOptions, handler as deleteHandler } from "./delete";
7
+ import { options as listOptions, handler as listHandler } from "./list";
8
+
9
+ export const queues: BuilderCallback<CommonYargsOptions, unknown> = (yargs) => {
10
+ yargs.command("list", "List Queues", listOptions, listHandler);
11
+
12
+ yargs.command(
13
+ "create <name>",
14
+ "Create a Queue",
15
+ createOptions,
16
+ createHandler
17
+ );
18
+
19
+ yargs.command(
20
+ "delete <name>",
21
+ "Delete a Queue",
22
+ deleteOptions,
23
+ deleteHandler
24
+ );
25
+
26
+ yargs.command(
27
+ "consumer",
28
+ "Configure Queue Consumers",
29
+ async (consumersYargs) => {
30
+ await consumers(consumersYargs);
31
+ }
32
+ );
33
+ };
@@ -0,0 +1,25 @@
1
+ import { type Argv } from "yargs";
2
+ import { readConfig } from "../../../config";
3
+ import { logger } from "../../../logger";
4
+ import { listQueues } from "../../client";
5
+
6
+ interface Args {
7
+ config?: string;
8
+ page?: number;
9
+ }
10
+
11
+ export function options(yargs: Argv): Argv<Args> {
12
+ return yargs.options({
13
+ page: {
14
+ type: "number",
15
+ describe: "Page number for pagination",
16
+ },
17
+ });
18
+ }
19
+
20
+ export async function handler(args: Args) {
21
+ const config = readConfig(args.config, args);
22
+
23
+ const queues = await listQueues(config, args.page);
24
+ logger.log(JSON.stringify(queues));
25
+ }
@@ -0,0 +1,135 @@
1
+ import { fetchResult } from "../cfetch";
2
+ import { type Config } from "../config";
3
+ import { requireAuth } from "../user";
4
+
5
+ export async function createQueue(
6
+ config: Config,
7
+ body: CreateQueueBody
8
+ ): Promise<QueueResponse> {
9
+ const accountId = await requireAuth(config);
10
+ return await fetchResult(`/accounts/${accountId}/workers/queues`, {
11
+ method: "POST",
12
+ body: JSON.stringify(body),
13
+ });
14
+ }
15
+
16
+ export interface CreateQueueBody {
17
+ queue_name: string;
18
+ }
19
+
20
+ export interface QueueResponse {
21
+ queue_name: string;
22
+ created_on: string;
23
+ modified_on: string;
24
+ }
25
+
26
+ export async function deleteQueue(
27
+ config: Config,
28
+ queueName: string
29
+ ): Promise<void> {
30
+ const accountId = await requireAuth(config);
31
+ return await fetchResult(
32
+ `/accounts/${accountId}/workers/queues/${queueName}`,
33
+ {
34
+ method: "DELETE",
35
+ }
36
+ );
37
+ }
38
+
39
+ // TODO(soon) show detailed queue response
40
+ export async function listQueues(
41
+ config: Config,
42
+ page?: number
43
+ ): Promise<QueueResponse[]> {
44
+ page = page ?? 1;
45
+ const accountId = await requireAuth(config);
46
+ return await fetchResult(
47
+ `/accounts/${accountId}/workers/queues`,
48
+ {},
49
+ new URLSearchParams({ page: page.toString() })
50
+ );
51
+ }
52
+
53
+ export async function getQueue(
54
+ config: Config,
55
+ queueName: string
56
+ ): Promise<QueueResponse> {
57
+ const accountId = await requireAuth(config);
58
+ return await fetchResult(
59
+ `/accounts/${accountId}/workers/queues/${queueName}`,
60
+ {}
61
+ );
62
+ }
63
+
64
+ export async function postConsumer(
65
+ config: Config,
66
+ queueName: string,
67
+ body: PostConsumerBody
68
+ ): Promise<ConsumerResponse> {
69
+ const accountId = await requireAuth(config);
70
+ return fetchResult(
71
+ `/accounts/${accountId}/workers/queues/${queueName}/consumers`,
72
+ {
73
+ method: "POST",
74
+ body: JSON.stringify(body),
75
+ }
76
+ );
77
+ }
78
+
79
+ export interface PutConsumerBody {
80
+ settings: ConsumerSettings;
81
+ dead_letter_queue?: string;
82
+ }
83
+
84
+ export interface PostConsumerBody extends PutConsumerBody {
85
+ script_name: string;
86
+ environment_name: string;
87
+ }
88
+
89
+ export interface ConsumerSettings {
90
+ batch_size?: number;
91
+ max_retries?: number;
92
+ max_wait_time_ms?: number;
93
+ }
94
+
95
+ export interface ConsumerResponse extends PostConsumerBody {
96
+ queue_name: string;
97
+ script_name: string;
98
+ environment_name: string;
99
+ settings: ConsumerSettings;
100
+ dead_letter_queue?: string;
101
+ }
102
+
103
+ export async function deleteConsumer(
104
+ config: Config,
105
+ queueName: string,
106
+ scriptName: string,
107
+ envName?: string
108
+ ): Promise<void> {
109
+ const accountId = await requireAuth(config);
110
+ let resource = `/accounts/${accountId}/workers/queues/${queueName}/consumers/${scriptName}`;
111
+ if (envName !== undefined) {
112
+ resource += `/environments/${envName}`;
113
+ }
114
+ return fetchResult(resource, {
115
+ method: "DELETE",
116
+ });
117
+ }
118
+
119
+ export async function putConsumer(
120
+ config: Config,
121
+ queueName: string,
122
+ scriptName: string,
123
+ envName: string | undefined,
124
+ body: PutConsumerBody
125
+ ): Promise<ConsumerResponse> {
126
+ const accountId = await requireAuth(config);
127
+ let resource = `/accounts/${accountId}/workers/queues/${queueName}/consumers/${scriptName}`;
128
+ if (envName !== undefined) {
129
+ resource += `/environments/${envName}`;
130
+ }
131
+ return fetchResult(resource, {
132
+ method: "PUT",
133
+ body: JSON.stringify(body),
134
+ });
135
+ }
@@ -14,12 +14,13 @@ import { parseJSON, readFileSync } from "../parse";
14
14
  import { requireAuth } from "../user";
15
15
 
16
16
  import type { ConfigPath } from "../index";
17
- import type { YargsOptionsToInterface } from "../yargs-types";
18
- import type { Argv, BuilderCallback } from "yargs";
17
+ import type {
18
+ CommonYargsOptions,
19
+ YargsOptionsToInterface,
20
+ } from "../yargs-types";
21
+ import type { Argv } from "yargs";
19
22
 
20
- export const secret: BuilderCallback<unknown, unknown> = (
21
- secretYargs: Argv
22
- ) => {
23
+ export const secret = (secretYargs: Argv<CommonYargsOptions>) => {
23
24
  return secretYargs
24
25
  .option("legacy-env", {
25
26
  type: "boolean",
@@ -39,13 +40,6 @@ export const secret: BuilderCallback<unknown, unknown> = (
39
40
  describe: "Name of the Worker",
40
41
  type: "string",
41
42
  requiresArg: true,
42
- })
43
- .option("env", {
44
- type: "string",
45
- requiresArg: true,
46
- describe:
47
- "Binds the secret to the Worker of the specific environment",
48
- alias: "e",
49
43
  });
50
44
  },
51
45
  async (args) => {
@@ -110,6 +104,7 @@ export const secret: BuilderCallback<unknown, unknown> = (
110
104
  kv_namespaces: [],
111
105
  vars: {},
112
106
  durable_objects: { bindings: [] },
107
+ queues: [],
113
108
  r2_buckets: [],
114
109
  d1_databases: [],
115
110
  services: [],
@@ -126,6 +121,7 @@ export const secret: BuilderCallback<unknown, unknown> = (
126
121
  compatibility_flags: undefined,
127
122
  usage_model: undefined,
128
123
  keepVars: false, // this doesn't matter since it's a new script anyway
124
+ logpush: false,
129
125
  }),
130
126
  }
131
127
  );
@@ -172,13 +168,6 @@ export const secret: BuilderCallback<unknown, unknown> = (
172
168
  describe: "Name of the Worker",
173
169
  type: "string",
174
170
  requiresArg: true,
175
- })
176
- .option("env", {
177
- type: "string",
178
- requiresArg: true,
179
- describe:
180
- "Binds the secret to the Worker of the specific environment",
181
- alias: "e",
182
171
  });
183
172
  },
184
173
  async (args) => {
@@ -225,19 +214,11 @@ export const secret: BuilderCallback<unknown, unknown> = (
225
214
  "list",
226
215
  "List all secrets for a Worker",
227
216
  (yargs) => {
228
- return yargs
229
- .option("name", {
230
- describe: "Name of the Worker",
231
- type: "string",
232
- requiresArg: true,
233
- })
234
- .option("env", {
235
- type: "string",
236
- requiresArg: true,
237
- describe:
238
- "Binds the secret to the Worker of the specific environment.",
239
- alias: "e",
240
- });
217
+ return yargs.option("name", {
218
+ describe: "Name of the Worker",
219
+ type: "string",
220
+ requiresArg: true,
221
+ });
241
222
  },
242
223
  async (args) => {
243
224
  const config = readConfig(args.config as ConfigPath, args);
@@ -264,7 +245,7 @@ export const secret: BuilderCallback<unknown, unknown> = (
264
245
  );
265
246
  };
266
247
 
267
- export const secretBulkOptions = (yargs: Argv) => {
248
+ export const secretBulkOptions = (yargs: Argv<CommonYargsOptions>) => {
268
249
  return yargs
269
250
  .positional("json", {
270
251
  describe: `The JSON file of key-value pairs to upload, in form {"key": value, ...}`,
@@ -275,12 +256,6 @@ export const secretBulkOptions = (yargs: Argv) => {
275
256
  describe: "Name of the Worker",
276
257
  type: "string",
277
258
  requiresArg: true,
278
- })
279
- .option("env", {
280
- type: "string",
281
- requiresArg: true,
282
- describe: "Binds the secret to the Worker of the specific environment.",
283
- alias: "e",
284
259
  });
285
260
  };
286
261
 
package/src/tail/index.ts CHANGED
@@ -21,12 +21,15 @@ import {
21
21
  } from "./createTail";
22
22
  import type { WorkerMetadata } from "../create-worker-upload-form";
23
23
  import type { ConfigPath } from "../index";
24
- import type { YargsOptionsToInterface } from "../yargs-types";
24
+ import type {
25
+ CommonYargsOptions,
26
+ YargsOptionsToInterface,
27
+ } from "../yargs-types";
25
28
  import type { TailCLIFilters } from "./createTail";
26
29
  import type { RawData } from "ws";
27
30
  import type { Argv } from "yargs";
28
31
 
29
- export function tailOptions(yargs: Argv) {
32
+ export function tailOptions(yargs: Argv<CommonYargsOptions>) {
30
33
  return yargs
31
34
  .positional("worker", {
32
35
  describe: "Name or route of the worker to tail",
@@ -69,12 +72,6 @@ export function tailOptions(yargs: Argv) {
69
72
  'Filter by the IP address the request originates from. Use "self" to filter for your own IP',
70
73
  array: true,
71
74
  })
72
- .option("env", {
73
- type: "string",
74
- requiresArg: true,
75
- describe: "Perform on a specific environment",
76
- alias: "e",
77
- })
78
75
  .option("debug", {
79
76
  type: "boolean",
80
77
  hidden: true,
@@ -0,0 +1,69 @@
1
+ import { spawnSync } from "child_process";
2
+ import { fetch } from "undici";
3
+ import { logger } from "../logger";
4
+
5
+ const cache: Record<string, string> = {};
6
+
7
+ const usesAccessCache = new Map();
8
+
9
+ export async function domainUsesAccess(domain: string): Promise<boolean> {
10
+ logger.debug("Checking if domain has Access enabled:", domain);
11
+
12
+ if (usesAccessCache.has(domain)) {
13
+ logger.debug(
14
+ "Using cached Access switch for:",
15
+ domain,
16
+ usesAccessCache.get(domain)
17
+ );
18
+ return usesAccessCache.get(domain);
19
+ }
20
+ logger.debug("Access switch not cached for:", domain);
21
+ try {
22
+ const controller = new AbortController();
23
+ const cancel = setTimeout(() => {
24
+ logger.debug("Timed out");
25
+ controller.abort();
26
+ }, 1000);
27
+
28
+ const output = await fetch(`https://${domain}`, {
29
+ redirect: "manual",
30
+ signal: controller.signal,
31
+ });
32
+ clearTimeout(cancel);
33
+ const usesAccess = !!(
34
+ output.status === 302 &&
35
+ output.headers.get("location")?.includes("cloudflareaccess.com")
36
+ );
37
+ logger.debug("Caching access switch for:", domain);
38
+
39
+ usesAccessCache.set(domain, usesAccess);
40
+ return usesAccess;
41
+ } catch (e) {
42
+ usesAccessCache.set(domain, false);
43
+ return false;
44
+ }
45
+ }
46
+ export async function getAccessToken(
47
+ domain: string
48
+ ): Promise<string | undefined> {
49
+ if (!(await domainUsesAccess(domain))) {
50
+ return undefined;
51
+ }
52
+ if (cache[domain]) {
53
+ return cache[domain];
54
+ }
55
+ const output = await spawnSync("cloudflared", ["access", "login", domain]);
56
+ if (output.error) {
57
+ // The cloudflared binary is not installed
58
+ throw new Error(
59
+ "To use Wrangler with Cloudflare Access, please install `cloudflared` from https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/installation"
60
+ );
61
+ }
62
+ const stringOutput = output.stdout.toString();
63
+ const matches = stringOutput.match(/fetched your token:\n\n(.*)/m);
64
+ if (matches && matches.length >= 2) {
65
+ cache[domain] = matches[1];
66
+ return matches[1];
67
+ }
68
+ throw new Error("Failed to authenticate with Cloudflare Access");
69
+ }
package/src/worker.ts CHANGED
@@ -113,6 +113,11 @@ export interface CfDurableObject {
113
113
  environment?: string;
114
114
  }
115
115
 
116
+ export interface CfQueue {
117
+ binding: string;
118
+ queue_name: string;
119
+ }
120
+
116
121
  export interface CfR2Bucket {
117
122
  binding: string;
118
123
  bucket_name: string;
@@ -194,6 +199,7 @@ export interface CfWorkerInit {
194
199
  text_blobs: CfTextBlobBindings | undefined;
195
200
  data_blobs: CfDataBlobBindings | undefined;
196
201
  durable_objects: { bindings: CfDurableObject[] } | undefined;
202
+ queues: CfQueue[] | undefined;
197
203
  r2_buckets: CfR2Bucket[] | undefined;
198
204
  d1_databases: CfD1Database[] | undefined;
199
205
  services: CfService[] | undefined;
@@ -206,6 +212,7 @@ export interface CfWorkerInit {
206
212
  compatibility_flags: string[] | undefined;
207
213
  usage_model: "bundled" | "unbound" | undefined;
208
214
  keepVars: boolean | undefined;
215
+ logpush: boolean | undefined;
209
216
  }
210
217
 
211
218
  export interface CfWorkerContext {
@@ -1,7 +1,20 @@
1
1
  import type { ArgumentsCamelCase, Argv } from "yargs";
2
+
3
+ /**
4
+ * Yargs options included in every wrangler command.
5
+ */
6
+ export interface CommonYargsOptions {
7
+ v: boolean | undefined;
8
+ config: string | undefined;
9
+ env: string | undefined;
10
+ }
11
+
2
12
  /**
3
13
  * Given some Yargs Options function factory, extract the interface
4
14
  * that corresponds to the yargs arguments
5
15
  */
6
- export type YargsOptionsToInterface<T extends (yargs: Argv) => Argv> =
7
- T extends (yargs: Argv) => Argv<infer P> ? ArgumentsCamelCase<P> : never;
16
+ export type YargsOptionsToInterface<
17
+ T extends (yargs: Argv<CommonYargsOptions>) => Argv
18
+ > = T extends (yargs: Argv<CommonYargsOptions>) => Argv<infer P>
19
+ ? ArgumentsCamelCase<P>
20
+ : never;
package/src/zones.ts CHANGED
@@ -9,12 +9,38 @@ export interface Zone {
9
9
  host: string;
10
10
  }
11
11
 
12
+ /**
13
+ * Get the hostname on which to run a Worker.
14
+ *
15
+ * The most accurate place is usually
16
+ * `route.pattern`, as that includes any subdomains. For example:
17
+ * ```js
18
+ * {
19
+ * pattern: foo.example.com
20
+ * zone_name: example.com
21
+ * }
22
+ * ```
23
+ * However, in the case of patterns that _can't_ be parsed as a hostname
24
+ * (primarily the pattern `*/ /*`), we fall back to the `zone_name`
25
+ * (and in the absence of that throw an error).
26
+ * @param route
27
+ */
12
28
  export function getHostFromRoute(route: Route): string | undefined {
13
- return typeof route === "string"
14
- ? getHostFromUrl(route)
15
- : typeof route === "object"
16
- ? getHostFromUrl(route.pattern)
17
- : undefined;
29
+ if (typeof route === "string") {
30
+ return getHostFromUrl(route);
31
+ } else if (typeof route === "object") {
32
+ try {
33
+ return getHostFromUrl(route.pattern);
34
+ } catch (e) {
35
+ if (
36
+ (e as { code: string }).code === "ERR_INVALID_URL" &&
37
+ "zone_name" in route
38
+ ) {
39
+ return getHostFromUrl(route.zone_name);
40
+ }
41
+ throw e;
42
+ }
43
+ }
18
44
  }
19
45
 
20
46
  /**
@@ -19,6 +19,7 @@ type EventContext<Env, P extends string, Data> = {
19
19
  request: Request;
20
20
  functionPath: string;
21
21
  waitUntil: (promise: Promise<unknown>) => void;
22
+ passThroughOnException: () => void;
22
23
  next: (input?: Request | string, init?: RequestInit) => Promise<Response>;
23
24
  env: Env & { ASSETS: { fetch: typeof fetch } };
24
25
  params: Params<P>;
@@ -29,6 +30,7 @@ type EventPluginContext<Env, P extends string, Data, PluginArgs> = {
29
30
  request: Request;
30
31
  functionPath: string;
31
32
  waitUntil: (promise: Promise<unknown>) => void;
33
+ passThroughOnException: () => void;
32
34
  next: (input?: Request | string, init?: RequestInit) => Promise<Response>;
33
35
  env: Env & { ASSETS: { fetch: typeof fetch } };
34
36
  params: Params<P>;
@@ -146,6 +148,8 @@ export default function (pluginArgs) {
146
148
  pluginArgs,
147
149
  env,
148
150
  waitUntil: workerContext.waitUntil.bind(workerContext),
151
+ passThroughOnException:
152
+ workerContext.passThroughOnException.bind(workerContext),
149
153
  };
150
154
 
151
155
  const response = await handler(context);
@@ -19,6 +19,7 @@ type EventContext<Env, P extends string, Data> = {
19
19
  request: Request;
20
20
  functionPath: string;
21
21
  waitUntil: (promise: Promise<unknown>) => void;
22
+ passThroughOnException: () => void;
22
23
  next: (input?: Request | string, init?: RequestInit) => Promise<Response>;
23
24
  env: Env & { ASSETS: { fetch: typeof fetch } };
24
25
  params: Params<P>;
@@ -53,6 +54,7 @@ type FetchEnv = {
53
54
 
54
55
  type WorkerContext = {
55
56
  waitUntil: (promise: Promise<unknown>) => void;
57
+ passThroughOnException: () => void;
56
58
  };
57
59
 
58
60
  function* executeRequest(request: Request) {
@@ -111,9 +113,16 @@ function* executeRequest(request: Request) {
111
113
  }
112
114
 
113
115
  export default {
114
- async fetch(request: Request, env: FetchEnv, workerContext: WorkerContext) {
116
+ async fetch(
117
+ originalRequest: Request,
118
+ env: FetchEnv,
119
+ workerContext: WorkerContext
120
+ ) {
121
+ let request = originalRequest;
115
122
  const handlerIterator = executeRequest(request);
116
123
  const data = {}; // arbitrary data the user can set between functions
124
+ let isFailOpen = false;
125
+
117
126
  const next = async (input?: RequestInfo, init?: RequestInit) => {
118
127
  if (input !== undefined) {
119
128
  let url = input;
@@ -135,6 +144,9 @@ export default {
135
144
  data,
136
145
  env,
137
146
  waitUntil: workerContext.waitUntil.bind(workerContext),
147
+ passThroughOnException: () => {
148
+ isFailOpen = true;
149
+ },
138
150
  };
139
151
 
140
152
  const response = await handler(context);
@@ -156,9 +168,14 @@ export default {
156
168
  };
157
169
 
158
170
  try {
159
- return next();
160
- } catch (err) {
161
- return new Response("Internal Error", { status: 500 });
171
+ return await next();
172
+ } catch (error) {
173
+ if (isFailOpen) {
174
+ const response = await env[__FALLBACK_SERVICE__].fetch(request);
175
+ return cloneResponse(response);
176
+ }
177
+
178
+ throw error;
162
179
  }
163
180
  },
164
181
  };
@@ -347,6 +347,17 @@ declare interface EnvironmentInheritable {
347
347
  destination: string;
348
348
  }[];
349
349
  };
350
+ /**
351
+ * Send Trace Events from this worker to Workers Logpush.
352
+ *
353
+ * This will not configure a corresponding Logpush job automatically.
354
+ *
355
+ * For more information about Workers Logpush, see:
356
+ * https://blog.cloudflare.com/logpush-for-workers/
357
+ *
358
+ * @inheritable
359
+ */
360
+ logpush: boolean | undefined;
350
361
  }
351
362
 
352
363
  /**
@@ -424,6 +435,37 @@ declare interface EnvironmentNonInheritable {
424
435
  /** The ID of the KV namespace used during `wrangler dev` */
425
436
  preview_id?: string;
426
437
  }[];
438
+ /**
439
+ * Specifies Queues that are bound to this Worker environment.
440
+ *
441
+ * NOTE: This field is not automatically inherited from the top level environment,
442
+ * and so must be specified in every named environment.
443
+ *
444
+ * @default `{}`
445
+ * @nonInheritable
446
+ */
447
+ queues: {
448
+ /** Producer bindings */
449
+ producers?: {
450
+ /** The binding name used to refer to the Queue in the worker. */
451
+ binding: string;
452
+ /** The name of this Queue. */
453
+ queue: string;
454
+ }[];
455
+ /** Consumer configuration */
456
+ consumers?: {
457
+ /** The name of the queue from which this script should consume. */
458
+ queue: string;
459
+ /** The maximum number of messages per batch */
460
+ max_batch_size?: number;
461
+ /** The maximum number of seconds to wait to fill a batch with messages. */
462
+ max_batch_timeout?: number;
463
+ /** The maximum number of retries for each message. */
464
+ max_retries?: number;
465
+ /** The queue to send messages that failed to be consumed. */
466
+ dead_letter_queue?: string;
467
+ }[];
468
+ };
427
469
  /**
428
470
  * Specifies R2 buckets that are bound to this Worker environment.
429
471
  *