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
@@ -0,0 +1,198 @@
1
+ import type {
2
+ Miniflare,
3
+ MiniflareOptions,
4
+ RemoteProxyConnectionString,
5
+ WorkerOptions,
6
+ } from "miniflare";
7
+ import path from "node:path";
8
+ import { logger } from "../../util/logger.ts";
9
+ import { HTTPServer } from "./http-server.ts";
10
+ import {
11
+ buildMiniflareWorkerOptions,
12
+ buildRemoteBindings,
13
+ type MiniflareWorkerOptions,
14
+ } from "./miniflare-worker-options.ts";
15
+ import { createMixedModeProxy, type MixedModeProxy } from "./mixed-mode.ts";
16
+
17
+ class MiniflareServer {
18
+ miniflare?: Miniflare;
19
+ workers = new Map<string, WorkerOptions>();
20
+ servers = new Map<string, HTTPServer>();
21
+ mixedModeProxies = new Map<string, MixedModeProxy>();
22
+
23
+ stream = new WritableStream<{
24
+ worker: MiniflareWorkerOptions;
25
+ promise: PromiseWithResolvers<HTTPServer>;
26
+ }>({
27
+ write: async ({ worker, promise }) => {
28
+ try {
29
+ const server = await this.set(worker);
30
+ promise.resolve(server);
31
+ } catch (error) {
32
+ promise.reject(error);
33
+ }
34
+ },
35
+ close: async () => {
36
+ await this.dispose();
37
+ },
38
+ });
39
+ writer = this.stream.getWriter();
40
+
41
+ async push(worker: MiniflareWorkerOptions) {
42
+ const promise = Promise.withResolvers<HTTPServer>();
43
+ const [, server] = await Promise.all([
44
+ this.writer.write({ worker, promise }),
45
+ promise.promise,
46
+ ]);
47
+ return server;
48
+ }
49
+
50
+ async close() {
51
+ await this.writer.close();
52
+ }
53
+
54
+ private async set(worker: MiniflareWorkerOptions) {
55
+ this.workers.set(
56
+ worker.name as string,
57
+ buildMiniflareWorkerOptions({
58
+ ...worker,
59
+ remoteProxyConnectionString:
60
+ await this.maybeCreateMixedModeProxy(worker),
61
+ }),
62
+ );
63
+ if (this.miniflare) {
64
+ await this.miniflare.setOptions(await this.miniflareOptions());
65
+ } else {
66
+ const { Miniflare } = await import("miniflare").catch(() => {
67
+ throw new Error(
68
+ "Miniflare is not installed, but is required in local mode for Workers. Please run `npm install miniflare`.",
69
+ );
70
+ });
71
+
72
+ // Miniflare intercepts SIGINT and exits with 130, which is not a failure.
73
+ // No one likes to see a non-zero exit code when they Ctrl+C, so here's our workaround.
74
+ process.on("exit", (code) => {
75
+ if (code === 130) {
76
+ process.exit(0);
77
+ }
78
+ });
79
+
80
+ this.miniflare = new Miniflare(await this.miniflareOptions());
81
+ await this.miniflare.ready;
82
+ }
83
+ const existing = this.servers.get(worker.name);
84
+ if (existing) {
85
+ return existing;
86
+ }
87
+ const server = new HTTPServer({
88
+ port: worker.port,
89
+ fetch: this.createRequestHandler(worker.name as string),
90
+ });
91
+ this.servers.set(worker.name, server);
92
+ await server.ready;
93
+ return server;
94
+ }
95
+
96
+ private async dispose() {
97
+ await Promise.all([
98
+ this.miniflare?.dispose(),
99
+ ...Array.from(this.servers.values()).map((server) => server.stop()),
100
+ ...Array.from(this.mixedModeProxies.values()).map((proxy) =>
101
+ proxy.server.stop(),
102
+ ),
103
+ ]);
104
+ this.miniflare = undefined;
105
+ this.workers.clear();
106
+ this.servers.clear();
107
+ }
108
+
109
+ private async maybeCreateMixedModeProxy(
110
+ worker: MiniflareWorkerOptions,
111
+ ): Promise<RemoteProxyConnectionString | undefined> {
112
+ const bindings = buildRemoteBindings(worker);
113
+ if (bindings.length === 0) {
114
+ return undefined;
115
+ }
116
+ const existing = this.mixedModeProxies.get(worker.name);
117
+ if (
118
+ existing?.bindings.every((b) =>
119
+ bindings.find((b2) => b2.name === b.name && b2.type === b.type),
120
+ )
121
+ ) {
122
+ return existing.connectionString;
123
+ }
124
+ const proxy = await createMixedModeProxy({
125
+ name: `mixed-mode-proxy-${crypto.randomUUID()}`,
126
+ bindings,
127
+ });
128
+ this.mixedModeProxies.set(worker.name, proxy);
129
+ return proxy.connectionString;
130
+ }
131
+
132
+ private createRequestHandler(name: string) {
133
+ return async (req: Request) => {
134
+ try {
135
+ if (!this.miniflare) {
136
+ return new Response(
137
+ "[Alchemy] Miniflare is not initialized. Please try again.",
138
+ {
139
+ status: 503,
140
+ },
141
+ );
142
+ }
143
+ const miniflare = await this.miniflare?.getWorker(name);
144
+ if (!miniflare) {
145
+ return new Response(
146
+ `[Alchemy] Cannot find worker "${name}". Please try again.`,
147
+ {
148
+ status: 503,
149
+ },
150
+ );
151
+ }
152
+ const res = await miniflare.fetch(req.url, {
153
+ method: req.method,
154
+ headers: req.headers as any,
155
+ body: req.body as any,
156
+ redirect: "manual",
157
+ });
158
+ return res as unknown as Response;
159
+ } catch (error) {
160
+ logger.error(error);
161
+ return new Response(
162
+ `[Alchemy] Internal server error: ${String(error)}`,
163
+ {
164
+ status: 500,
165
+ },
166
+ );
167
+ }
168
+ };
169
+ }
170
+
171
+ private async miniflareOptions(): Promise<MiniflareOptions> {
172
+ const { getDefaultDevRegistryPath } = await import("miniflare");
173
+ return {
174
+ workers: Array.from(this.workers.values()),
175
+ defaultPersistRoot: path.join(process.cwd(), ".alchemy/miniflare"),
176
+ unsafeDevRegistryPath: getDefaultDevRegistryPath(),
177
+ analyticsEngineDatasetsPersist: true,
178
+ cachePersist: true,
179
+ d1Persist: true,
180
+ durableObjectsPersist: true,
181
+ kvPersist: true,
182
+ r2Persist: true,
183
+ secretsStorePersist: true,
184
+ workflowsPersist: true,
185
+ };
186
+ }
187
+ }
188
+
189
+ declare global {
190
+ var _ALCHEMY_MINIFLARE_SERVER: MiniflareServer | undefined;
191
+ }
192
+
193
+ export const miniflareServer = new Proxy({} as MiniflareServer, {
194
+ get: (_, prop: keyof MiniflareServer) => {
195
+ globalThis._ALCHEMY_MINIFLARE_SERVER ??= new MiniflareServer();
196
+ return globalThis._ALCHEMY_MINIFLARE_SERVER[prop];
197
+ },
198
+ });
@@ -0,0 +1,220 @@
1
+ import type { RemoteProxyConnectionString } from "miniflare";
2
+ import { createCloudflareApi, type CloudflareApi } from "../api.ts";
3
+ import type { WorkerBindingSpec } from "../bindings.ts";
4
+ import type { CloudflareApiResponse } from "../types.ts";
5
+ import type { WorkerMetadata } from "../worker-metadata.ts";
6
+ import { getWorkerTemplate } from "./get-worker-template.ts";
7
+ import { HTTPServer } from "./http-server.ts";
8
+ import { getAccountSubdomain } from "./subdomain.ts";
9
+
10
+ type WranglerSessionConfig =
11
+ | {
12
+ workers_dev: boolean;
13
+ minimal_mode: boolean;
14
+ }
15
+ | {
16
+ routes: string[];
17
+ minimal_mode: boolean;
18
+ };
19
+
20
+ interface WorkersPreviewSession {
21
+ inspector_websocket: string;
22
+ prewarm: string;
23
+ token: string;
24
+ }
25
+
26
+ export async function createMixedModeProxy(input: {
27
+ name: string;
28
+ bindings: WorkerBindingSpec[];
29
+ }) {
30
+ const api = await createCloudflareApi();
31
+ const script = await getWorkerTemplate("mixed-mode-proxy-worker");
32
+ const [token, subdomain] = await Promise.all([
33
+ createWorkersPreviewToken(api, {
34
+ name: input.name,
35
+ metadata: {
36
+ main_module: script.name,
37
+ compatibility_date: "2025-06-16",
38
+ bindings: input.bindings,
39
+ observability: {
40
+ enabled: false,
41
+ },
42
+ },
43
+ files: [script],
44
+ session: {
45
+ workers_dev: true,
46
+ minimal_mode: true,
47
+ },
48
+ }),
49
+ getAccountSubdomain(api),
50
+ ]);
51
+ return new MixedModeProxy(
52
+ `https://${input.name}.${subdomain}.workers.dev`,
53
+ token,
54
+ input.bindings,
55
+ );
56
+ }
57
+
58
+ const DEBUG: boolean = false;
59
+
60
+ export class MixedModeProxy {
61
+ server: HTTPServer;
62
+
63
+ constructor(
64
+ readonly url: string,
65
+ readonly token: string,
66
+ readonly bindings: WorkerBindingSpec[],
67
+ ) {
68
+ this.server = new HTTPServer({
69
+ fetch: this.fetch.bind(this),
70
+ });
71
+ }
72
+
73
+ get connectionString() {
74
+ const hostname =
75
+ this.server.hostname === "::" ? "localhost" : this.server.hostname;
76
+ return new URL(
77
+ `http://${hostname}:${this.server.port}`,
78
+ ) as RemoteProxyConnectionString;
79
+ }
80
+
81
+ async fetch(req: Request) {
82
+ const origin = new URL(req.url);
83
+ const url = new URL(origin.pathname, this.url);
84
+ url.search = origin.search;
85
+ url.hash = origin.hash;
86
+
87
+ const headers = new Headers(req.headers);
88
+ headers.set("cf-workers-preview-token", this.token);
89
+ headers.set("host", new URL(this.url).hostname);
90
+ headers.delete("cf-connecting-ip");
91
+
92
+ const res = await fetch(url, {
93
+ method: req.method,
94
+ headers,
95
+ body: req.body,
96
+ redirect: "manual",
97
+ });
98
+
99
+ // Remove headers that are not supported by miniflare
100
+ const responseHeaders = new Headers(res.headers);
101
+ responseHeaders.delete("transfer-encoding");
102
+ responseHeaders.delete("content-encoding");
103
+
104
+ if (DEBUG) {
105
+ const clone = res.clone();
106
+ console.log({
107
+ request: {
108
+ url: url.toString(),
109
+ method: req.method,
110
+ headers,
111
+ body: req.body,
112
+ },
113
+ response: {
114
+ status: res.status,
115
+ headers: res.headers,
116
+ body: await res.text(),
117
+ },
118
+ });
119
+ return new Response(clone.body, {
120
+ status: clone.status,
121
+ headers: responseHeaders,
122
+ });
123
+ }
124
+
125
+ return new Response(res.body, {
126
+ status: res.status,
127
+ headers: responseHeaders,
128
+ });
129
+ }
130
+ }
131
+
132
+ async function createWorkersPreviewToken(
133
+ api: CloudflareApi,
134
+ input: {
135
+ name: string;
136
+ metadata: WorkerMetadata;
137
+ files: File[];
138
+ session: WranglerSessionConfig;
139
+ },
140
+ ) {
141
+ const session = await createWorkersPreviewSession(api);
142
+ const formData = new FormData();
143
+ formData.append("metadata", JSON.stringify(input.metadata));
144
+ for (const file of input.files) {
145
+ formData.append(file.name, file);
146
+ }
147
+ formData.append("wrangler-session-config", JSON.stringify(input.session));
148
+ const res = await api
149
+ .post(
150
+ `/accounts/${api.accountId}/workers/scripts/${input.name}/edge-preview`,
151
+ formData,
152
+ {
153
+ headers: {
154
+ "cf-preview-upload-config-token": session.token,
155
+ },
156
+ },
157
+ )
158
+ .then((res) =>
159
+ parseCloudflareResponse<{ preview_token: string }>(
160
+ res,
161
+ "Failed to create workers preview token",
162
+ ),
163
+ );
164
+ // Fire and forget prewarm call
165
+ // (see https://github.com/cloudflare/workers-sdk/blob/6c6afbd6072b96e78e67d3a863ed849c6aa49472/packages/wrangler/src/dev/create-worker-preview.ts#L338)
166
+ void prewarm(session.prewarm, res.preview_token);
167
+ return res.preview_token;
168
+ }
169
+
170
+ async function prewarm(url: string, previewToken: string) {
171
+ const res = await fetch(url, {
172
+ headers: {
173
+ "cf-workers-preview-token": previewToken,
174
+ },
175
+ });
176
+ if (!res.ok) {
177
+ console.error(`Failed to prewarm worker: ${res.status} ${res.statusText}`);
178
+ }
179
+ }
180
+
181
+ async function createWorkersPreviewSession(api: CloudflareApi) {
182
+ const { exchange_url } = await api
183
+ .get(`/accounts/${api.accountId}/workers/subdomain/edge-preview`)
184
+ .then((res) =>
185
+ parseCloudflareResponse<{
186
+ exchange_url: string;
187
+ token: string;
188
+ }>(res, "Failed to create workers preview session"),
189
+ );
190
+ return await fetch(exchange_url).then((res) =>
191
+ parseResponse<WorkersPreviewSession>(
192
+ res,
193
+ "Failed to create workers preview session",
194
+ ),
195
+ );
196
+ }
197
+
198
+ async function parseResponse<T>(res: Response, message: string): Promise<T> {
199
+ if (!res.ok) {
200
+ throw new Error(`${message} (${res.status} ${res.statusText})`);
201
+ }
202
+ const json: T = await res.json();
203
+ return json;
204
+ }
205
+
206
+ async function parseCloudflareResponse<T>(
207
+ res: Response,
208
+ message: string,
209
+ ): Promise<T> {
210
+ const json: CloudflareApiResponse<T> = await res.json();
211
+ if (!json.success) {
212
+ throw new Error(
213
+ `${message} (${res.status} ${res.statusText} - ${json.errors.map((e) => `${e.code}: ${e.message}`).join(", ")})`,
214
+ );
215
+ }
216
+ if (!json.result) {
217
+ throw new Error(`${message} (${res.status} ${res.statusText})`);
218
+ }
219
+ return json.result;
220
+ }
@@ -0,0 +1,16 @@
1
+ import { memoize } from "../../util/memoize.ts";
2
+ import type { CloudflareApi } from "../api.ts";
3
+
4
+ export const getAccountSubdomain = memoize(
5
+ async (api: CloudflareApi) => {
6
+ const res = await api.get(`/accounts/${api.accountId}/workers/subdomain`);
7
+ if (!res.ok) {
8
+ throw new Error(
9
+ `Failed to get account subdomain: ${res.status} ${res.statusText}`,
10
+ );
11
+ }
12
+ const json: { result?: { subdomain: string } } = await res.json();
13
+ return json.result?.subdomain;
14
+ },
15
+ (api) => api.accountId,
16
+ );
@@ -1,4 +1,5 @@
1
1
  import path from "node:path";
