alchemy 0.48.0 → 0.48.2

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/bin/alchemy.mjs +63 -56
  2. package/lib/apply.js +1 -0
  3. package/lib/apply.js.map +1 -1
  4. package/lib/cloudflare/compatibility-date.gen.d.ts +5 -0
  5. package/lib/cloudflare/compatibility-date.gen.d.ts.map +1 -0
  6. package/lib/cloudflare/compatibility-date.gen.js +7 -0
  7. package/lib/cloudflare/compatibility-date.gen.js.map +1 -0
  8. package/lib/cloudflare/d1-migrations.d.ts.map +1 -1
  9. package/lib/cloudflare/d1-migrations.js +6 -11
  10. package/lib/cloudflare/d1-migrations.js.map +1 -1
  11. package/lib/cloudflare/index.d.ts +1 -0
  12. package/lib/cloudflare/index.d.ts.map +1 -1
  13. package/lib/cloudflare/index.js +1 -0
  14. package/lib/cloudflare/index.js.map +1 -1
  15. package/lib/cloudflare/miniflare/miniflare-worker-options.d.ts +2 -2
  16. package/lib/cloudflare/miniflare/miniflare-worker-options.d.ts.map +1 -1
  17. package/lib/cloudflare/miniflare/miniflare-worker-options.js +11 -2
  18. package/lib/cloudflare/miniflare/miniflare-worker-options.js.map +1 -1
  19. package/lib/cloudflare/miniflare/miniflare-worker-proxy.d.ts +20 -0
  20. package/lib/cloudflare/miniflare/miniflare-worker-proxy.d.ts.map +1 -0
  21. package/lib/cloudflare/miniflare/miniflare-worker-proxy.js +80 -0
  22. package/lib/cloudflare/miniflare/miniflare-worker-proxy.js.map +1 -0
  23. package/lib/cloudflare/miniflare/miniflare.d.ts +6 -6
  24. package/lib/cloudflare/miniflare/miniflare.d.ts.map +1 -1
  25. package/lib/cloudflare/miniflare/miniflare.js +25 -16
  26. package/lib/cloudflare/miniflare/miniflare.js.map +1 -1
  27. package/lib/cloudflare/miniflare/remote-binding-proxy.d.ts +10 -10
  28. package/lib/cloudflare/miniflare/remote-binding-proxy.d.ts.map +1 -1
  29. package/lib/cloudflare/miniflare/remote-binding-proxy.js +34 -44
  30. package/lib/cloudflare/miniflare/remote-binding-proxy.js.map +1 -1
  31. package/lib/cloudflare/oauth.js +3 -3
  32. package/lib/cloudflare/oauth.js.map +1 -1
  33. package/lib/cloudflare/website.d.ts.map +1 -1
  34. package/lib/cloudflare/website.js +2 -1
  35. package/lib/cloudflare/website.js.map +1 -1
  36. package/lib/cloudflare/worker.d.ts +1 -2
  37. package/lib/cloudflare/worker.d.ts.map +1 -1
  38. package/lib/cloudflare/worker.js +3 -10
  39. package/lib/cloudflare/worker.js.map +1 -1
  40. package/lib/scope.d.ts +6 -0
  41. package/lib/scope.d.ts.map +1 -1
  42. package/lib/scope.js +15 -1
  43. package/lib/scope.js.map +1 -1
  44. package/lib/state/cloudflare-state-store.d.ts.map +1 -1
  45. package/lib/state/cloudflare-state-store.js +2 -2
  46. package/lib/state/cloudflare-state-store.js.map +1 -1
  47. package/lib/util/http.d.ts +13 -0
  48. package/lib/util/http.d.ts.map +1 -0
  49. package/lib/util/http.js +78 -0
  50. package/lib/util/http.js.map +1 -0
  51. package/package.json +3 -3
  52. package/src/apply.ts +1 -0
  53. package/src/cloudflare/compatibility-date.gen.ts +7 -0
  54. package/src/cloudflare/d1-migrations.ts +8 -21
  55. package/src/cloudflare/index.ts +1 -0
  56. package/src/cloudflare/miniflare/miniflare-worker-options.ts +13 -1
  57. package/src/cloudflare/miniflare/miniflare-worker-proxy.ts +97 -0
  58. package/src/cloudflare/miniflare/miniflare.ts +30 -21
  59. package/src/cloudflare/miniflare/remote-binding-proxy.ts +40 -52
  60. package/src/cloudflare/oauth.ts +3 -3
  61. package/src/cloudflare/website.ts +2 -6
  62. package/src/cloudflare/worker.ts +5 -12
  63. package/src/scope.ts +17 -1
  64. package/src/state/cloudflare-state-store.ts +2 -2
  65. package/src/util/http.ts +94 -0
  66. package/lib/build-date.d.ts +0 -6
  67. package/lib/build-date.d.ts.map +0 -1
  68. package/lib/build-date.js +0 -8
  69. package/lib/build-date.js.map +0 -1
  70. package/lib/util/http-server.d.ts +0 -14
  71. package/lib/util/http-server.d.ts.map +0 -1
  72. package/lib/util/http-server.js +0 -71
  73. package/lib/util/http-server.js.map +0 -1
  74. package/src/build-date.ts +0 -8
  75. package/src/util/http-server.ts +0 -84
