wrangler 2.0.27 → 2.1.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 (65) hide show
  1. package/bin/wrangler.js +1 -1
  2. package/miniflare-dist/index.mjs +1141 -369
  3. package/package.json +6 -4
  4. package/src/__tests__/api-dev.test.ts +19 -0
  5. package/src/__tests__/configuration.test.ts +27 -27
  6. package/src/__tests__/dev.test.tsx +8 -6
  7. package/src/__tests__/helpers/hello-world-worker.js +5 -0
  8. package/src/__tests__/helpers/mock-cfetch.ts +4 -4
  9. package/src/__tests__/helpers/mock-console.ts +11 -2
  10. package/src/__tests__/helpers/mock-get-zone-from-host.ts +8 -0
  11. package/src/__tests__/helpers/mock-known-routes.ts +7 -0
  12. package/src/__tests__/index.test.ts +37 -37
  13. package/src/__tests__/init.test.ts +356 -5
  14. package/src/__tests__/jest.setup.ts +13 -0
  15. package/src/__tests__/middleware.test.ts +768 -0
  16. package/src/__tests__/pages.test.ts +829 -104
  17. package/src/__tests__/paths.test.ts +17 -0
  18. package/src/__tests__/publish.test.ts +512 -445
  19. package/src/__tests__/tail.test.ts +79 -72
  20. package/src/__tests__/test-old-node-version.js +3 -3
  21. package/src/__tests__/worker-namespace.test.ts +37 -35
  22. package/src/api/dev.ts +93 -28
  23. package/src/bundle.ts +239 -12
  24. package/src/cfetch/internal.ts +64 -3
  25. package/src/cli.ts +1 -1
  26. package/src/config/environment.ts +1 -1
  27. package/src/config/index.ts +4 -4
  28. package/src/config/validation.ts +3 -3
  29. package/src/create-worker-upload-form.ts +29 -26
  30. package/src/dev/dev.tsx +3 -1
  31. package/src/dev/local.tsx +319 -171
  32. package/src/dev/remote.tsx +16 -4
  33. package/src/dev/start-server.ts +416 -0
  34. package/src/dev/use-esbuild.ts +4 -0
  35. package/src/dev.tsx +340 -166
  36. package/src/dialogs.tsx +12 -0
  37. package/src/{worker-namespace.ts → dispatch-namespace.ts} +18 -18
  38. package/src/entry.ts +2 -1
  39. package/src/index.tsx +59 -12
  40. package/src/init.ts +291 -16
  41. package/src/metrics/send-event.ts +6 -5
  42. package/src/miniflare-cli/assets.ts +130 -476
  43. package/src/miniflare-cli/index.ts +39 -33
  44. package/src/pages/constants.ts +3 -0
  45. package/src/pages/dev.tsx +8 -3
  46. package/src/pages/functions/buildPlugin.ts +2 -1
  47. package/src/pages/functions/buildWorker.ts +2 -1
  48. package/src/pages/functions/routes-transformation.test.ts +12 -1
  49. package/src/pages/functions/routes-transformation.ts +7 -1
  50. package/src/pages/hash.tsx +13 -0
  51. package/src/pages/publish.tsx +82 -38
  52. package/src/pages/upload.tsx +3 -18
  53. package/src/paths.ts +20 -1
  54. package/src/publish.ts +49 -8
  55. package/src/tail/filters.ts +1 -5
  56. package/src/tail/index.ts +6 -3
  57. package/src/worker.ts +10 -9
  58. package/src/zones.ts +91 -0
  59. package/templates/middleware/common.ts +62 -0
  60. package/templates/middleware/loader-modules.ts +84 -0
  61. package/templates/middleware/loader-sw.ts +213 -0
  62. package/templates/middleware/middleware-pretty-error.ts +40 -0
  63. package/templates/middleware/middleware-scheduled.ts +14 -0
  64. package/wrangler-dist/cli.d.ts +22 -8
  65. package/wrangler-dist/cli.js +71020 -65212
