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.
- package/lib/alchemy.d.ts +6 -0
- package/lib/alchemy.d.ts.map +1 -1
- package/lib/alchemy.js +5 -0
- package/lib/alchemy.js.map +1 -1
- package/lib/apply.js +1 -1
- package/lib/apply.js.map +1 -1
- package/lib/build-date.d.ts +1 -1
- package/lib/build-date.js +1 -1
- package/lib/cloudflare/bucket.d.ts +10 -0
- package/lib/cloudflare/bucket.d.ts.map +1 -1
- package/lib/cloudflare/bucket.js +1 -0
- package/lib/cloudflare/bucket.js.map +1 -1
- package/lib/cloudflare/bundle/bundle-worker-dev.d.ts +25 -0
- package/lib/cloudflare/bundle/bundle-worker-dev.d.ts.map +1 -0
- package/lib/cloudflare/bundle/bundle-worker-dev.js +85 -0
- package/lib/cloudflare/bundle/bundle-worker-dev.js.map +1 -0
- package/lib/cloudflare/bundle/bundle-worker.d.ts.map +1 -1
- package/lib/cloudflare/bundle/bundle-worker.js +0 -3
- package/lib/cloudflare/bundle/bundle-worker.js.map +1 -1
- package/lib/cloudflare/bundle/nodejs-compat-mode.d.ts +1 -2
- package/lib/cloudflare/bundle/nodejs-compat-mode.d.ts.map +1 -1
- package/lib/cloudflare/bundle/nodejs-compat-mode.js +6 -1
- package/lib/cloudflare/bundle/nodejs-compat-mode.js.map +1 -1
- package/lib/cloudflare/d1-database.d.ts +10 -0
- package/lib/cloudflare/d1-database.d.ts.map +1 -1
- package/lib/cloudflare/d1-database.js +1 -0
- package/lib/cloudflare/d1-database.js.map +1 -1
- package/lib/cloudflare/do-state-store/internal.d.ts +1 -0
- package/lib/cloudflare/do-state-store/internal.d.ts.map +1 -1
- package/lib/cloudflare/do-state-store/internal.js +27 -29
- package/lib/cloudflare/do-state-store/internal.js.map +1 -1
- package/lib/cloudflare/do-state-store/store.d.ts.map +1 -1
- package/lib/cloudflare/do-state-store/store.js +7 -20
- package/lib/cloudflare/do-state-store/store.js.map +1 -1
- package/lib/cloudflare/images.d.ts +12 -0
- package/lib/cloudflare/images.d.ts.map +1 -1
- package/lib/cloudflare/images.js +4 -0
- package/lib/cloudflare/images.js.map +1 -1
- package/lib/cloudflare/kv-namespace.d.ts +10 -0
- package/lib/cloudflare/kv-namespace.d.ts.map +1 -1
- package/lib/cloudflare/kv-namespace.js +1 -0
- package/lib/cloudflare/kv-namespace.js.map +1 -1
- package/lib/cloudflare/queue.d.ts +10 -0
- package/lib/cloudflare/queue.d.ts.map +1 -1
- package/lib/cloudflare/queue.js +1 -0
- package/lib/cloudflare/queue.js.map +1 -1
- package/lib/cloudflare/vite.d.ts.map +1 -1
- package/lib/cloudflare/vite.js +12 -0
- package/lib/cloudflare/vite.js.map +1 -1
- package/lib/cloudflare/website.d.ts +7 -0
- package/lib/cloudflare/website.d.ts.map +1 -1
- package/lib/cloudflare/website.js +16 -6
- package/lib/cloudflare/website.js.map +1 -1
- package/lib/cloudflare/worker/get-worker-template.d.ts +2 -0
- package/lib/cloudflare/worker/get-worker-template.d.ts.map +1 -0
- package/lib/cloudflare/worker/get-worker-template.js +12 -0
- package/lib/cloudflare/worker/get-worker-template.js.map +1 -0
- package/lib/cloudflare/worker/http-server.d.ts +14 -0
- package/lib/cloudflare/worker/http-server.d.ts.map +1 -0
- package/lib/cloudflare/worker/http-server.js +67 -0
- package/lib/cloudflare/worker/http-server.js.map +1 -0
- package/lib/cloudflare/worker/miniflare-worker-options.d.ts +13 -0
- package/lib/cloudflare/worker/miniflare-worker-options.d.ts.map +1 -0
- package/lib/cloudflare/worker/miniflare-worker-options.js +408 -0
- package/lib/cloudflare/worker/miniflare-worker-options.js.map +1 -0
- package/lib/cloudflare/worker/miniflare.d.ts +31 -0
- package/lib/cloudflare/worker/miniflare.d.ts.map +1 -0
- package/lib/cloudflare/worker/miniflare.js +150 -0
- package/lib/cloudflare/worker/miniflare.js.map +1 -0
- package/lib/cloudflare/worker/mixed-mode.d.ts +17 -0
- package/lib/cloudflare/worker/mixed-mode.d.ts.map +1 -0
- package/lib/cloudflare/worker/mixed-mode.js +145 -0
- package/lib/cloudflare/worker/mixed-mode.js.map +1 -0
- package/lib/cloudflare/worker/subdomain.d.ts +3 -0
- package/lib/cloudflare/worker/subdomain.d.ts.map +1 -0
- package/lib/cloudflare/worker/subdomain.js +10 -0
- package/lib/cloudflare/worker/subdomain.js.map +1 -0
- package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
- package/lib/cloudflare/worker-metadata.js +7 -3
- package/lib/cloudflare/worker-metadata.js.map +1 -1
- package/lib/cloudflare/worker.d.ts +23 -1
- package/lib/cloudflare/worker.d.ts.map +1 -1
- package/lib/cloudflare/worker.js +229 -8
- package/lib/cloudflare/worker.js.map +1 -1
- package/lib/cloudflare/workflow.d.ts +7 -0
- package/lib/cloudflare/workflow.d.ts.map +1 -1
- package/lib/cloudflare/workflow.js.map +1 -1
- package/lib/destroy.d.ts.map +1 -1
- package/lib/destroy.js +9 -5
- package/lib/destroy.js.map +1 -1
- package/lib/scope.d.ts +2 -0
- package/lib/scope.d.ts.map +1 -1
- package/lib/scope.js +10 -2
- package/lib/scope.js.map +1 -1
- package/lib/util/assert-never.d.ts +1 -1
- package/lib/util/assert-never.d.ts.map +1 -1
- package/lib/util/assert-never.js +2 -2
- package/lib/util/assert-never.js.map +1 -1
- package/lib/util/cli.d.ts +21 -7
- package/lib/util/cli.d.ts.map +1 -1
- package/lib/util/cli.js +8 -0
- package/lib/util/cli.js.map +1 -1
- package/lib/util/deferred-promise.d.ts +8 -0
- package/lib/util/deferred-promise.d.ts.map +1 -0
- package/lib/util/deferred-promise.js +16 -0
- package/lib/util/deferred-promise.js.map +1 -0
- package/lib/util/detect-package-manager.d.ts +3 -0
- package/lib/util/detect-package-manager.d.ts.map +1 -0
- package/lib/util/detect-package-manager.js +25 -0
- package/lib/util/detect-package-manager.js.map +1 -0
- package/lib/util/logger.d.ts.map +1 -1
- package/lib/util/logger.js +1 -0
- package/lib/util/logger.js.map +1 -1
- package/lib/util/memoize.d.ts +4 -0
- package/lib/util/memoize.d.ts.map +1 -0
- package/lib/util/memoize.js +17 -0
- package/lib/util/memoize.js.map +1 -0
- package/package.json +7 -5
- package/src/alchemy.ts +14 -0
- package/src/apply.ts +2 -1
- package/src/build-date.ts +1 -1
- package/src/cloudflare/bucket.ts +12 -0
- package/src/cloudflare/bundle/bundle-worker-dev.ts +126 -0
- package/src/cloudflare/bundle/bundle-worker.ts +0 -5
- package/src/cloudflare/bundle/nodejs-compat-mode.ts +12 -3
- package/src/cloudflare/d1-database.ts +11 -0
- package/src/cloudflare/do-state-store/internal.ts +30 -31
- package/src/cloudflare/do-state-store/store.ts +9 -26
- package/src/cloudflare/images.ts +14 -0
- package/src/cloudflare/kv-namespace.ts +12 -0
- package/src/cloudflare/queue.ts +12 -0
- package/src/cloudflare/vite.ts +12 -0
- package/src/cloudflare/website.ts +25 -6
- package/src/cloudflare/worker/get-worker-template.ts +14 -0
- package/src/cloudflare/worker/http-server.ts +80 -0
- package/src/cloudflare/worker/miniflare-worker-options.ts +531 -0
- package/src/cloudflare/worker/miniflare.ts +198 -0
- package/src/cloudflare/worker/mixed-mode.ts +220 -0
- package/src/cloudflare/worker/subdomain.ts +16 -0
- package/src/cloudflare/worker-metadata.ts +12 -4
- package/src/cloudflare/worker.ts +284 -18
- package/src/cloudflare/workflow.ts +7 -0
- package/src/destroy.ts +9 -5
- package/src/scope.ts +20 -1
- package/src/util/assert-never.ts +2 -2
- package/src/util/cli.ts +19 -7
- package/src/util/deferred-promise.ts +19 -0
- package/src/util/detect-package-manager.ts +23 -0
- package/src/util/logger.ts +1 -0
- package/src/util/memoize.ts +23 -0
- package/workers/do-state-store.js +826 -0
- package/{src/cloudflare/do-state-store/worker.ts → workers/do-state-store.ts} +55 -23
- package/workers/mixed-mode-proxy-worker.js +39 -0
- package/workers/mixed-mode-proxy-worker.ts +46 -0
- package/lib/cloudflare/do-state-store/worker.d.ts +0 -27
- package/lib/cloudflare/do-state-store/worker.d.ts.map +0 -1
- package/lib/cloudflare/do-state-store/worker.js +0 -179
- 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:
|
|
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
|
-
|
|
514
|
-
|
|
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
|
|