@@ -0,0 +1,97 @@
1
+ import type http from "node:http";
2
+ import { WebSocket, WebSocketServer } from "ws";
3
+ import { HTTPServer } from "../../util/http.ts";
4
+
5
+ export interface MiniflareWorkerProxyOptions {
6
+ /**
7
+ * Used to proxy websocket connections to the worker.
8
+ * (This is a hack; used because trying to interact with `response.webSocket` doesn't work on Bun.)
9
+ */
10
+ getDirectURL: () => Promise<URL>;
11
+ /** Used to proxy HTTP requests to the worker. */
12
+ fetch: (request: Request) => Promise<Response>;
13
+ }
14
+
15
+ export class MiniflareWorkerProxy extends HTTPServer {
16
+ wsServer: WebSocketServer;
17
+ wsReconnectAttempts = new Map<string, number>();
18
+
19
+ constructor(private readonly options: MiniflareWorkerProxyOptions) {
20
+ super({
21
+ fetch: options.fetch,
22
+ });
23
+ this.wsServer = new WebSocketServer({ noServer: true });
24
+ this.httpServer.on("upgrade", async (req, socket, head) => {
25
+ this.wsServer.handleUpgrade(req, socket, head, async (client) => {
26
+ const id = crypto.randomUUID();
27
+ await this.handleUpgrade(id, client, req);
28
+ });
29
+ });
30
+ }
31
+
32
+ private async handleUpgrade(
33
+ id: string,
34
+ client: WebSocket,
35
+ request: http.IncomingMessage,
36
+ ) {
37
+ const attempt = this.wsReconnectAttempts.get(id) ?? 0;
38
+ this.wsReconnectAttempts.set(id, attempt + 1);
39
+ if (attempt > 3) {
40
+ client.close(1006, "Too many reconnect attempts");
41
+ return;
42
+ }
43
+ if (attempt > 0) {
44
+ const delay = attempt * 1000;
45
+ await new Promise((resolve) => setTimeout(resolve, delay));
46
+ }
47
+
48
+ const target = await this.options.getDirectURL();
49
+ const url = new URL(request.url ?? "/", target);
50
+ const headers = { ...request.headers };
51
+ // All of these headers are set by the WebSocket constructor,
52
+ // so if we don't delete them, the request will fail.
53
+ delete headers.host;
54
+ delete headers.connection;
55
+ delete headers.upgrade;
56
+ delete headers["sec-websocket-version"];
57
+ delete headers["sec-websocket-key"];
58
+ delete headers["sec-websocket-protocol"];
59
+ delete headers["sec-websocket-extensions"];
60
+ delete headers["sec-websocket-accept"];
61
+ const server = new WebSocket(url.toString(), {
62
+ protocol: request.headers["sec-websocket-protocol"],
63
+ key: request.headers["sec-websocket-key"],
64
+ headers,
65
+ });
66
+ server.on("open", () => {
67
+ // Reset the reconnect attempts when the connection is established successfully.
68
+ this.wsReconnectAttempts.set(id, 0);
69
+ });
70
+ server.on("message", (data, binary) => {
71
+ client.send(data, { binary });
72
+ });
73
+ server.on("close", async (code, reason) => {
74
+ if (code === 1006) {
75
+ // When the worker hot reloads, the websocket connection is closed with this code.
76
+ // Reconnecting allows the client to maintain the same connection.
77
+ await this.handleUpgrade(id, client, request);
78
+ return;
79
+ }
80
+ client.close(code, reason);
81
+ });
82
+ client.on("message", (data, binary) => {
83
+ server.send(data, { binary });
84
+ });
85
+ client.on("close", (code, reason) => {
86
+ this.wsReconnectAttempts.delete(id);
87
+ if (server.readyState === WebSocket.OPEN) {
88
+ server.close(code, reason);
89
+ }
90
+ });
91
+ }
92
+
93
+ async close() {
94
+ this.wsServer.close();
95
+ await super.close();
96
+ }
97
+ }
@@ -7,7 +7,6 @@ import {
7
7
  } from "miniflare";