@@ -122,7 +122,6 @@ type QueryFilter = {
122
122
  */
123
123
  export type TailFilterMessage = {
124
124
  filters: TailAPIFilter[];
125
- debug: boolean;
126
125
  };
127
126
 
128
127
  /**
@@ -130,12 +129,10 @@ export type TailFilterMessage = {
130
129
  * into a message that we can send to the tail worker.
131
130
  *
132
131
  * @param cliFilters An object containing all the filters passed in from the CLI
133
- * @param debug Whether or not we should be in debug mode
134
132
  * @returns A filter message ready to be sent to the tail worker
135
133
  */
136
134
  export function translateCLICommandToFilterMessage(
137
- cliFilters: TailCLIFilters,
138
- debug: boolean
135
+ cliFilters: TailCLIFilters
139
136
  ): TailFilterMessage {
140
137
  const apiFilters: TailAPIFilter[] = [];
141
138
 
@@ -165,7 +162,6 @@ export function translateCLICommandToFilterMessage(
165
162
 
166
163
  return {
167
164
  filters: apiFilters,
168
- debug,
169
165
  };
170
166
  }
171
167
 
package/src/tail/index.ts CHANGED
@@ -69,13 +69,15 @@ function makeDeleteTailUrl(
69
69
  *
70
70
  * @param accountId the account ID associated with the worker to tail
71
71
  * @param workerName the name of the worker to tail
72
- * @param message a `TailFilterMessage` to send up to the tail worker
72
+ * @param filters A list of `TailAPIFilters` given to the tail
73
+ * @param debug Flag to run tail in debug mode
73
74
  * @returns a websocket connection, an expiration, and a function to call to delete the tail
74
75
  */
75
76
  export async function createTail(
76
77
  accountId: string,
77
78
  workerName: string,
78
- message: TailFilterMessage,
79
+ filters: TailFilterMessage,
80
+ debug: boolean,
79
81
  env: string | undefined
80
82
  ): Promise<{
81
83
  tail: WebSocket;
@@ -90,6 +92,7 @@ export async function createTail(
90
92
  expires_at: expiration,
91
93
  } = await fetchResult<TailCreationApiResponse>(createTailUrl, {
92
94
  method: "POST",
95
+ body: JSON.stringify(filters),
93
96
  });
94
97
 
95
98
  // delete the tail (not yet!)
@@ -109,7 +112,7 @@ export async function createTail(
109
112
  // send filters when we open up
110
113
  tail.on("open", function () {
111
114
  tail.send(
112
- JSON.stringify(message),
115
+ JSON.stringify({ debug: debug }),
113
116
  { binary: false, compress: false, mask: false, fin: true },
114
117
  (err) => {
115
118
  if (err) {
package/src/worker.ts CHANGED
@@ -65,14 +65,14 @@ export interface CfModule {
65
65
  /**
66
66
  * A map of variable names to values.
67
67
  */
68
- interface CfVars {
68
+ export interface CfVars {
69
69
  [key: string]: unknown;
70
70
  }
71
71
 
72
72
  /**
73
73
  * A KV namespace.
74
74
  */
75
- interface CfKvNamespace {
75
+ export interface CfKvNamespace {
76
76
  binding: string;
77
77
  id: string;
78
78
  }
@@ -81,7 +81,7 @@ interface CfKvNamespace {
81
81
  * A binding to a wasm module (in service-worker format)
82
82
  */
83
83
 
84
- interface CfWasmModuleBindings {
84
+ export interface CfWasmModuleBindings {
85
85
  [key: string]: string;
86
86
  }
87
87
 
@@ -89,7 +89,7 @@ interface CfWasmModuleBindings {
89
89
  * A binding to a text blob (in service-worker format)
90
90
  */
91
91
 
92
- interface CfTextBlobBindings {
92
+ export interface CfTextBlobBindings {
93
93
  [key: string]: string;
94
94
  }
95
95
 
@@ -97,21 +97,21 @@ interface CfTextBlobBindings {
97
97
  * A binding to a data blob (in service-worker format)
98
98
  */
99
99
 
100
- interface CfDataBlobBindings {
100
+ export interface CfDataBlobBindings {
101
101
  [key: string]: string;
102
102
  }
103
103
 
104
104
  /**
105
105
  * A Durable Object.
106
106
  */
107
- interface CfDurableObject {
107
+ export interface CfDurableObject {
108
108
  name: string;
109
109
  class_name: string;
110
110
  script_name?: string;
111
111
  environment?: string;
112
112
  }
113
113
 
114
- interface CfR2Bucket {
114
+ export interface CfR2Bucket {
115
115
  binding: string;
116
116
  bucket_name: string;
117
117
  }
@@ -122,7 +122,7 @@ interface CfService {
122
122
  environment?: string;
123
123
  }
124
124
 
125
- interface CfWorkerNamespace {
125
+ interface CfDispatchNamespace {
126
126
  binding: string;
127
127
  namespace: string;
128
128
  }
@@ -183,7 +183,7 @@ export interface CfWorkerInit {
183
183
  durable_objects: { bindings: CfDurableObject[] } | undefined;
184
184
  r2_buckets: CfR2Bucket[] | undefined;
185
185
  services: CfService[] | undefined;
186
- worker_namespaces: CfWorkerNamespace[] | undefined;
186
+ dispatch_namespaces: CfDispatchNamespace[] | undefined;
187
187
  logfwdr: CfLogfwdr | undefined;
188
188
  unsafe: CfUnsafeBinding[] | undefined;
189
189
  };
@@ -191,6 +191,7 @@ export interface CfWorkerInit {
191
191
  compatibility_date: string | undefined;
192
192
  compatibility_flags: string[] | undefined;
193
193
  usage_model: "bundled" | "unbound" | undefined;
194
+ keep_bindings: boolean | undefined;
194
195
  }
195
196
 
196
197
  export interface CfWorkerContext {
package/src/zones.ts CHANGED
@@ -74,3 +74,94 @@ export async function getZoneIdFromHost(host: string): Promise<string> {
74
74
 
75
75
  throw new Error(`Could not find zone for ${host}`);
76
76
  }
77
+
78
+ /**
79
+ * An object holding information about an assigned worker route, returned from the API
80
+ */
81
+ interface WorkerRoute {
82
+ id: string;
83
+ pattern: string;
84
+ script: string;
85
+ }
86
+
87
+ /**
88
+ * Given a zone within the user's account, return a list of all assigned worker routes
89
+ */
90
+ export async function getRoutesForZone(zone: string): Promise<WorkerRoute[]> {
91
+ const routes = await fetchListResult<WorkerRoute>(
92
+ `/zones/${zone}/workers/routes`
93
+ );
94
+ return routes;
95
+ }
96
+
97
+ /**
98
+ * Given two strings, return the levenshtein distance between them as a simple text match heuristic
99
+ */
100
+ function distanceBetween(a: string, b: string, cache = new Map()): number {
101
+ if (cache.has(`${a}|${b}`)) {
102
+ return cache.get(`${a}|${b}`);
103
+ }
104
+ let result;
105
+ if (b == "") {
106
+ result = a.length;
107
+ } else if (a == "") {
108
+ result = b.length;
109
+ } else if (a[0] === b[0]) {
110
+ result = distanceBetween(a.slice(1), b.slice(1), cache);
111
+ } else {
112
+ result =
113
+ 1 +
114
+ Math.min(
115
+ distanceBetween(a.slice(1), b, cache),
116
+ distanceBetween(a, b.slice(1), cache),
117
+ distanceBetween(a.slice(1), b.slice(1), cache)
118
+ );
119
+ }
120
+ cache.set(`${a}|${b}`, result);
121
+ return result;
122
+ }
123
+
124
+ /**
125
+ * Given an invalid route, sort the valid routes by closeness to the invalid route (levenstein distance)
126
+ */
127
+ export function findClosestRoute(
128
+ providedRoute: string,
129
+ assignedRoutes: WorkerRoute[]
130
+ ): WorkerRoute[] {
131
+ return assignedRoutes.sort((a, b) => {
132
+ const distanceA = distanceBetween(providedRoute, a.pattern);
133
+ const distanceB = distanceBetween(providedRoute, b.pattern);
134
+ return distanceA - distanceB;
135
+ });
136
+ }
137
+
138
+ /**
139
+ * Given a route (must be assigned and within the correct zone), return the name of the worker assigned to it
140
+ */
141
+ export async function getWorkerForZone(worker: string) {
142
+ const zone = await getZoneForRoute(worker);
143
+ if (!zone) {
144
+ throw new Error(
145
+ `The route '${worker}' is not part of one of your zones. Either add this zone from the Cloudflare dashboard, or try using a route within one of your existing zones.`
146
+ );
147
+ }
148
+ const routes = await getRoutesForZone(zone.id);
149
+
150
+ const scriptName = routes.find((route) => route.pattern === worker)?.script;
151
+
152
+ if (!scriptName) {
153
+ const closestRoute = findClosestRoute(worker, routes)?.[0];
154
+
155
+ if (!closestRoute) {
156
+ throw new Error(
157
+ `The route '${worker}' has no workers assigned. You can assign a worker to it from wrangler.toml or the Cloudflare dashboard`
158
+ );
159
+ } else {
160
+ throw new Error(
161
+ `The route '${worker}' has no workers assigned. Did you mean to tail the route '${closestRoute.pattern}'?`
162
+ );
163
+ }
164
+ }
165
+
166
+ return scriptName;
167
+ }
@@ -0,0 +1,62 @@
1
+ export type Awaitable<T> = T | Promise<T>;
2
+ // TODO: allow dispatching more events?
3
+ export type Dispatcher = (
4
+ type: "scheduled",
5
+ init: { cron?: string }
6
+ ) => Awaitable<void>;
7
+
8
+ export interface MiddlewareContext {
9
+ dispatch: Dispatcher;
10
+ next(request: Request, env: any): Awaitable<Response>;
11
+ }
12
+
13
+ export type Middleware = (
14
+ request: Request,
15
+ env: any,
16
+ ctx: ExecutionContext,
17
+ middlewareCtx: MiddlewareContext
18
+ ) => Awaitable<Response>;
19
+
20
+ const __facade_middleware__: Middleware[] = [];
21
+
22
+ // The register functions allow for the insertion of one or many middleware,
23
+ // We register internal middleware first in the stack, but have no way of controlling
24
+ // the order that addMiddleware is run in service workers so need an internal function.
25
+ export function __facade_register__(...args: (Middleware | Middleware[])[]) {
26
+ __facade_middleware__.push(...args.flat());
27
+ }
28
+ export function __facade_registerInternal__(
29
+ ...args: (Middleware | Middleware[])[]
30
+ ) {
31
+ __facade_middleware__.unshift(...args.flat());
32
+ }
33
+
34
+ function __facade_invokeChain__(
35
+ request: Request,
36
+ env: any,
37
+ ctx: ExecutionContext,
38
+ dispatch: Dispatcher,
39
+ middlewareChain: Middleware[]
40
+ ): Awaitable<Response> {
41
+ const [head, ...tail] = middlewareChain;
42
+ const middlewareCtx: MiddlewareContext = {
43
+ dispatch,
44
+ next(newRequest, newEnv) {
45
+ return __facade_invokeChain__(newRequest, newEnv, ctx, dispatch, tail);
46
+ },
47
+ };
48
+ return head(request, env, ctx, middlewareCtx);
49
+ }
50
+
51
+ export function __facade_invoke__(
52
+ request: Request,
53
+ env: any,
54
+ ctx: ExecutionContext,
55
+ dispatch: Dispatcher,
56
+ finalMiddleware: Middleware
57
+ ): Awaitable<Response> {
58
+ return __facade_invokeChain__(request, env, ctx, dispatch, [
59
+ ...__facade_middleware__,
60
+ finalMiddleware,
61
+ ]);
62
+ }
@@ -0,0 +1,84 @@
1
+ // // This loads all middlewares exposed on the middleware object
2
+ // // and then starts the invocation chain.
3
+ // // The big idea is that we can add these to the middleware export dynamically
4
+ // // through wrangler, or we can potentially let users directly add them as a sort
5
+ // // of "plugin" system.
6
+
7
+ import {
8
+ Dispatcher,
9
+ Middleware,
10
+ __facade_invoke__,
11
+ __facade_register__,
12
+ } from "./common";
13
+
14
+ // @ts-expect-error We'll swap in the entry point during build
15
+ import worker from "__ENTRY_POINT__";
16
+
17
+ // We need to preserve all of the exports from the worker
18
+ // @ts-expect-error We'll swap in the entry point during build
19
+ export * from "__ENTRY_POINT__";
20
+
21
+ class __Facade_ScheduledController__ implements ScheduledController {
22
+ #noRetry: ScheduledController["noRetry"];
23
+
24
+ constructor(
25
+ readonly scheduledTime: number,
26
+ readonly cron: string,
27
+ noRetry: ScheduledController["noRetry"]
28
+ ) {
29
+ this.#noRetry = noRetry;
30
+ }
31
+
32
+ noRetry() {
33
+ if (!(this instanceof __Facade_ScheduledController__)) {
34
+ throw new TypeError("Illegal invocation");
35
+ }
36
+ // Need to call native method immediately in case uncaught error thrown
37
+ this.#noRetry();
38
+ }
39
+ }
40
+
41
+ const __facade_modules_fetch__: Middleware = function (request, env, ctx) {
42
+ if (worker.fetch === undefined) throw new Error("No fetch handler!"); // TODO: proper error message
43
+ return worker.fetch(request, env, ctx);
44
+ };
45
+
46
+ const facade: ExportedHandler<unknown> = {
47
+ fetch(request, env, ctx) {
48
+ // Get the chain of middleware from the worker object
49
+ if (worker.middleware && worker.middleware.length > 0) {
50
+ for (const middleware of worker.middleware) {
51
+ __facade_register__(middleware);
52
+ }
53
+
54
+ const __facade_modules_dispatch__: Dispatcher = function (type, init) {
55
+ if (type === "scheduled" && worker.scheduled !== undefined) {
56
+ const controller = new __Facade_ScheduledController__(
57
+ Date.now(),
58
+ init.cron ?? "",
59
+ () => {}
60
+ );
61
+ return worker.scheduled(controller, env, ctx);
62
+ }
63
+ };
64
+
65
+ return __facade_invoke__(
66
+ request,
67
+ env,
68
+ ctx,
69
+ __facade_modules_dispatch__,
70
+ __facade_modules_fetch__
71
+ );
72
+ } else {
73
+ // We didn't have any middleware so we can skip the invocation chain,
74
+ // and just call the fetch handler directly
75
+
76
+ // We "don't care" if this is undefined as we want to have the same behaviour
77
+ // as if the worker completely bypassed middleware.
78
+ return worker.fetch!(request, env, ctx);
79
+ }
80
+ },
81
+ scheduled: worker.scheduled,
82
+ };
83
+
84
+ export default facade;
@@ -0,0 +1,213 @@
1
+ import { Awaitable, Dispatcher, Middleware, __facade_invoke__ } from "./common";
2
+ export { __facade_register__, __facade_registerInternal__ } from "./common";
3
+
4
+ // Miniflare's `EventTarget` follows the spec and doesn't allow exceptions to
5
+ // be caught by `dispatchEvent`. Instead it has a custom`ThrowingEventTarget`
6
+ // class that rethrows errors from event listeners in `dispatchEvent`.
7
+ // We'd like errors to be propagated to the top-level `addEventListener`, so
8
+ // we'd like to use `ThrowingEventTarget`. Unfortunately, `ThrowingEventTarget`
9
+ // isn't exposed on the global scope, but `WorkerGlobalScope` (which extends
10
+ // `ThrowingEventTarget`) is. Therefore, we get at it in this nasty way.
11
+ let __FACADE_EVENT_TARGET__: EventTarget;
12
+ if ((globalThis as any).MINIFLARE) {
13
+ __FACADE_EVENT_TARGET__ = new (Object.getPrototypeOf(WorkerGlobalScope))();
14
+ } else {
15
+ __FACADE_EVENT_TARGET__ = new EventTarget();
16
+ }
17
+
18
+ declare global {
19
+ var __facade_addEventListener__: (
20
+ type: string,
21
+ listener: EventListenerOrEventListenerObject,
22
+ options?: EventTargetAddEventListenerOptions | boolean
23
+ ) => void;
24
+ var __facade_removeEventListener__: (
25
+ type: string,
26
+ listener: EventListenerOrEventListenerObject,
27
+ options?: EventTargetEventListenerOptions | boolean
28
+ ) => void;
29
+ var __facade_dispatchEvent__: (event: Event) => void;
30
+ }
31
+
32
+ function __facade_isSpecialEvent__(type: string) {
33
+ return type === "fetch" || type === "scheduled";
34
+ }
35
+ globalThis.__facade_addEventListener__ = function (type, listener, options) {
36
+ if (__facade_isSpecialEvent__(type)) {
37
+ __FACADE_EVENT_TARGET__.addEventListener(type, listener, options);
38
+ } else {
39
+ globalThis.addEventListener(type as any, listener, options);
40
+ }
41
+ };
42
+ globalThis.__facade_removeEventListener__ = function (type, listener, options) {
43
+ if (__facade_isSpecialEvent__(type)) {
44
+ __FACADE_EVENT_TARGET__.removeEventListener(type, listener, options);
45
+ } else {
46
+ globalThis.removeEventListener(type as any, listener, options);
47
+ }
48
+ };
49
+ globalThis.__facade_dispatchEvent__ = function (event) {
50
+ if (__facade_isSpecialEvent__(event.type)) {
51
+ __FACADE_EVENT_TARGET__.dispatchEvent(event);
52
+ } else {
53
+ globalThis.dispatchEvent(event as any);
54
+ }
55
+ };
56
+
57
+ const __facade_waitUntil__ = Symbol("__facade_waitUntil__");
58
+ const __facade_response__ = Symbol("__facade_response__");
59
+ const __facade_dispatched__ = Symbol("__facade_dispatched__");
60
+
61
+ class __Facade_ExtendableEvent__ extends Event {
62
+ [__facade_waitUntil__]: Awaitable<unknown>[] = [];
63
+
64
+ waitUntil(promise: Awaitable<any>) {
65
+ if (!(this instanceof __Facade_ExtendableEvent__)) {
66
+ throw new TypeError("Illegal invocation");
67
+ }
68
+ this[__facade_waitUntil__].push(promise);
69
+ }
70
+ }
71
+
72
+ interface FetchEventInit extends EventInit {
73
+ request: Request;
74
+ passThroughOnException: FetchEvent["passThroughOnException"];
75
+ }
76
+
77
+ class __Facade_FetchEvent__ extends __Facade_ExtendableEvent__ {
78
+ #request: Request;
79
+ #passThroughOnException: FetchEvent["passThroughOnException"];
80
+ [__facade_response__]?: Awaitable<Response>;
81
+ [__facade_dispatched__] = false;
82
+
83
+ constructor(type: "fetch", init: FetchEventInit) {
84
+ super(type);
85
+ this.#request = init.request;
86
+ this.#passThroughOnException = init.passThroughOnException;
87
+ }
88
+
89
+ get request() {
90
+ return this.#request;
91
+ }
92
+
93
+ respondWith(response: Awaitable<Response>) {
94
+ if (!(this instanceof __Facade_FetchEvent__)) {
95
+ throw new TypeError("Illegal invocation");
96
+ }
97
+ if (this[__facade_response__] !== undefined) {
98
+ throw new DOMException(
99
+ "FetchEvent.respondWith() has already been called; it can only be called once.",
100
+ "InvalidStateError"
101
+ );
102
+ }
103
+ if (this[__facade_dispatched__]) {
104
+ throw new DOMException(
105
+ "Too late to call FetchEvent.respondWith(). It must be called synchronously in the event handler.",
106
+ "InvalidStateError"
107
+ );
108
+ }
109
+ this.stopImmediatePropagation();
110
+ this[__facade_response__] = response;
111
+ }
112
+
113
+ passThroughOnException() {
114
+ if (!(this instanceof __Facade_FetchEvent__)) {
115
+ throw new TypeError("Illegal invocation");
116
+ }
117
+ // Need to call native method immediately in case uncaught error thrown
118
+ this.#passThroughOnException();
119
+ }
120
+ }
121
+
122
+ interface ScheduledEventInit extends EventInit {
123
+ scheduledTime: number;
124
+ cron: string;
125
+ noRetry: ScheduledEvent["noRetry"];
126
+ }
127
+
128
+ class __Facade_ScheduledEvent__ extends __Facade_ExtendableEvent__ {
129
+ #scheduledTime: number;
130
+ #cron: string;
131
+ #noRetry: ScheduledEvent["noRetry"];
132
+
133
+ constructor(type: "scheduled", init: ScheduledEventInit) {
134
+ super(type);
135
+ this.#scheduledTime = init.scheduledTime;
136
+ this.#cron = init.cron;
137
+ this.#noRetry = init.noRetry;
138
+ }
139
+
140
+ get scheduledTime() {
141
+ return this.#scheduledTime;
142
+ }
143
+
144
+ get cron() {
145
+ return this.#cron;
146
+ }
147
+
148
+ noRetry() {
149
+ if (!(this instanceof __Facade_ScheduledEvent__)) {
150
+ throw new TypeError("Illegal invocation");
151
+ }
152
+ // Need to call native method immediately in case uncaught error thrown
153
+ this.#noRetry();
154
+ }
155
+ }
156
+
157
+ globalThis.addEventListener("fetch", (event) => {
158
+ const ctx: ExecutionContext = {
159
+ waitUntil: event.waitUntil.bind(event),
160
+ passThroughOnException: event.passThroughOnException.bind(event),
161
+ };
162
+
163
+ const __facade_sw_dispatch__: Dispatcher = function (type, init) {
164
+ if (type === "scheduled") {
165
+ const facadeEvent = new __Facade_ScheduledEvent__("scheduled", {
166
+ scheduledTime: Date.now(),
167
+ cron: init.cron ?? "",
168
+ noRetry() {},
169
+ });
170
+
171
+ __FACADE_EVENT_TARGET__.dispatchEvent(facadeEvent);
172
+ event.waitUntil(Promise.all(facadeEvent[__facade_waitUntil__]));
173
+ }
174
+ };
175
+
176
+ const __facade_sw_fetch__: Middleware = function (request, _env, ctx) {
177
+ const facadeEvent = new __Facade_FetchEvent__("fetch", {
178
+ request,
179
+ passThroughOnException: ctx.passThroughOnException,
180
+ });
181
+
182
+ __FACADE_EVENT_TARGET__.dispatchEvent(facadeEvent);
183
+ facadeEvent[__facade_dispatched__] = true;
184
+ event.waitUntil(Promise.all(facadeEvent[__facade_waitUntil__]));
185
+
186
+ const response = facadeEvent[__facade_response__];
187
+ if (response === undefined) {
188
+ throw new Error("No response!"); // TODO: proper error message
189
+ }
190
+ return response;
191
+ };
192
+
193
+ event.respondWith(
194
+ __facade_invoke__(
195
+ event.request,
196
+ globalThis,
197
+ ctx,
198
+ __facade_sw_dispatch__,
199
+ __facade_sw_fetch__
200
+ )
201
+ );
202
+ });
203
+
204
+ globalThis.addEventListener("scheduled", (event) => {
205
+ const facadeEvent = new __Facade_ScheduledEvent__("scheduled", {
206
+ scheduledTime: event.scheduledTime,
207
+ cron: event.cron,
208
+ noRetry: event.noRetry.bind(event),
209
+ });
210
+
211
+ __FACADE_EVENT_TARGET__.dispatchEvent(facadeEvent);
212
+ event.waitUntil(Promise.all(facadeEvent[__facade_waitUntil__]));
213
+ });
@@ -0,0 +1,40 @@
1
+ import type { Middleware } from "./common";
2
+
3
+ // A middleware has to be a function of type Middleware
4
+ const prettyError: Middleware = async (request, env, _ctx, middlewareCtx) => {
5
+ try {
6
+ const response = await middlewareCtx.next(request, env);
7
+ return response;
8
+ } catch (e: any) {
9
+ const html = `
10
+ <!DOCTYPE html>
11
+ <html lang="en">
12
+ <head>
13
+ <meta charset="UTF-8">
14
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
15
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
16
+ <title>Error 🚨</title>
17
+ <style>
18
+ pre {
19
+ margin: 16px auto;
20
+ max-width: 600px;
21
+ background-color: #eeeeee;
22
+ border-radius: 4px;
23
+ padding: 16px;
24
+ }
25
+ </style>
26
+ </head>
27
+ <body>
28
+ <pre>${e.stack}</pre>
29
+ </body>
30
+ </html>
31
+ `;
32
+
33
+ return new Response(html, {
34
+ status: 500,
35
+ headers: { "Content-Type": "text/html;charset=utf-8" },
36
+ });
37
+ }
38
+ };
39
+
40
+ export default prettyError;
@@ -0,0 +1,14 @@
1
+ import type { Middleware } from "./common";
2
+
3
+ // A middleware has to be a function of type Middleware
4
+ const scheduled: Middleware = async (request, env, _ctx, middlewareCtx) => {
5
+ const url = new URL(request.url);
6
+ if (url.pathname === "/__scheduled") {
7
+ const cron = url.searchParams.get("cron") ?? "";
8
+ await middlewareCtx.dispatch("scheduled", { cron });
9
+ return new Response("OK");
10
+ }
11
+ return middlewareCtx.next(request, env);
12
+ };
13
+
14
+ export default scheduled;