alchemy 0.39.0 → 0.40.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 (158) hide show
  1. package/lib/alchemy.d.ts +6 -0
  2. package/lib/alchemy.d.ts.map +1 -1
  3. package/lib/alchemy.js +5 -0
  4. package/lib/alchemy.js.map +1 -1
  5. package/lib/apply.js +1 -1
  6. package/lib/apply.js.map +1 -1
  7. package/lib/build-date.d.ts +1 -1
  8. package/lib/build-date.js +1 -1
  9. package/lib/cloudflare/bucket.d.ts +10 -0
  10. package/lib/cloudflare/bucket.d.ts.map +1 -1
  11. package/lib/cloudflare/bucket.js +1 -0
  12. package/lib/cloudflare/bucket.js.map +1 -1
  13. package/lib/cloudflare/bundle/bundle-worker-dev.d.ts +25 -0
  14. package/lib/cloudflare/bundle/bundle-worker-dev.d.ts.map +1 -0
  15. package/lib/cloudflare/bundle/bundle-worker-dev.js +85 -0
  16. package/lib/cloudflare/bundle/bundle-worker-dev.js.map +1 -0
  17. package/lib/cloudflare/bundle/bundle-worker.d.ts.map +1 -1
  18. package/lib/cloudflare/bundle/bundle-worker.js +0 -3
  19. package/lib/cloudflare/bundle/bundle-worker.js.map +1 -1
  20. package/lib/cloudflare/bundle/nodejs-compat-mode.d.ts +1 -2
  21. package/lib/cloudflare/bundle/nodejs-compat-mode.d.ts.map +1 -1
  22. package/lib/cloudflare/bundle/nodejs-compat-mode.js +6 -1
  23. package/lib/cloudflare/bundle/nodejs-compat-mode.js.map +1 -1
  24. package/lib/cloudflare/d1-database.d.ts +10 -0
  25. package/lib/cloudflare/d1-database.d.ts.map +1 -1
  26. package/lib/cloudflare/d1-database.js +1 -0
  27. package/lib/cloudflare/d1-database.js.map +1 -1
  28. package/lib/cloudflare/do-state-store/internal.d.ts +1 -0
  29. package/lib/cloudflare/do-state-store/internal.d.ts.map +1 -1
  30. package/lib/cloudflare/do-state-store/internal.js +27 -29
  31. package/lib/cloudflare/do-state-store/internal.js.map +1 -1
  32. package/lib/cloudflare/do-state-store/store.d.ts.map +1 -1
  33. package/lib/cloudflare/do-state-store/store.js +7 -20
  34. package/lib/cloudflare/do-state-store/store.js.map +1 -1
  35. package/lib/cloudflare/images.d.ts +12 -0
  36. package/lib/cloudflare/images.d.ts.map +1 -1
  37. package/lib/cloudflare/images.js +4 -0
  38. package/lib/cloudflare/images.js.map +1 -1
  39. package/lib/cloudflare/kv-namespace.d.ts +10 -0
  40. package/lib/cloudflare/kv-namespace.d.ts.map +1 -1
  41. package/lib/cloudflare/kv-namespace.js +1 -0
  42. package/lib/cloudflare/kv-namespace.js.map +1 -1
  43. package/lib/cloudflare/queue.d.ts +10 -0
  44. package/lib/cloudflare/queue.d.ts.map +1 -1
  45. package/lib/cloudflare/queue.js +1 -0
  46. package/lib/cloudflare/queue.js.map +1 -1
  47. package/lib/cloudflare/vite.d.ts.map +1 -1
  48. package/lib/cloudflare/vite.js +12 -0
  49. package/lib/cloudflare/vite.js.map +1 -1
  50. package/lib/cloudflare/website.d.ts +7 -0
  51. package/lib/cloudflare/website.d.ts.map +1 -1
  52. package/lib/cloudflare/website.js +16 -6
  53. package/lib/cloudflare/website.js.map +1 -1
  54. package/lib/cloudflare/worker/get-worker-template.d.ts +2 -0
  55. package/lib/cloudflare/worker/get-worker-template.d.ts.map +1 -0
  56. package/lib/cloudflare/worker/get-worker-template.js +12 -0
  57. package/lib/cloudflare/worker/get-worker-template.js.map +1 -0
  58. package/lib/cloudflare/worker/http-server.d.ts +14 -0
  59. package/lib/cloudflare/worker/http-server.d.ts.map +1 -0
  60. package/lib/cloudflare/worker/http-server.js +67 -0
  61. package/lib/cloudflare/worker/http-server.js.map +1 -0
  62. package/lib/cloudflare/worker/miniflare-worker-options.d.ts +13 -0
  63. package/lib/cloudflare/worker/miniflare-worker-options.d.ts.map +1 -0
  64. package/lib/cloudflare/worker/miniflare-worker-options.js +408 -0
  65. package/lib/cloudflare/worker/miniflare-worker-options.js.map +1 -0
  66. package/lib/cloudflare/worker/miniflare.d.ts +31 -0
  67. package/lib/cloudflare/worker/miniflare.d.ts.map +1 -0
  68. package/lib/cloudflare/worker/miniflare.js +150 -0
  69. package/lib/cloudflare/worker/miniflare.js.map +1 -0
  70. package/lib/cloudflare/worker/mixed-mode.d.ts +17 -0
  71. package/lib/cloudflare/worker/mixed-mode.d.ts.map +1 -0
  72. package/lib/cloudflare/worker/mixed-mode.js +145 -0
  73. package/lib/cloudflare/worker/mixed-mode.js.map +1 -0
  74. package/lib/cloudflare/worker/subdomain.d.ts +3 -0
  75. package/lib/cloudflare/worker/subdomain.d.ts.map +1 -0
  76. package/lib/cloudflare/worker/subdomain.js +10 -0
  77. package/lib/cloudflare/worker/subdomain.js.map +1 -0
  78. package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
  79. package/lib/cloudflare/worker-metadata.js +7 -3
  80. package/lib/cloudflare/worker-metadata.js.map +1 -1
  81. package/lib/cloudflare/worker.d.ts +23 -1
  82. package/lib/cloudflare/worker.d.ts.map +1 -1
  83. package/lib/cloudflare/worker.js +229 -8
  84. package/lib/cloudflare/worker.js.map +1 -1
  85. package/lib/cloudflare/workflow.d.ts +7 -0
  86. package/lib/cloudflare/workflow.d.ts.map +1 -1
  87. package/lib/cloudflare/workflow.js.map +1 -1
  88. package/lib/destroy.d.ts.map +1 -1
  89. package/lib/destroy.js +9 -5
  90. package/lib/destroy.js.map +1 -1
  91. package/lib/scope.d.ts +2 -0
  92. package/lib/scope.d.ts.map +1 -1
  93. package/lib/scope.js +10 -2
  94. package/lib/scope.js.map +1 -1
  95. package/lib/util/assert-never.d.ts +1 -1
  96. package/lib/util/assert-never.d.ts.map +1 -1
  97. package/lib/util/assert-never.js +2 -2
  98. package/lib/util/assert-never.js.map +1 -1
  99. package/lib/util/cli.d.ts +21 -7
  100. package/lib/util/cli.d.ts.map +1 -1
  101. package/lib/util/cli.js +8 -0
  102. package/lib/util/cli.js.map +1 -1
  103. package/lib/util/deferred-promise.d.ts +8 -0
  104. package/lib/util/deferred-promise.d.ts.map +1 -0
  105. package/lib/util/deferred-promise.js +16 -0
  106. package/lib/util/deferred-promise.js.map +1 -0
  107. package/lib/util/detect-package-manager.d.ts +3 -0
  108. package/lib/util/detect-package-manager.d.ts.map +1 -0
  109. package/lib/util/detect-package-manager.js +25 -0
  110. package/lib/util/detect-package-manager.js.map +1 -0
  111. package/lib/util/logger.d.ts.map +1 -1
  112. package/lib/util/logger.js +1 -0
  113. package/lib/util/logger.js.map +1 -1
  114. package/lib/util/memoize.d.ts +4 -0
  115. package/lib/util/memoize.d.ts.map +1 -0
  116. package/lib/util/memoize.js +17 -0
  117. package/lib/util/memoize.js.map +1 -0
  118. package/package.json +7 -5
  119. package/src/alchemy.ts +14 -0
  120. package/src/apply.ts +2 -1
  121. package/src/build-date.ts +1 -1
  122. package/src/cloudflare/bucket.ts +12 -0
  123. package/src/cloudflare/bundle/bundle-worker-dev.ts +126 -0
  124. package/src/cloudflare/bundle/bundle-worker.ts +0 -5
  125. package/src/cloudflare/bundle/nodejs-compat-mode.ts +12 -3
  126. package/src/cloudflare/d1-database.ts +11 -0
  127. package/src/cloudflare/do-state-store/internal.ts +30 -31
  128. package/src/cloudflare/do-state-store/store.ts +9 -26
  129. package/src/cloudflare/images.ts +14 -0
  130. package/src/cloudflare/kv-namespace.ts +12 -0
  131. package/src/cloudflare/queue.ts +12 -0
  132. package/src/cloudflare/vite.ts +12 -0
  133. package/src/cloudflare/website.ts +25 -6
  134. package/src/cloudflare/worker/get-worker-template.ts +14 -0
  135. package/src/cloudflare/worker/http-server.ts +80 -0
  136. package/src/cloudflare/worker/miniflare-worker-options.ts +531 -0
  137. package/src/cloudflare/worker/miniflare.ts +198 -0
  138. package/src/cloudflare/worker/mixed-mode.ts +220 -0
  139. package/src/cloudflare/worker/subdomain.ts +16 -0
  140. package/src/cloudflare/worker-metadata.ts +12 -4
  141. package/src/cloudflare/worker.ts +284 -18
  142. package/src/cloudflare/workflow.ts +7 -0
  143. package/src/destroy.ts +9 -5
  144. package/src/scope.ts +20 -1
  145. package/src/util/assert-never.ts +2 -2
  146. package/src/util/cli.ts +19 -7
  147. package/src/util/deferred-promise.ts +19 -0
  148. package/src/util/detect-package-manager.ts +23 -0
  149. package/src/util/logger.ts +1 -0
  150. package/src/util/memoize.ts +23 -0
  151. package/workers/do-state-store.js +826 -0
  152. package/{src/cloudflare/do-state-store/worker.ts → workers/do-state-store.ts} +55 -23
  153. package/workers/mixed-mode-proxy-worker.js +39 -0
  154. package/workers/mixed-mode-proxy-worker.ts +46 -0
  155. package/lib/cloudflare/do-state-store/worker.d.ts +0 -27
  156. package/lib/cloudflare/do-state-store/worker.d.ts.map +0 -1
  157. package/lib/cloudflare/do-state-store/worker.js +0 -179
  158. package/lib/cloudflare/do-state-store/worker.js.map +0 -1