8
8
  import path from "node:path";
9
9
  import { findOpenPort } from "../../util/find-open-port.ts";
10
- import { HTTPServer } from "../../util/http-server.ts";
11
10
  import { logger } from "../../util/logger.ts";
12
11
  import {
13
12
  promiseWithResolvers,
@@ -18,6 +17,7 @@ import {
18
17
  buildRemoteBindings,
19
18
  type MiniflareWorkerOptions,
20
19
  } from "./miniflare-worker-options.ts";
20
+ import { MiniflareWorkerProxy } from "./miniflare-worker-proxy.ts";
21
21
  import {
22
22
  createRemoteProxyWorker,
23
23
  type RemoteBindingProxy,
@@ -26,12 +26,12 @@ import {
26
26
  class MiniflareServer {
27
27
  miniflare?: Miniflare;
28
28
  workers = new Map<string, WorkerOptions>();
29
- servers = new Map<string, HTTPServer>();
29
+ workerProxies = new Map<string, MiniflareWorkerProxy>();
30
30
  remoteBindingProxies = new Map<string, RemoteBindingProxy>();
31
31
 
32
32
  stream = new WritableStream<{
33
33
  worker: MiniflareWorkerOptions;
34
- promise: PromiseWithResolvers<HTTPServer>;
34
+ promise: PromiseWithResolvers<MiniflareWorkerProxy>;
35
35
  }>({
36
36
  write: async ({ worker, promise }) => {
37
37
  try {
@@ -48,7 +48,7 @@ class MiniflareServer {
48
48
  writer = this.stream.getWriter();
49
49
 
50
50
  async push(worker: MiniflareWorkerOptions) {
51
- const promise = promiseWithResolvers<HTTPServer>();
51
+ const promise = promiseWithResolvers<MiniflareWorkerProxy>();
52
52
  const [, server] = await Promise.all([
53
53
  this.writer.write({ worker, promise }),
54
54
  promise.promise,
@@ -62,11 +62,10 @@ class MiniflareServer {
62
62
 
63
63
  private async set(worker: MiniflareWorkerOptions) {
64
64
  this.workers.set(
65
- worker.name as string,
65
+ worker.name,
66
66
  await buildMiniflareWorkerOptions({
67
67
  ...worker,
68
- remoteProxyConnectionString:
69
- await this.maybeCreateMixedModeProxy(worker),
68
+ remoteProxyConnectionString: await this.maybeCreateRemoteProxy(worker),
70
69
  }),
71
70
  );
72
71
  if (this.miniflare) {
@@ -90,33 +89,41 @@ class MiniflareServer {
90
89
  this.miniflare = new Miniflare(await this.miniflareOptions());
91
90
  await withErrorRewrite(this.miniflare.ready);
92
91
  }
93
- const existing = this.servers.get(worker.name);
92
+ const existing = this.workerProxies.get(worker.name);
94
93
  if (existing) {
95
94
  return existing;
96
95
  }
97
- const server = new HTTPServer({
98
- port: worker.port ?? (await findOpenPort()),
99
- fetch: this.createRequestHandler(worker.name as string),
96
+ const server = new MiniflareWorkerProxy({
97
+ getDirectURL: async () => {
98
+ const url = await this.miniflare?.unsafeGetDirectURL(worker.name);
99
+ if (!url) {
100
+ throw new Error(`Worker "${worker.name}" is not running`);
101
+ }
102
+ return url;
103
+ },
104
+ fetch: this.createRequestHandler(worker.name),
100
105
  });
101
- this.servers.set(worker.name, server);
102
- await server.ready;
106
+ this.workerProxies.set(worker.name, server);
107
+ await server.listen(worker.port ?? (await findOpenPort()));
103
108
  return server;
104
109
  }
105
110
 
106
111
  private async dispose() {
107
112
  await Promise.all([
108
113
  this.miniflare?.dispose(),
109
- ...Array.from(this.servers.values()).map((server) => server.stop()),
114
+ ...Array.from(this.workerProxies.values()).map((server) =>
115
+ server.close(),
116
+ ),
110
117
  ...Array.from(this.remoteBindingProxies.values()).map((proxy) =>
111
- proxy.server.stop(),
118
+ proxy.server.close(),
112
119
  ),
113
120
  ]);
114
121
  this.miniflare = undefined;
115
122
  this.workers.clear();
116
- this.servers.clear();
123
+ this.workerProxies.clear();
117
124
  }
118
125
 
119
- private async maybeCreateMixedModeProxy(
126
+ private async maybeCreateRemoteProxy(
120
127
  worker: MiniflareWorkerOptions,
121
128
  ): Promise<RemoteProxyConnectionString | undefined> {
122
129
  const bindings = buildRemoteBindings(worker);
@@ -130,6 +137,8 @@ class MiniflareServer {
130
137
  )
131
138
  ) {
132
139
  return existing.connectionString;
140
+ } else if (existing) {
141
+ await existing.server.close();
133
142
  }
134
143
  const proxy = await createRemoteProxyWorker({
135
144
  name: `mixed-mode-proxy-${crypto.randomUUID()}`,
@@ -150,8 +159,8 @@ class MiniflareServer {
150
159
  },
151
160
  );
152
161
  }
153
- const miniflare = await this.miniflare?.getWorker(name);
154
- if (!miniflare) {
162
+ const worker = await this.miniflare?.getWorker(name);
163
+ if (!worker) {
155
164
  return new Response(
156
165
  `[Alchemy] Cannot find worker "${name}". Please try again.`,
157
166
  {
@@ -159,12 +168,12 @@ class MiniflareServer {
159
168
  },
160
169
  );
161
170
  }
162
- const res = await miniflare.fetch(req.url, {
171
+ const res = await worker.fetch(req.url, {
163
172
  method: req.method,
164
173
  headers: req.headers as any,
165
174
  body: req.body as any,
166
- redirect: "manual",
167
175
  duplex: "half",
176
+ redirect: "manual",
168
177
  });
169
178
  return res as unknown as Response;
170
179
  } catch (error) {
@@ -1,5 +1,5 @@
1
1
  import type { RemoteProxyConnectionString } from "miniflare";
2
- import { HTTPServer } from "../../util/http-server.ts";
2
+ import { HTTPServer } from "../../util/http.ts";
3
3
  import { extractCloudflareResult } from "../api-response.ts";
4
4
  import { createCloudflareApi, type CloudflareApi } from "../api.ts";
5
5
  import type { WorkerBindingSpec } from "../bindings.ts";
@@ -22,6 +22,12 @@ interface WorkersPreviewSession {
22
22
  token: string;
23
23
  }
24
24
 
25
+ export interface RemoteBindingProxy {
26
+ server: HTTPServer;
27
+ bindings: WorkerBindingSpec[];
28
+ connectionString: RemoteProxyConnectionString;
29
+ }
30
+
25
31
  export async function createRemoteProxyWorker(input: {
26
32
  name: string;
27
33
  bindings: WorkerBindingSpec[];
@@ -47,62 +53,44 @@ export async function createRemoteProxyWorker(input: {
47
53
  }),
48
54
  import("../worker-subdomain.ts").then((m) => m.getAccountSubdomain(api)),
49
55
  ]);
50
- return new RemoteBindingProxy(
51
- `https://${input.name}.${subdomain}.workers.dev`,
52
- token,
53
- input.bindings,
54
- );
55
- }
56
56
 
57
- export class RemoteBindingProxy {
58
- server: HTTPServer;
57
+ const proxyURL = new URL(`https://${input.name}.${subdomain}.workers.dev`);
58
+ const server = new HTTPServer({
59
+ fetch: async (req) => {
60
+ const origin = new URL(req.url);
61
+ const url = new URL(origin.pathname, proxyURL.toString());
62
+ url.search = origin.search;
63
+ url.hash = origin.hash;
59
64
 
60
- constructor(
61
- readonly url: string,
62
- readonly token: string,
63
- readonly bindings: WorkerBindingSpec[],
64
- ) {
65
- this.server = new HTTPServer({
66
- fetch: this.fetch.bind(this),
67
- });
68
- }
65
+ const requestHeaders = new Headers(req.headers);
66
+ requestHeaders.set("cf-workers-preview-token", token);
67
+ requestHeaders.set("host", proxyURL.hostname);
68
+ requestHeaders.delete("cf-connecting-ip");
69
69
 
70
- get connectionString() {
71
- const hostname =
72
- this.server.hostname === "::" ? "localhost" : this.server.hostname;
73
- return new URL(
74
- `http://${hostname}:${this.server.port}`,
75
- ) as RemoteProxyConnectionString;
76
- }
77
-
78
- async fetch(req: Request) {
79
- const origin = new URL(req.url);
80
- const url = new URL(origin.pathname, this.url);
81
- url.search = origin.search;
82
- url.hash = origin.hash;
83
-
84
- const headers = new Headers(req.headers);
85
- headers.set("cf-workers-preview-token", this.token);
86
- headers.set("host", new URL(this.url).hostname);
87
- headers.delete("cf-connecting-ip");
88
-
89
- const res = await fetch(url, {
90
- method: req.method,
91
- headers,
92
- body: req.body,
93
- redirect: "manual",
94
- });
70
+ const res = await fetch(url, {
71
+ method: req.method,
72
+ headers: requestHeaders,
73
+ body: req.body,
74
+ redirect: "manual",
75
+ });
95
76
 
96
- // Remove headers that are not supported by miniflare
97
- const responseHeaders = new Headers(res.headers);
98
- responseHeaders.delete("transfer-encoding");
99
- responseHeaders.delete("content-encoding");
77
+ // Remove headers that are not supported by miniflare
78
+ const responseHeaders = new Headers(res.headers);
79
+ responseHeaders.delete("transfer-encoding");
80
+ responseHeaders.delete("content-encoding");
100
81
 
101
- return new Response(res.body, {
102
- status: res.status,
103
- headers: responseHeaders,
104
- });
105
- }
82
+ return new Response(res.body, {
83
+ status: res.status,
84
+ headers: responseHeaders,
85
+ });
86
+ },
87
+ });
88
+ await server.listen();
89
+ return {
90
+ server,
91
+ bindings: input.bindings,
92
+ connectionString: new URL(server.url) as RemoteProxyConnectionString,
93
+ };
106
94
  }
107
95
 
108
96
  async function createWorkersPreviewToken(
@@ -7,7 +7,7 @@ import os from "node:os";
7
7
  import path from "node:path";
8
8
  import open from "open";
9
9
  import xdgAppPaths from "xdg-app-paths";
10
- import { HTTPServer } from "../util/http-server.ts";
10
+ import { HTTPServer } from "../util/http.ts";
11
11
  import { memoize } from "../util/memoize.ts";
12
12
  import {
13
13
  detached,
@@ -145,7 +145,6 @@ export const wranglerLogin = (
145
145
  return Response.redirect(`${ALCHEMY_BASE_URL}/auth/${type}`);
146
146
  };
147
147
  const server = new HTTPServer({
148
- port: 8976,
149
148
  fetch: async (request) => {
150
149
  const url = new URL(request.url);
151
150
  if (url.pathname !== "/oauth/callback") {
@@ -190,6 +189,7 @@ export const wranglerLogin = (
190
189
  return renderResponse("success");
191
190
  },
192
191
  });
192
+ server.listen(8976);
193
193
  const timeout = setTimeout(
194
194
  () => {
195
195
  err(
@@ -204,7 +204,7 @@ export const wranglerLogin = (
204
204
  );
205
205
  return ensure(result, () => {
206
206
  clearTimeout(timeout);
207
- void server.stop();
207
+ void server.close();
208
208
  });
209
209
  };
210
210
 
@@ -5,12 +5,8 @@ import { Scope } from "../scope.ts";
5
5
  import { detectPackageManager } from "../util/detect-package-manager.ts";
6
6
  import { Assets } from "./assets.ts";
7
7
  import type { Bindings } from "./bindings.ts";
8
- import {
9
- DEFAULT_COMPATIBILITY_DATE,
10
- Worker,
11
- type AssetsConfig,
12
- type WorkerProps,
13
- } from "./worker.ts";
8
+ import { DEFAULT_COMPATIBILITY_DATE } from "./compatibility-date.gen.ts";
9
+ import { Worker, type AssetsConfig, type WorkerProps } from "./worker.ts";
14
10
  import { WranglerJson, type WranglerJsonSpec } from "./wrangler.json.ts";
15
11
 
16
12
  export interface WebsiteProps<B extends Bindings>
@@ -4,7 +4,6 @@ import { spawn } from "node:child_process";
4
4
  import fs from "node:fs/promises";
5
5
  import { isDeepStrictEqual } from "node:util";
6
6
  import path from "pathe";
7
- import { BUILD_DATE } from "../build-date.ts";
8
7
  import type { Context } from "../context.ts";
9
8
  import type { BundleProps } from "../esbuild/bundle.ts";
10
9
  import { InnerResourceScope, Resource, ResourceKind } from "../resource.ts";
@@ -43,6 +42,7 @@ import {
43
42
  normalizeWorkerBundle,
44
43
  } from "./bundle/index.ts";
45
44
  import { wrap } from "./bundle/normalize.ts";
45
+ import { DEFAULT_COMPATIBILITY_DATE } from "./compatibility-date.gen.ts";
46
46
  import {
47
47
  type CompatibilityPreset,
48
48
  unionCompatibilityFlags,
@@ -212,7 +212,7 @@ export interface BaseWorkerProps<
212
212
 
213
213
  /**
214
214
  * The compatibility date for the worker
215
- * @default BUILD_DATE - automatically pinned to the package build date
215
+ * @default DEFAULT_WORKER_COMPATIBILITY_DATE - automatically pinned to the latest Workers release
216
216
  */
217
217
  compatibilityDate?: string;
218
218
 
@@ -1013,8 +1013,6 @@ export function Worker<const B extends Bindings>(
1013
1013
  });
1014
1014
  }
1015
1015
 
1016
- export const DEFAULT_COMPATIBILITY_DATE = BUILD_DATE;
1017
-
1018
1016
  export const _Worker = Resource(
1019
1017
  "cloudflare::Worker",
1020
1018
  {
@@ -1102,6 +1100,7 @@ export const _Worker = Resource(
1102
1100
  bindings: props.bindings,
1103
1101
  bundle,
1104
1102
  port: props.dev?.port,
1103
+ assets: props.assets,
1105
1104
  });
1106
1105
  }
1107
1106
 
@@ -1671,6 +1670,7 @@ async function createMiniflare(props: {
1671
1670
  bindings: Bindings | undefined;
1672
1671
  port: number | undefined;
1673
1672
  bundle: WorkerBundleProvider;
1673
+ assets: AssetsConfig | undefined;
1674
1674
  }) {
1675
1675
  const sharedOptions: Omit<MiniflareWorkerOptions, "bundle"> = {
1676
1676
  name: props.workerName,
@@ -1678,6 +1678,7 @@ async function createMiniflare(props: {
1678
1678
  compatibilityFlags: props.compatibilityFlags,
1679
1679
  bindings: props.bindings,
1680
1680
  port: props.port,
1681
+ assets: props.assets,
1681
1682
  };
1682
1683
 
1683
1684
  const startPromise = new DeferredPromise<string>();
@@ -1837,14 +1838,6 @@ async function createDevCommand(props: {
1837
1838
  }
1838
1839
  }
1839
1840
  const command = props.command.split(" ");
1840
- console.log({
1841
- command,
1842
- args: command.slice(1),
1843
- cwd: props.cwd,
1844
- env: {
1845
- ...props.env,
1846
- },
1847
- });
1848
1841
  const proc = spawn(command[0], command.slice(1), {
1849
1842
  cwd: props.cwd,
1850
1843
  env: {
package/src/scope.ts CHANGED
@@ -125,6 +125,7 @@ export class Scope {
125
125
  public readonly dataMutex: AsyncMutex;
126
126
 
127
127
  private isErrored = false;
128
+ private isSkipped = false;
128
129
  private finalized = false;
129
130
  private startedAt = performance.now();
130
131
 
@@ -194,6 +195,17 @@ export class Scope {
194
195
  this.dataMutex = new AsyncMutex();
195
196
  }
196
197
 
198
+ /**
199
+ * @internal
200
+ */
201
+ public clear() {
202
+ for (const child of this.children.values()) {
203
+ child.clear();
204
+ }
205
+ this.resources.clear();
206
+ this.children.clear();
207
+ }
208
+
197
209
  public get root(): Scope {
198
210
  let root: Scope = this;
199
211
  while (root.parent) {
@@ -239,6 +251,10 @@ export class Scope {
239
251
  this.isErrored = true;
240
252
  }
241
253
 
254
+ public skip() {
255
+ this.isSkipped = true;
256
+ }
257
+
242
258
  public async init() {
243
259
  await Promise.all([
244
260
  this.state.init?.(),
@@ -378,7 +394,7 @@ export class Scope {
378
394
  this.finalized = true;
379
395
  // trigger and await all deferred promises
380
396
  await Promise.all(this.deferred.map((fn) => fn()));
381
- if (!this.isErrored) {
397
+ if (!this.isErrored && !this.isSkipped) {
382
398
  // TODO: need to detect if it is in error
383
399
  const resourceIds = await this.state.list();
384
400
  const aliveIds = new Set(this.resources.keys());
@@ -1,8 +1,8 @@
1
1
  import { alchemy } from "../alchemy.ts";
2
- import { BUILD_DATE } from "../build-date.ts";
3
2
  import type { CloudflareApiOptions } from "../cloudflare/api.ts";
4
3
  import { createCloudflareApi } from "../cloudflare/api.ts";
5
4
  import { getInternalWorkerBundle } from "../cloudflare/bundle/internal-worker-bundle.ts";
5
+ import { DEFAULT_COMPATIBILITY_DATE } from "../cloudflare/compatibility-date.gen.ts";
6
6
  import { DurableObjectNamespace } from "../cloudflare/durable-object-namespace.ts";
7
7
  import { getWorkerSettings } from "../cloudflare/worker-metadata.ts";
8
8
  import {
@@ -109,7 +109,7 @@ const provision = memoize(async (options: CloudflareStateStoreOptions) => {
109
109
  );
110
110
  await putWorker(api, {
111
111
  workerName: scriptName,
112
- compatibilityDate: BUILD_DATE,
112
+ compatibilityDate: DEFAULT_COMPATIBILITY_DATE,
113
113
  format: "esm",
114
114
  scriptBundle: {
115
115
  entrypoint: bundle.file.name,
@@ -0,0 +1,94 @@
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
+ httpServer: http.Server;
7
+
8
+ constructor(options: {
9
+ fetch: (request: Request) => Promise<Response>;
10
+ }) {
11
+ this.httpServer = http.createServer();
12
+ this.httpServer.on("request", async (req, res) => {
13
+ const response = await options.fetch(toWebRequest(req));
14
+ await writeNodeResponse(res, response);
15
+ });
16
+ }
17
+
18
+ listen(port?: number) {
19
+ return new Promise<this>((resolve, reject) => {
20
+ this.httpServer.on("listening", () => {
21
+ resolve(this);
22
+ });
23
+ this.httpServer.on("error", (error) => {
24
+ reject(error);
25
+ });
26
+ this.httpServer.listen(port);
27
+ });
28
+ }
29
+
30
+ get url() {
31
+ const address = this.httpServer.address() as AddressInfo | null;
32
+ if (!address) {
33
+ throw new Error("Server is not listening");
34
+ }
35
+ const hostname = address.address === "::" ? "localhost" : address.address;
36
+ return `http://${hostname}:${address.port}`;
37
+ }
38
+
39
+ close() {
40
+ return new Promise((resolve, reject) => {
41
+ this.httpServer.close((err) => {
42
+ if (err) {
43
+ reject(err);
44
+ } else {
45
+ resolve(undefined);
46
+ }
47
+ });
48
+ });
49
+ }
50
+ }
51
+
52
+ export function toWebRequest(
53
+ req: http.IncomingMessage,
54
+ host?: string,
55
+ ): Request {
56
+ const method = req.method ?? "GET";
57
+ const url = new URL(req.url ?? "/", `http://${host ?? req.headers.host}`);
58
+ const body =
59
+ ["GET", "HEAD", "OPTIONS"].includes(method) || !req.readable
60
+ ? undefined
61
+ : Readable.toWeb(req);
62
+ return new Request(url.toString(), {
63
+ method,
64
+ headers: req.headers as Record<string, string>,
65
+ body: body as unknown as BodyInit,
66
+ // @ts-expect-error - caused by @cloudflare/workers-types
67
+ duplex: body ? "half" : undefined,
68
+ redirect: "manual",
69
+ });
70
+ }
71
+
72
+ export async function writeNodeResponse(
73
+ res: http.ServerResponse,
74
+ response: Response,
75
+ ) {
76
+ res.statusCode = response.status;
77
+ response.headers.forEach((value, key) => {
78
+ res.setHeader(key, value);
79
+ });
80
+ if (response.body) {
81
+ await response.body.pipeTo(
82
+ new WritableStream({
83
+ write(chunk) {
84
+ res.write(chunk);
85
+ },
86
+ close() {
87
+ res.end();
88
+ },
89
+ }),
90
+ );
91
+ } else {
92
+ res.end();
93
+ }
94
+ }
@@ -1,6 +0,0 @@
1
- /**
2
- * The build date used as the default worker compatibility date.
3
- * This is set to the date when the package was built.
4
- */
5
- export declare const BUILD_DATE = "2025-07-16";
6
- //# sourceMappingURL=build-date.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"build-date.d.ts","sourceRoot":"","sources":["../src/build-date.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,eAAO,MAAM,UAAU,eAAe,CAAC"}
package/lib/build-date.js DELETED
@@ -1,8 +0,0 @@
1
- // This file is auto-generated during build
2
- // Do not edit manually
3
- /**
4
- * The build date used as the default worker compatibility date.
5
- * This is set to the date when the package was built.
6
- */
7
- export const BUILD_DATE = "2025-07-16";
8
- //# sourceMappingURL=build-date.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"build-date.js","sourceRoot":"","sources":["../src/build-date.ts"],"names":[],"mappings":"AAAA,2CAA2C;AAC3C,uBAAuB;AAEvB;;;GAGG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,YAAY,CAAC"}
@@ -1,14 +0,0 @@
1
- import http from "node:http";
2
- export declare class HTTPServer {
3
- server: http.Server;
4
- ready: Promise<void>;
5
- constructor(options: {
6
- port?: number;
7
- fetch: (request: Request) => Promise<Response>;
8
- });
9
- get port(): number;
10
- get hostname(): string;
11
- get url(): string;
12
- stop(): Promise<unknown>;
13
- }
14
- //# sourceMappingURL=http-server.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"http-server.d.ts","sourceRoot":"","sources":["../../src/util/http-server.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAK7B,qBAAa,UAAU;IACrB,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC;IACpB,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;gBAET,OAAO,EAAE;QACnB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;KAChD;IAWD,IAAI,IAAI,WAEP;IAED,IAAI,QAAQ,WAMX;IAED,IAAI,GAAG,WAEN;IAED,IAAI;CAWL"}