2
+ import { assertNever } from "../util/assert-never.ts";
2
3
  import { logger } from "../util/logger.ts";
3
4
  import type { CloudflareApi } from "./api.ts";
4
5
  import {
@@ -209,7 +210,6 @@ export async function prepareWorkerMetadata(
209
210
  const oldTags: string[] | undefined =
210
211
  oldSettings?.default_environment?.script?.tags;
211
212
  const oldBindings = oldSettings?.bindings;
212
-
213
213
  // we use Cloudflare Worker tags to store a mapping between Alchemy's stable identifier and the binding name
214
214
  // e.g.
215
215
  // {
@@ -384,7 +384,10 @@ export async function prepareWorkerMetadata(
384
384
  type: "durable_object_namespace",
385
385
  name: bindingName,
386
386
  class_name: binding.className,
387
- script_name: binding.scriptName,
387
+ script_name:
388
+ binding.scriptName === props.workerName
389
+ ? undefined
390
+ : binding.scriptName,
388
391
  environment: binding.environment,
389
392
  namespace_id: binding.namespaceId,
390
393
  });
@@ -510,8 +513,13 @@ export async function prepareWorkerMetadata(
510
513
  key_jwk: binding.key_jwk?.unencrypted,
511
514
  });
512
515
  } else {
513
- // @ts-expect-error - we should never reach here
514
- throw new Error(`Unsupported binding type: ${binding.type}`);
516
+ assertNever(
517
+ binding,
518
+ `Unsupported binding type: ${
519
+ // @ts-expect-error - types think it's never
520
+ binding.type
521
+ }`,
522
+ );
515
523
  }
516
524
  }
517
525