@@ -1,11 +1,9 @@
1
- import path from "node:path";
2
- import { fileURLToPath } from "node:url";
3
- import { bundle } from "../../esbuild/index.ts";
4
- import { exists } from "../../util/exists.ts";
1
+ import { logger } from "../../util/logger.ts";
5
2
  import { withExponentialBackoff } from "../../util/retry.ts";
6
3
  import { handleApiError } from "../api-error.ts";
7
4
  import type { CloudflareApi } from "../api.ts";
8
5
  import type { WorkerMetadata } from "../worker-metadata.ts";
6
+ import { getWorkerTemplate } from "../worker/get-worker-template.ts";
9
7
  import type { DOStateStoreAPI } from "./types.ts";
10
8
 
11
9
  interface DOStateStoreClientOptions {
@@ -84,6 +82,30 @@ export class DOStateStoreClient {
84
82
  });
85
83
  }
86
84
 
85
+ async waitUntilReady(): Promise<void> {
86
+ // This ensures the token is correct and the worker is ready to use.
87
+ let last: Response | undefined;
88
+ let delay = 1000;
89
+ for (let i = 0; i < 20; i++) {
90
+ const res = await this.validate();
91
+ if (res.ok) {
92
+ return;
93
+ }
94
+ if (!last) {
95
+ logger.log("Waiting for state store deployment...");
96
+ }
97
+ last = res;
98
+ // Exponential backoff with jitter
99
+ const jitter = Math.random() * 0.1 * delay;
100
+ await new Promise((resolve) => setTimeout(resolve, delay + jitter));
101
+ delay *= 1.5; // Increase the delay for next attempt
102
+ delay = Math.min(delay, 10000); // Cap at 10 seconds
103
+ }
104
+ throw new Error(
105
+ `Failed to access state store: ${last?.status} ${last?.statusText}`,
106
+ );
107
+ }
108
+
87
109
  async fetch(path: string, init: RequestInit = {}): Promise<Response> {
88
110
  const url = new URL(path, this.options.url);
89
111
  url.searchParams.set("app", this.options.app);
@@ -98,7 +120,7 @@ export class DOStateStoreClient {
98
120
  }
99
121
  }
100
122
 
101
- const TAG = "alchemy-state-store:2025-06-03";
123
+ const TAG = "alchemy-state-store:2025-06-23";
102
124
 
103
125
  const cache = new Map<string, string>();
104
126
 
@@ -119,12 +141,13 @@ export async function upsertStateStoreWorker(
119
141
  return;
120
142
  }
121
143
  const formData = new FormData();
122
- formData.append("worker.js", await bundleWorkerScript());
144
+ const worker = await getWorkerTemplate("do-state-store");
145
+ formData.append(worker.name, worker);
123
146
  formData.append(
124
147
  "metadata",
125
148
  new Blob([
126
149
  JSON.stringify({
127
- main_module: "worker.js",
150
+ main_module: worker.name,
128
151
  compatibility_date: "2025-06-01",
129
152
  compatibility_flags: ["nodejs_compat"],
130
153
  bindings: [
@@ -217,27 +240,3 @@ export async function getAccountSubdomain(api: CloudflareApi) {
217
240
  cache.set(key, subdomain);
218
241
  return subdomain;
219
242
  }
220
-
221
- async function bundleWorkerScript() {
222
- const thisDir =
223
- typeof __dirname !== "undefined"
224
- ? __dirname
225
- : path.dirname(fileURLToPath(import.meta.url));
226
- // we can be running from .ts or .js, so resolve it defensively
227
- const workerTs = path.join(thisDir, "worker.ts");
228
- const workerJs = path.join(thisDir, "worker.js");
229
- const result = await bundle({
230
- entryPoint: (await exists(workerTs)) ? workerTs : workerJs,
231
- bundle: true,
232
- format: "esm",
233
- target: "es2022",
234
- external: ["cloudflare:*", "node:crypto"],
235
- write: false,
236
- });
237
- if (!result.outputFiles?.[0]) {
238
- throw new Error("Failed to bundle worker.ts");
239
- }
240
- return new File([result.outputFiles[0].text], "worker.js", {
241
- type: "application/javascript+module",
242
- });
243
- }
@@ -4,11 +4,8 @@ import type { Scope } from "../../scope.ts";
4
4
  import type { State, StateStore } from "../../state.ts";
5
5
  import { deserializeState } from "../../state.ts";
6
6
  import { createCloudflareApi, type CloudflareApiOptions } from "../api.ts";
7
- import {
8
- DOStateStoreClient,
9
- getAccountSubdomain,
10
- upsertStateStoreWorker,
11
- } from "./internal.ts";
7
+ import { getAccountSubdomain } from "../worker/subdomain.ts";
8
+ import { DOStateStoreClient, upsertStateStoreWorker } from "./internal.ts";
12
9
 
13
10
  export interface DOStateStoreOptions extends CloudflareApiOptions {
14
11
  /**
@@ -95,33 +92,19 @@ export class DOStateStore implements StateStore {
95
92
  this.options.worker?.force ?? false,
96
93
  ),
97
94
  ]);
95
+ if (!subdomain) {
96
+ throw new Error(
97
+ "Failed to access state store worker because the workers.dev subdomain is not available.",
98
+ );
99
+ }
98
100
  const client = new DOStateStoreClient({
99
101
  app: this.scope.appName ?? "alchemy",
100
102
  stage: this.scope.stage,
101
103
  url: `https://${workerName}.${subdomain}.workers.dev`,
102
104
  token,
103
105
  });
104
- // This ensures the token is correct and the worker is ready to use.
105
- let last: Response | undefined;
106
- let delay = 1000;
107
- for (let i = 0; i < 20; i++) {
108
- const res = await client.validate();
109
- if (res.ok) {
110
- return client;
111
- }
112
- if (!last) {
113
- console.log("Waiting for state store deployment...");
114
- }
115
- last = res;
116
- // Exponential backoff with jitter
117
- const jitter = Math.random() * 0.1 * delay;
118
- await new Promise((resolve) => setTimeout(resolve, delay + jitter));
119
- delay *= 1.5; // Increase the delay for next attempt
120
- delay = Math.min(delay, 10000); // Cap at 10 seconds
121
- }
122
- throw new Error(
123
- `Failed to access state store: ${last?.status} ${last?.statusText}`,
124
- );
106
+ await client.waitUntilReady();
107
+ return client;
125
108
  }
126
109
 
127
110
  private async getClient() {
@@ -56,4 +56,18 @@
56
56
  */
57
57
  export class Images {
58
58
  public readonly type = "images";
59
+ public readonly dev?: {
60
+ remote?: boolean;
61
+ };
62
+ constructor(props?: {
63
+ dev?: {
64
+ /**
65
+ * Whether to run the images binding remotely instead of locally
66
+ * @default false
67
+ */
68
+ remote?: boolean;
69
+ };
70
+ }) {
71
+ this.dev = props?.dev;
72
+ }
59
73
  }
@@ -41,6 +41,17 @@ export interface KVNamespaceProps extends CloudflareApiOptions {
41
41
  * @default true
42
42
  */
43
43
  delete?: boolean;
44
+
45
+ /**
46
+ * Whether to emulate the KV namespace locally when Alchemy is running in watch mode.
47
+ */
48
+ dev?: {
49
+ /**
50
+ * Whether to run the KV namespace remotely instead of locally
51
+ * @default false
52
+ */
53
+ remote?: boolean;
54
+ };
44
55
  }
45
56
 
46
57
  /**
@@ -251,6 +262,7 @@ const _KVNamespace = Resource(
251
262
  namespaceId,
252
263
  title: props.title,
253
264
  values: props.values,
265
+ dev: props.dev,
254
266
  createdAt: createdAt,
255
267
  modifiedAt: Date.now(),
256
268
  });
@@ -72,6 +72,17 @@ export interface QueueProps extends CloudflareApiOptions {
72
72
  * @default false
73
73
  */
74
74
  adopt?: boolean;
75
+
76
+ /**
77
+ * Whether to emulate the queue locally when Alchemy is running in watch mode.
78
+ */
79
+ dev?: {
80
+ /**
81
+ * Whether to run the queue remotely instead of locally
82
+ * @default false
83
+ */
84
+ remote?: boolean;
85
+ };
75
86
  }
76
87
 
77
88
  export function isQueue(eventSource: any): eventSource is Queue {
@@ -289,6 +300,7 @@ const QueueResource = Resource("cloudflare::Queue", async function <
289
300
  createdOn: queueData.result.created_on || new Date().toISOString(),
290
301
  modifiedOn: queueData.result.modified_on || new Date().toISOString(),
291
302
  accountId: api.accountId,
303
+ dev: props.dev,
292
304
  // phantom properties
293
305
  Body: undefined as T,
294
306
  Batch: undefined! as MessageBatch<T>,
@@ -1,4 +1,5 @@
1
1
  import path from "node:path";
2
+ import { detectPackageManager } from "../util/detect-package-manager.ts";
2
3
  import type { Assets } from "./assets.ts";
3
4
  import type { Bindings } from "./bindings.ts";
4
5
  import { Website, type WebsiteProps } from "./website.ts";
@@ -17,6 +18,13 @@ export async function Vite<B extends Bindings>(
17
18
  props: ViteProps<B>,
18
19
  ): Promise<Vite<B>> {
19
20
  const defaultAssets = path.join("dist", "client");
21
+ const packageManager = detectPackageManager();
22
+ const devCommand = {
23
+ npm: "npx vite dev",
24
+ bun: "bun vite dev",
25
+ pnpm: "pnpm vite dev",
26
+ yarn: "yarn vite dev",
27
+ }[packageManager];
20
28
  return Website(id, {
21
29
  ...props,
22
30
  spa: true,
@@ -27,5 +35,9 @@ export async function Vite<B extends Bindings>(
27
35
  dist: props.assets.dist ?? defaultAssets,
28
36
  }
29
37
  : (props.assets ?? defaultAssets),
38
+ dev: props.dev ?? {
39
+ command: devCommand,
40
+ url: "http://localhost:5173",
41
+ },
30
42
  });
31
43
  }
@@ -94,6 +94,14 @@ export interface WebsiteProps<B extends Bindings>
94
94
  * @default false
95
95
  */
96
96
  spa?: boolean;
97
+
98
+ /**
99
+ * Configure the command to use in development mode
100
+ */
101
+ dev?: {
102
+ command: string;
103
+ url: string;
104
+ };
97
105
  }
98
106
 
99
107
  export type Website<B extends Bindings> = B extends { ASSETS: any }
@@ -114,7 +122,7 @@ export async function Website<B extends Bindings>(
114
122
  {
115
123
  parent: Scope.current,
116
124
  },
117
- async () => {
125
+ async (scope) => {
118
126
  // directory from which all relative paths are resolved
119
127
  const cwd = path.resolve(props.cwd || process.cwd());
120
128
 
@@ -177,6 +185,13 @@ export default {
177
185
  };`,
178
186
  url: true,
179
187
  adopt: true,
188
+ dev: props.dev
189
+ ? {
190
+ command: props.dev.command,
191
+ url: props.dev.url,
192
+ cwd,
193
+ }
194
+ : undefined,
180
195
  } as WorkerProps<any> & { name: string };
181
196
 
182
197
  if (wrangler) {
@@ -196,7 +211,9 @@ export default {
196
211
  });
197
212
  }
198
213
 
199
- if (props.command) {
214
+ const isDev = scope.dev && !!props.dev;
215
+
216
+ if (props.command && !isDev) {
200
217
  await Exec("build", {
201
218
  cwd,
202
219
  command: props.command,
@@ -211,10 +228,12 @@ export default {
211
228
  ...workerProps.bindings,
212
229
  // we don't include the Assets binding until after build to make sure the asset manifest is correct
213
230
  // we generate the wrangler.json using all the bind
214
- ASSETS: await Assets("assets", {
215
- // Assets are discovered from proces.cwd(), not Website.cwd or wrangler.jsonc
216
- path: path.relative(process.cwd(), assetsDirPath),
217
- }),
231
+ ASSETS: isDev
232
+ ? undefined
233
+ : await Assets("assets", {
234
+ // Assets are discovered from proces.cwd(), not Website.cwd or wrangler.jsonc
235
+ path: path.relative(process.cwd(), assetsDirPath),
236
+ }),
218
237
  },
219
238
  } as WorkerProps<any> & { name: string })) as Website<B>;
220
239
  },
@@ -0,0 +1,14 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ export async function getWorkerTemplate(
6
+ name: "do-state-store" | "mixed-mode-proxy-worker",
7
+ ) {
8
+ const dir = dirname(fileURLToPath(import.meta.url));
9
+ const path = join(dir, "..", "..", "..", "workers", `${name}.js`);
10
+ const template = await readFile(path, "utf8");
11
+ return new File([template], `${name}.js`, {
12
+ type: "application/javascript+module",
13
+ });
14
+ }
@@ -0,0 +1,80 @@
1
+ import http from "node:http";
2
+ import type { AddressInfo } from "node:net";
3
+ import { Readable } from "node:stream";
4
+
5
+ export class HTTPServer {
6
+ server: http.Server;
7
+ ready: Promise<void>;
8
+
9
+ constructor(options: {
10
+ port?: number;
11
+ fetch: (request: Request) => Promise<Response>;
12
+ }) {
13
+ const { promise, resolve } = Promise.withResolvers<void>();
14
+ this.ready = promise;
15
+ this.server = http
16
+ .createServer(async (req, res) => {
17
+ const response = await options.fetch(toWebRequest(req));
18
+ await writeNodeResponse(res, response);
19
+ })
20
+ .listen(options.port, resolve);
21
+ }
22
+
23
+ get port() {
24
+ return (this.server.address() as AddressInfo).port;
25
+ }
26
+
27
+ get hostname() {
28
+ const address = (this.server.address() as AddressInfo)?.address;
29
+ if (address === "::") {
30
+ return "localhost";
31
+ }
32
+ return address;
33
+ }
34
+
35
+ get url() {
36
+ return `http://${this.hostname}:${this.port}`;
37
+ }
38
+
39
+ stop() {
40
+ return new Promise((resolve, reject) => {
41
+ this.server.close((err) => {
42
+ if (err) {
43
+ reject(err);
44
+ } else {
45
+ resolve(undefined);
46
+ }
47
+ });
48
+ });
49
+ }
50
+ }
51
+
52
+ function toWebRequest(request: http.IncomingMessage): Request {
53
+ const method = request.method ?? "GET";
54
+ return new Request(`http://${request.headers.host}${request.url ?? "/"}`, {
55
+ method,
56
+ headers: request.headers as Record<string, string>,
57
+ body:
58
+ ["GET", "HEAD", "OPTIONS"].includes(method) || !request.readable
59
+ ? undefined
60
+ : (Readable.toWeb(request) as unknown as BodyInit),
61
+ });
62
+ }
63
+
64
+ async function writeNodeResponse(res: http.ServerResponse, response: Response) {
65
+ res.statusCode = response.status;
66
+ response.headers.forEach((value, key) => {
67
+ res.setHeader(key, value);
68
+ });
69
+ await response.body?.pipeTo(
70
+ new WritableStream({
71
+ write(chunk) {
72
+ res.write(chunk);
73
+ },
74
+ close() {
75
+ res.end();
76
+ },
77
+ }),
78
+ );
79
+ res.end();
80
+ }