alchemy 0.55.2 → 0.56.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/bin/alchemy.js +13920 -6724
- package/lib/alchemy.d.ts.map +1 -1
- package/lib/alchemy.js +7 -0
- package/lib/alchemy.js.map +1 -1
- package/lib/cloudflare/bundle/plugin-alias.d.ts +3 -0
- package/lib/cloudflare/bundle/plugin-alias.d.ts.map +1 -0
- package/lib/cloudflare/bundle/plugin-alias.js +40 -0
- package/lib/cloudflare/bundle/plugin-alias.js.map +1 -0
- package/lib/cloudflare/bundle/plugin-hot-reload.d.ts +1 -3
- package/lib/cloudflare/bundle/plugin-hot-reload.d.ts.map +1 -1
- package/lib/cloudflare/bundle/plugin-hot-reload.js +4 -1
- package/lib/cloudflare/bundle/plugin-hot-reload.js.map +1 -1
- package/lib/cloudflare/compatibility-date.gen.d.ts +1 -1
- package/lib/cloudflare/compatibility-date.gen.js +1 -1
- package/lib/cloudflare/compatibility-presets.d.ts +1 -1
- package/lib/cloudflare/compatibility-presets.js +1 -1
- package/lib/cloudflare/compatibility-presets.js.map +1 -1
- package/lib/cloudflare/local-dev-cloudflare-shim.d.ts +3 -1
- package/lib/cloudflare/local-dev-cloudflare-shim.d.ts.map +1 -1
- package/lib/cloudflare/local-dev-cloudflare-shim.js +10 -5
- package/lib/cloudflare/local-dev-cloudflare-shim.js.map +1 -1
- package/lib/cloudflare/miniflare/build-worker-options.d.ts +22 -0
- package/lib/cloudflare/miniflare/build-worker-options.d.ts.map +1 -0
- package/lib/cloudflare/miniflare/build-worker-options.js +394 -0
- package/lib/cloudflare/miniflare/build-worker-options.js.map +1 -0
- package/lib/cloudflare/miniflare/miniflare-controller.d.ts +23 -0
- package/lib/cloudflare/miniflare/miniflare-controller.d.ts.map +1 -0
- package/lib/cloudflare/miniflare/miniflare-controller.js +110 -0
- package/lib/cloudflare/miniflare/miniflare-controller.js.map +1 -0
- package/lib/cloudflare/miniflare/miniflare-worker-proxy.d.ts.map +1 -1
- package/lib/cloudflare/miniflare/miniflare-worker-proxy.js +5 -15
- package/lib/cloudflare/miniflare/miniflare-worker-proxy.js.map +1 -1
- package/lib/cloudflare/miniflare/remote-binding-proxy.d.ts +1 -5
- package/lib/cloudflare/miniflare/remote-binding-proxy.d.ts.map +1 -1
- package/lib/cloudflare/miniflare/remote-binding-proxy.js +17 -22
- package/lib/cloudflare/miniflare/remote-binding-proxy.js.map +1 -1
- package/lib/cloudflare/website.js +4 -4
- package/lib/cloudflare/website.js.map +1 -1
- package/lib/cloudflare/worker-bundle.d.ts +2 -0
- package/lib/cloudflare/worker-bundle.d.ts.map +1 -1
- package/lib/cloudflare/worker-bundle.js +21 -4
- package/lib/cloudflare/worker-bundle.js.map +1 -1
- package/lib/cloudflare/worker.d.ts.map +1 -1
- package/lib/cloudflare/worker.js +76 -97
- package/lib/cloudflare/worker.js.map +1 -1
- package/lib/context.d.ts +11 -0
- package/lib/context.d.ts.map +1 -1
- package/lib/context.js +8 -0
- package/lib/context.js.map +1 -1
- package/lib/scope.d.ts +12 -0
- package/lib/scope.d.ts.map +1 -1
- package/lib/scope.js +25 -0
- package/lib/scope.js.map +1 -1
- package/lib/util/telemetry/client.d.ts +3 -4
- package/lib/util/telemetry/client.d.ts.map +1 -1
- package/lib/util/telemetry/client.js +19 -25
- package/lib/util/telemetry/client.js.map +1 -1
- package/lib/util/telemetry/types.d.ts +1 -1
- package/lib/util/telemetry/types.d.ts.map +1 -1
- package/package.json +3 -2
- package/src/alchemy.ts +7 -0
- package/src/cloudflare/bundle/plugin-alias.ts +49 -0
- package/src/cloudflare/bundle/plugin-hot-reload.ts +4 -3
- package/src/cloudflare/compatibility-date.gen.ts +1 -1
- package/src/cloudflare/compatibility-presets.ts +1 -1
- package/src/cloudflare/local-dev-cloudflare-shim.ts +11 -5
- package/src/cloudflare/miniflare/build-worker-options.ts +454 -0
- package/src/cloudflare/miniflare/miniflare-controller.ts +128 -0
- package/src/cloudflare/miniflare/miniflare-worker-proxy.ts +6 -15
- package/src/cloudflare/miniflare/remote-binding-proxy.ts +18 -23
- package/src/cloudflare/website.ts +4 -4
- package/src/cloudflare/worker-bundle.ts +30 -4
- package/src/cloudflare/worker.ts +90 -125
- package/src/context.ts +19 -0
- package/src/scope.ts +27 -0
- package/src/util/telemetry/client.ts +23 -33
- package/src/util/telemetry/types.ts +3 -3
- package/lib/cloudflare/miniflare/miniflare-worker-options.d.ts +0 -14
- package/lib/cloudflare/miniflare/miniflare-worker-options.d.ts.map +0 -1
- package/lib/cloudflare/miniflare/miniflare-worker-options.js +0 -468
- package/lib/cloudflare/miniflare/miniflare-worker-options.js.map +0 -1
- package/lib/cloudflare/miniflare/miniflare.d.ts +0 -40
- package/lib/cloudflare/miniflare/miniflare.d.ts.map +0 -1
- package/lib/cloudflare/miniflare/miniflare.js +0 -142
- package/lib/cloudflare/miniflare/miniflare.js.map +0 -1
- package/src/cloudflare/miniflare/miniflare-worker-options.ts +0 -599
- package/src/cloudflare/miniflare/miniflare.ts +0 -183
|
@@ -3,11 +3,9 @@ import assert from "node:assert";
|
|
|
3
3
|
|
|
4
4
|
export interface HotReloadPluginProps {
|
|
5
5
|
onBuildStart?: () => void | Promise<void>;
|
|
6
|
-
onBuildEnd?: (result: esbuild.BuildResult) => void | Promise<void>;
|
|
7
|
-
onBuildError?: (errors: esbuild.Message[]) => void | Promise<void>;
|
|
8
6
|
}
|
|
9
7
|
|
|
10
|
-
export function createHotReloadPlugin(): {
|
|
8
|
+
export function createHotReloadPlugin(props?: HotReloadPluginProps): {
|
|
11
9
|
plugin: esbuild.Plugin;
|
|
12
10
|
iterator: AsyncIterable<esbuild.BuildResult>;
|
|
13
11
|
} {
|
|
@@ -22,6 +20,9 @@ export function createHotReloadPlugin(): {
|
|
|
22
20
|
const plugin: esbuild.Plugin = {
|
|
23
21
|
name: "alchemy-hot-reload",
|
|
24
22
|
setup(build) {
|
|
23
|
+
if (props?.onBuildStart) {
|
|
24
|
+
build.onStart(props.onBuildStart);
|
|
25
|
+
}
|
|
25
26
|
build.onEnd(async (result) => {
|
|
26
27
|
assert(_controller);
|
|
27
28
|
_controller.enqueue(result);
|
|
@@ -13,7 +13,7 @@ export const COMPATIBILITY_PRESETS = {
|
|
|
13
13
|
* Node.js compatibility preset
|
|
14
14
|
* Enables Node.js APIs and runtime compatibility
|
|
15
15
|
*/
|
|
16
|
-
node: ["nodejs_compat"],
|
|
16
|
+
node: ["nodejs_compat", "nodejs_compat_populate_process_env"],
|
|
17
17
|
} as const;
|
|
18
18
|
|
|
19
19
|
export type CompatibilityPreset = keyof typeof COMPATIBILITY_PRESETS;
|
|
@@ -3,7 +3,9 @@ import { dedent } from "../util/dedent.ts";
|
|
|
3
3
|
/**
|
|
4
4
|
* TanStackStart server functions and middleware run in Node.js intead of Miniflare when using `vite dev`.
|
|
5
5
|
*
|
|
6
|
-
* This plugin polyfills the cloudflare:workers module during the dev server phase.
|
|
6
|
+
* This plugin polyfills the cloudflare:workers module & includes `process.env` during the dev server phase.
|
|
7
|
+
*
|
|
8
|
+
* @see https://developers.cloudflare.com/workers/framework-guides/web-apps/tanstack/#using-cloudflare-bindings
|
|
7
9
|
*/
|
|
8
10
|
export function cloudflareWorkersDevEnvironmentShim() {
|
|
9
11
|
return {
|
|
@@ -14,12 +16,16 @@ export function cloudflareWorkersDevEnvironmentShim() {
|
|
|
14
16
|
if (id === "cloudflare:workers") return id; // tell Vite we handled it
|
|
15
17
|
},
|
|
16
18
|
load(id: string) {
|
|
17
|
-
if (id === "cloudflare:workers")
|
|
19
|
+
if (id === "cloudflare:workers") {
|
|
20
|
+
const processEnv = JSON.stringify(process.env);
|
|
21
|
+
|
|
22
|
+
// @see https://developers.cloudflare.com/workers/wrangler/api/#getplatformproxy
|
|
18
23
|
return dedent`
|
|
19
24
|
import { getPlatformProxy } from "wrangler";
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
|
|
25
|
+
const cloudflare = await getPlatformProxy({ experimental: { remoteBindings: true } });
|
|
26
|
+
export const env = Object.assign(${processEnv}, cloudflare.env);
|
|
27
|
+
`.trim();
|
|
28
|
+
}
|
|
23
29
|
},
|
|
24
30
|
} as const;
|
|
25
31
|
}
|
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
import * as miniflare from "miniflare";
|
|
2
|
+
import { assertNever } from "../../util/assert-never.ts";
|
|
3
|
+
import type { HTTPServer } from "../../util/http.ts";
|
|
4
|
+
import {
|
|
5
|
+
Self,
|
|
6
|
+
type Binding,
|
|
7
|
+
type Bindings,
|
|
8
|
+
type WorkerBindingService,
|
|
9
|
+
type WorkerBindingSpec,
|
|
10
|
+
} from "../bindings.ts";
|
|
11
|
+
import { isQueueEventSource, type EventSource } from "../event-source.ts";
|
|
12
|
+
import type { WorkerBundle, WorkerBundleSource } from "../worker-bundle.ts";
|
|
13
|
+
import type { AssetsConfig } from "../worker.ts";
|
|
14
|
+
import { createRemoteProxyWorker } from "./remote-binding-proxy.ts";
|
|
15
|
+
|
|
16
|
+
export interface MiniflareWorkerInput {
|
|
17
|
+
id: string;
|
|
18
|
+
name: string;
|
|
19
|
+
compatibilityDate: string | undefined;
|
|
20
|
+
compatibilityFlags: string[] | undefined;
|
|
21
|
+
bindings: Bindings | undefined;
|
|
22
|
+
eventSources: EventSource[] | undefined;
|
|
23
|
+
assets: AssetsConfig | undefined;
|
|
24
|
+
bundle: WorkerBundleSource;
|
|
25
|
+
port: number | undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
type RemoteOnlyBindingType =
|
|
29
|
+
| "ai"
|
|
30
|
+
| "ai_gateway"
|
|
31
|
+
| "browser"
|
|
32
|
+
| "dispatch_namespace"
|
|
33
|
+
| "mtls_certificate"
|
|
34
|
+
| "vectorize";
|
|
35
|
+
type RemoteOptionalBindingType =
|
|
36
|
+
| "d1"
|
|
37
|
+
| "images"
|
|
38
|
+
| "kv_namespace"
|
|
39
|
+
| "queue"
|
|
40
|
+
| "r2_bucket";
|
|
41
|
+
|
|
42
|
+
type RemoteBinding =
|
|
43
|
+
| (Extract<
|
|
44
|
+
WorkerBindingSpec,
|
|
45
|
+
{
|
|
46
|
+
type: RemoteOnlyBindingType | RemoteOptionalBindingType;
|
|
47
|
+
}
|
|
48
|
+
> & {
|
|
49
|
+
raw: true;
|
|
50
|
+
})
|
|
51
|
+
| WorkerBindingService;
|
|
52
|
+
|
|
53
|
+
type BaseWorkerOptions = {
|
|
54
|
+
[K in keyof miniflare.WorkerOptions]: K extends "compatibilityFlags"
|
|
55
|
+
? miniflare.WorkerOptions[K]
|
|
56
|
+
: Exclude<miniflare.WorkerOptions[K], string[]>;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export const buildWorkerOptions = async (
|
|
60
|
+
input: MiniflareWorkerInput,
|
|
61
|
+
): Promise<{
|
|
62
|
+
watch: (signal: AbortSignal) => AsyncGenerator<miniflare.WorkerOptions>;
|
|
63
|
+
remoteProxy: HTTPServer | undefined;
|
|
64
|
+
}> => {
|
|
65
|
+
const remoteBindings: RemoteBinding[] = [];
|
|
66
|
+
const options: Partial<BaseWorkerOptions> = {
|
|
67
|
+
name: input.name,
|
|
68
|
+
compatibilityDate: input.compatibilityDate,
|
|
69
|
+
compatibilityFlags: input.compatibilityFlags,
|
|
70
|
+
// TODO: Setting `proxy: true` here causes the following error when connecting via a websocket:
|
|
71
|
+
// workerd/io/worker.c++:2164: info: uncaught exception; source = Uncaught (in promise); stack = TypeError: Invalid URL string.
|
|
72
|
+
unsafeDirectSockets: [{ proxy: false }],
|
|
73
|
+
containerEngine: {
|
|
74
|
+
localDocker: {
|
|
75
|
+
socketPath:
|
|
76
|
+
process.platform === "win32"
|
|
77
|
+
? "//./pipe/docker_engine"
|
|
78
|
+
: "unix:///var/run/docker.sock",
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
for (const [key, binding] of Object.entries(input.bindings ?? {})) {
|
|
83
|
+
if (typeof binding === "string") {
|
|
84
|
+
(options.bindings ??= {})[key] = binding;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (binding === Self) {
|
|
88
|
+
(options.serviceBindings ??= {})[key] = miniflare.kCurrentWorker;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
switch (binding.type) {
|
|
92
|
+
case "ai":
|
|
93
|
+
case "ai_gateway": {
|
|
94
|
+
remoteBindings.push({
|
|
95
|
+
type: "ai",
|
|
96
|
+
name: key,
|
|
97
|
+
raw: true,
|
|
98
|
+
});
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
case "analytics_engine": {
|
|
102
|
+
(options.analyticsEngineDatasets ??= {})[key] = {
|
|
103
|
+
dataset: binding.dataset,
|
|
104
|
+
};
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
case "assets": {
|
|
108
|
+
options.assets = {
|
|
109
|
+
binding: key,
|
|
110
|
+
directory: binding.path,
|
|
111
|
+
assetConfig: {
|
|
112
|
+
html_handling: input.assets?.html_handling,
|
|
113
|
+
not_found_handling: input.assets?.not_found_handling,
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
case "browser": {
|
|
119
|
+
remoteBindings.push({
|
|
120
|
+
type: "browser",
|
|
121
|
+
name: key,
|
|
122
|
+
raw: true,
|
|
123
|
+
});
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
case "container": {
|
|
127
|
+
(options.durableObjects ??= {})[key] = {
|
|
128
|
+
className: binding.className,
|
|
129
|
+
scriptName: binding.scriptName,
|
|
130
|
+
useSQLite: binding.sqlite,
|
|
131
|
+
container: {
|
|
132
|
+
imageName: binding.image.imageRef,
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
case "d1": {
|
|
138
|
+
if (isRemoteBinding(binding)) {
|
|
139
|
+
remoteBindings.push({
|
|
140
|
+
type: "d1",
|
|
141
|
+
name: key,
|
|
142
|
+
id: binding.id,
|
|
143
|
+
raw: true,
|
|
144
|
+
});
|
|
145
|
+
} else {
|
|
146
|
+
(options.d1Databases ??= {})[key] = binding.id;
|
|
147
|
+
}
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
case "dispatch_namespace": {
|
|
151
|
+
remoteBindings.push({
|
|
152
|
+
type: "dispatch_namespace",
|
|
153
|
+
name: key,
|
|
154
|
+
namespace: binding.namespace,
|
|
155
|
+
raw: true,
|
|
156
|
+
});
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
case "durable_object_namespace": {
|
|
160
|
+
(options.durableObjects ??= {})[key] = {
|
|
161
|
+
className: binding.className,
|
|
162
|
+
scriptName: binding.scriptName,
|
|
163
|
+
useSQLite: binding.sqlite,
|
|
164
|
+
};
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
case "hyperdrive": {
|
|
168
|
+
throw new Error("Hyperdrive bindings are not supported in local mode");
|
|
169
|
+
}
|
|
170
|
+
case "images": {
|
|
171
|
+
if (isRemoteBinding(binding)) {
|
|
172
|
+
remoteBindings.push({
|
|
173
|
+
type: "images",
|
|
174
|
+
name: key,
|
|
175
|
+
raw: true,
|
|
176
|
+
});
|
|
177
|
+
} else {
|
|
178
|
+
options.images = {
|
|
179
|
+
binding: key,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
case "kv_namespace": {
|
|
185
|
+
const namespaceId =
|
|
186
|
+
"namespaceId" in binding ? binding.namespaceId : binding.id;
|
|
187
|
+
if (isRemoteBinding(binding)) {
|
|
188
|
+
remoteBindings.push({
|
|
189
|
+
type: "kv_namespace",
|
|
190
|
+
name: key,
|
|
191
|
+
namespace_id: namespaceId,
|
|
192
|
+
raw: true,
|
|
193
|
+
});
|
|
194
|
+
} else {
|
|
195
|
+
(options.kvNamespaces ??= {})[key] = namespaceId;
|
|
196
|
+
}
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
case "json": {
|
|
200
|
+
(options.bindings ??= {})[key] = binding.json;
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
case "queue": {
|
|
204
|
+
if (isRemoteBinding(binding)) {
|
|
205
|
+
remoteBindings.push({
|
|
206
|
+
type: "queue",
|
|
207
|
+
name: key,
|
|
208
|
+
queue_name: binding.name,
|
|
209
|
+
raw: true,
|
|
210
|
+
});
|
|
211
|
+
} else {
|
|
212
|
+
(options.queueProducers ??= {})[key] = {
|
|
213
|
+
queueName: binding.name,
|
|
214
|
+
deliveryDelay: binding.settings?.deliveryDelay,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
case "pipeline": {
|
|
220
|
+
(options.pipelines ??= {})[key] = binding.id;
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
case "ratelimit": {
|
|
224
|
+
(options.ratelimits ??= {})[key] = {
|
|
225
|
+
simple: binding.simple,
|
|
226
|
+
};
|
|
227
|
+
break;
|
|
228
|
+
}
|
|
229
|
+
case "secret": {
|
|
230
|
+
(options.bindings ??= {})[key] = binding.unencrypted;
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
case "secret_key": {
|
|
234
|
+
throw new Error("Secret keys are not supported in local mode");
|
|
235
|
+
}
|
|
236
|
+
case "secrets_store_secret": {
|
|
237
|
+
(options.secretsStoreSecrets ??= {})[key] = {
|
|
238
|
+
store_id: binding.storeId,
|
|
239
|
+
secret_name: binding.name,
|
|
240
|
+
};
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
case "r2_bucket": {
|
|
244
|
+
if (isRemoteBinding(binding)) {
|
|
245
|
+
remoteBindings.push({
|
|
246
|
+
type: "r2_bucket",
|
|
247
|
+
name: key,
|
|
248
|
+
bucket_name: binding.name,
|
|
249
|
+
raw: true,
|
|
250
|
+
});
|
|
251
|
+
} else {
|
|
252
|
+
(options.r2Buckets ??= {})[key] = binding.name;
|
|
253
|
+
}
|
|
254
|
+
break;
|
|
255
|
+
}
|
|
256
|
+
case "service": {
|
|
257
|
+
const service = "service" in binding ? binding.service : binding.name;
|
|
258
|
+
const normalized: WorkerBindingService = {
|
|
259
|
+
type: "service",
|
|
260
|
+
name: key,
|
|
261
|
+
service,
|
|
262
|
+
environment:
|
|
263
|
+
"environment" in binding ? binding.environment : undefined,
|
|
264
|
+
};
|
|
265
|
+
if (isRemoteBinding(binding)) {
|
|
266
|
+
remoteBindings.push(normalized);
|
|
267
|
+
} else {
|
|
268
|
+
(options.serviceBindings ??= {})[key] = service;
|
|
269
|
+
}
|
|
270
|
+
break;
|
|
271
|
+
}
|
|
272
|
+
case "vectorize": {
|
|
273
|
+
remoteBindings.push({
|
|
274
|
+
type: "vectorize",
|
|
275
|
+
name: key,
|
|
276
|
+
index_name: binding.name,
|
|
277
|
+
raw: true,
|
|
278
|
+
});
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
case "version_metadata": {
|
|
282
|
+
(options.bindings ??= {})[key] = {
|
|
283
|
+
id: crypto.randomUUID(),
|
|
284
|
+
tag: "",
|
|
285
|
+
timestamp: "0",
|
|
286
|
+
};
|
|
287
|
+
break;
|
|
288
|
+
}
|
|
289
|
+
case "workflow": {
|
|
290
|
+
(options.workflows ??= {})[key] = {
|
|
291
|
+
name: binding.workflowName,
|
|
292
|
+
className: binding.className,
|
|
293
|
+
scriptName: binding.scriptName,
|
|
294
|
+
};
|
|
295
|
+
break;
|
|
296
|
+
}
|
|
297
|
+
default: {
|
|
298
|
+
assertNever(binding);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
for (const eventSource of input.eventSources ?? []) {
|
|
303
|
+
const queue = "queue" in eventSource ? eventSource.queue : eventSource;
|
|
304
|
+
if (queue.dev?.remote) {
|
|
305
|
+
throw new Error(
|
|
306
|
+
`Locally emulated workers cannot consume remote queues. Worker "${input.name}" is locally emulated but is consuming remote queue "${queue.name}".`,
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
if (isQueueEventSource(eventSource)) {
|
|
310
|
+
(options.queueConsumers ??= {})[queue.name] = {
|
|
311
|
+
maxBatchSize: eventSource.settings?.batchSize,
|
|
312
|
+
maxBatchTimeout: eventSource.settings?.maxWaitTimeMs,
|
|
313
|
+
maxRetries: eventSource.settings?.maxRetries,
|
|
314
|
+
retryDelay: eventSource.settings?.retryDelay,
|
|
315
|
+
};
|
|
316
|
+
} else {
|
|
317
|
+
(options.queueConsumers ??= {})[eventSource.name] = {};
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
async function* watch(signal: AbortSignal) {
|
|
321
|
+
for await (const bundle of input.bundle.watch(signal)) {
|
|
322
|
+
const { modules, rootPath } = normalizeBundle(bundle);
|
|
323
|
+
yield {
|
|
324
|
+
...options,
|
|
325
|
+
modules,
|
|
326
|
+
rootPath,
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
if (remoteBindings.length > 0) {
|
|
331
|
+
const remoteProxy = await createRemoteProxyWorker({
|
|
332
|
+
name: input.name,
|
|
333
|
+
bindings: remoteBindings,
|
|
334
|
+
});
|
|
335
|
+
for (const binding of remoteBindings) {
|
|
336
|
+
switch (binding.type) {
|
|
337
|
+
case "ai":
|
|
338
|
+
options.ai = {
|
|
339
|
+
binding: binding.name,
|
|
340
|
+
remoteProxyConnectionString: remoteProxy.connectionString,
|
|
341
|
+
};
|
|
342
|
+
break;
|
|
343
|
+
case "browser":
|
|
344
|
+
options.browserRendering = {
|
|
345
|
+
binding: binding.name,
|
|
346
|
+
remoteProxyConnectionString: remoteProxy.connectionString,
|
|
347
|
+
};
|
|
348
|
+
break;
|
|
349
|
+
case "d1":
|
|
350
|
+
(options.d1Databases ??= {})[binding.name] = {
|
|
351
|
+
id: binding.id,
|
|
352
|
+
remoteProxyConnectionString: remoteProxy.connectionString,
|
|
353
|
+
};
|
|
354
|
+
break;
|
|
355
|
+
case "dispatch_namespace":
|
|
356
|
+
(options.dispatchNamespaces ??= {})[binding.name] = {
|
|
357
|
+
namespace: binding.namespace,
|
|
358
|
+
remoteProxyConnectionString: remoteProxy.connectionString,
|
|
359
|
+
};
|
|
360
|
+
break;
|
|
361
|
+
case "kv_namespace":
|
|
362
|
+
(options.kvNamespaces ??= {})[binding.name] = {
|
|
363
|
+
id: binding.namespace_id,
|
|
364
|
+
remoteProxyConnectionString: remoteProxy.connectionString,
|
|
365
|
+
};
|
|
366
|
+
break;
|
|
367
|
+
case "images":
|
|
368
|
+
options.images = {
|
|
369
|
+
binding: binding.name,
|
|
370
|
+
remoteProxyConnectionString: remoteProxy.connectionString,
|
|
371
|
+
};
|
|
372
|
+
break;
|
|
373
|
+
case "mtls_certificate":
|
|
374
|
+
(options.mtlsCertificates ??= {})[binding.name] = {
|
|
375
|
+
certificate_id: binding.certificate_id,
|
|
376
|
+
remoteProxyConnectionString: remoteProxy.connectionString,
|
|
377
|
+
};
|
|
378
|
+
break;
|
|
379
|
+
case "queue":
|
|
380
|
+
(options.queueProducers ??= {})[binding.name] = {
|
|
381
|
+
queueName: binding.queue_name,
|
|
382
|
+
remoteProxyConnectionString: remoteProxy.connectionString,
|
|
383
|
+
};
|
|
384
|
+
break;
|
|
385
|
+
case "r2_bucket":
|
|
386
|
+
(options.r2Buckets ??= {})[binding.name] = {
|
|
387
|
+
id: binding.bucket_name,
|
|
388
|
+
remoteProxyConnectionString: remoteProxy.connectionString,
|
|
389
|
+
};
|
|
390
|
+
break;
|
|
391
|
+
case "service":
|
|
392
|
+
(options.serviceBindings ??= {})[binding.name] = {
|
|
393
|
+
name: binding.name,
|
|
394
|
+
remoteProxyConnectionString: remoteProxy.connectionString,
|
|
395
|
+
};
|
|
396
|
+
break;
|
|
397
|
+
case "vectorize":
|
|
398
|
+
(options.vectorize ??= {})[binding.name] = {
|
|
399
|
+
index_name: binding.index_name,
|
|
400
|
+
remoteProxyConnectionString: remoteProxy.connectionString,
|
|
401
|
+
};
|
|
402
|
+
break;
|
|
403
|
+
default: {
|
|
404
|
+
assertNever(binding);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
return {
|
|
409
|
+
watch,
|
|
410
|
+
remoteProxy: remoteProxy.server,
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
return {
|
|
414
|
+
watch,
|
|
415
|
+
remoteProxy: undefined,
|
|
416
|
+
};
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
const moduleTypes = {
|
|
420
|
+
esm: "ESModule",
|
|
421
|
+
cjs: "CommonJS",
|
|
422
|
+
text: "Text",
|
|
423
|
+
data: "Data",
|
|
424
|
+
wasm: "CompiledWasm",
|
|
425
|
+
sourcemap: "Text",
|
|
426
|
+
} as const;
|
|
427
|
+
|
|
428
|
+
const normalizeBundle = (bundle: WorkerBundle) => {
|
|
429
|
+
const modules = bundle.modules.map((module) => ({
|
|
430
|
+
type: moduleTypes[module.type],
|
|
431
|
+
path: module.path,
|
|
432
|
+
contents: module.content,
|
|
433
|
+
}));
|
|
434
|
+
const entry = modules.find((module) => module.path === bundle.entrypoint);
|
|
435
|
+
if (!entry) {
|
|
436
|
+
throw new Error(`Entrypoint "${bundle.entrypoint}" not found in bundle.`);
|
|
437
|
+
}
|
|
438
|
+
return {
|
|
439
|
+
modules: [entry, ...modules.filter((module) => module.path !== entry.path)],
|
|
440
|
+
rootPath: bundle.root,
|
|
441
|
+
};
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
const isRemoteBinding = (binding: Binding) => {
|
|
445
|
+
if (typeof binding === "string" || binding === Self) {
|
|
446
|
+
return false;
|
|
447
|
+
}
|
|
448
|
+
return (
|
|
449
|
+
"dev" in binding &&
|
|
450
|
+
typeof binding.dev === "object" &&
|
|
451
|
+
"remote" in binding.dev &&
|
|
452
|
+
!!binding.dev.remote
|
|
453
|
+
);
|
|
454
|
+
};
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import * as miniflare from "miniflare";
|
|
2
|
+
import assert from "node:assert";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { findOpenPort } from "../../util/find-open-port.ts";
|
|
5
|
+
import type { HTTPServer } from "../../util/http.ts";
|
|
6
|
+
import { logger } from "../../util/logger.ts";
|
|
7
|
+
import { AsyncMutex } from "../../util/mutex.ts";
|
|
8
|
+
import {
|
|
9
|
+
buildWorkerOptions,
|
|
10
|
+
type MiniflareWorkerInput,
|
|
11
|
+
} from "./build-worker-options.ts";
|
|
12
|
+
import { MiniflareWorkerProxy } from "./miniflare-worker-proxy.ts";
|
|
13
|
+
|
|
14
|
+
declare global {
|
|
15
|
+
var ALCHEMY_MINIFLARE_CONTROLLER: MiniflareController | undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class MiniflareController {
|
|
19
|
+
abort = new AbortController();
|
|
20
|
+
miniflare: miniflare.Miniflare | undefined;
|
|
21
|
+
options = new Map<string, miniflare.WorkerOptions>();
|
|
22
|
+
localProxies = new Map<string, MiniflareWorkerProxy>();
|
|
23
|
+
remoteProxies = new Map<string, HTTPServer>();
|
|
24
|
+
mutex = new AsyncMutex();
|
|
25
|
+
|
|
26
|
+
static get singleton() {
|
|
27
|
+
globalThis.ALCHEMY_MINIFLARE_CONTROLLER ??= new MiniflareController();
|
|
28
|
+
return globalThis.ALCHEMY_MINIFLARE_CONTROLLER;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async add(input: MiniflareWorkerInput) {
|
|
32
|
+
const { watch, remoteProxy } = await buildWorkerOptions(input);
|
|
33
|
+
if (remoteProxy) {
|
|
34
|
+
this.remoteProxies.set(input.name, remoteProxy);
|
|
35
|
+
}
|
|
36
|
+
const watcher = watch(this.abort.signal);
|
|
37
|
+
const first = await watcher.next();
|
|
38
|
+
assert(first.value, "First value is undefined");
|
|
39
|
+
this.options.set(input.name, first.value);
|
|
40
|
+
const miniflare = await this.update();
|
|
41
|
+
const proxy = new MiniflareWorkerProxy({
|
|
42
|
+
name: input.name,
|
|
43
|
+
port: input.port ?? (await findOpenPort()),
|
|
44
|
+
miniflare,
|
|
45
|
+
});
|
|
46
|
+
this.localProxies.set(input.name, proxy);
|
|
47
|
+
void this.watch(input.id, watcher);
|
|
48
|
+
logger.task(input.id, {
|
|
49
|
+
message: `Ready at ${proxy.url}`,
|
|
50
|
+
status: "success",
|
|
51
|
+
resource: input.id,
|
|
52
|
+
prefix: "dev",
|
|
53
|
+
prefixColor: "cyanBright",
|
|
54
|
+
});
|
|
55
|
+
return proxy.url;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
private async watch(
|
|
59
|
+
id: string,
|
|
60
|
+
watcher: AsyncGenerator<miniflare.WorkerOptions>,
|
|
61
|
+
) {
|
|
62
|
+
for await (const options of watcher) {
|
|
63
|
+
this.options.set(options.name!, options);
|
|
64
|
+
await this.update();
|
|
65
|
+
logger.task(id, {
|
|
66
|
+
message: "Updated",
|
|
67
|
+
status: "success",
|
|
68
|
+
resource: id,
|
|
69
|
+
prefix: "dev",
|
|
70
|
+
prefixColor: "cyanBright",
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
private async update() {
|
|
76
|
+
return await this.mutex.lock(async () => {
|
|
77
|
+
const options: miniflare.MiniflareOptions = {
|
|
78
|
+
workers: Array.from(this.options.values()),
|
|
79
|
+
defaultPersistRoot: path.join(process.cwd(), ".alchemy/miniflare"),
|
|
80
|
+
unsafeDevRegistryPath: miniflare.getDefaultDevRegistryPath(),
|
|
81
|
+
analyticsEngineDatasetsPersist: true,
|
|
82
|
+
cachePersist: true,
|
|
83
|
+
d1Persist: true,
|
|
84
|
+
durableObjectsPersist: true,
|
|
85
|
+
kvPersist: true,
|
|
86
|
+
r2Persist: true,
|
|
87
|
+
secretsStorePersist: true,
|
|
88
|
+
workflowsPersist: true,
|
|
89
|
+
log: process.env.DEBUG
|
|
90
|
+
? new miniflare.Log(miniflare.LogLevel.DEBUG)
|
|
91
|
+
: undefined,
|
|
92
|
+
};
|
|
93
|
+
return await this.setMiniflareOptions(options);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
private async setMiniflareOptions(options: miniflare.MiniflareOptions) {
|
|
98
|
+
try {
|
|
99
|
+
if (this.miniflare) {
|
|
100
|
+
await this.miniflare.setOptions(options);
|
|
101
|
+
} else {
|
|
102
|
+
this.miniflare = new miniflare.Miniflare(options);
|
|
103
|
+
await this.miniflare.ready;
|
|
104
|
+
}
|
|
105
|
+
return this.miniflare;
|
|
106
|
+
} catch (error) {
|
|
107
|
+
if (
|
|
108
|
+
error instanceof miniflare.MiniflareCoreError &&
|
|
109
|
+
error.code === "ERR_MODULE_STRING_SCRIPT"
|
|
110
|
+
) {
|
|
111
|
+
throw new Error(
|
|
112
|
+
'Miniflare detected an external dependency that could not be resolved. This typically occurs when the "nodejs_compat" or "nodejs_als" compatibility flag is not enabled.',
|
|
113
|
+
);
|
|
114
|
+
} else {
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async dispose() {
|
|
121
|
+
this.abort.abort();
|
|
122
|
+
await Promise.all([
|
|
123
|
+
this.miniflare?.dispose(),
|
|
124
|
+
...this.localProxies.values().map((proxy) => proxy.close()),
|
|
125
|
+
...this.remoteProxies.values().map((proxy) => proxy.close()),
|
|
126
|
+
]);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -55,18 +55,10 @@ export class MiniflareWorkerProxy {
|
|
|
55
55
|
return;
|
|
56
56
|
}
|
|
57
57
|
this.wss.handleUpgrade(req, socket, head, (client) => {
|
|
58
|
-
client.on("message", (event, binary) => {
|
|
59
|
-
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
server.close(code, reason);
|
|
63
|
-
});
|
|
64
|
-
server.on("message", (event, binary) => {
|
|
65
|
-
client.send(event, { binary });
|
|
66
|
-
});
|
|
67
|
-
server.on("close", (code, reason) => {
|
|
68
|
-
client.close(code, reason);
|
|
69
|
-
});
|
|
58
|
+
client.on("message", (event, binary) => server.send(event, { binary }));
|
|
59
|
+
client.on("close", (code, reason) => server.close(code, reason));
|
|
60
|
+
server.on("message", (event, binary) => client.send(event, { binary }));
|
|
61
|
+
server.on("close", (code, reason) => client.close(code, reason));
|
|
70
62
|
this.wss.emit("connection", client, req);
|
|
71
63
|
});
|
|
72
64
|
}
|
|
@@ -157,9 +149,8 @@ const writeMiniflareResponseToNode = (
|
|
|
157
149
|
out: http.ServerResponse,
|
|
158
150
|
) => {
|
|
159
151
|
out.statusCode = res.status;
|
|
160
|
-
res.headers.forEach((value, key) =>
|
|
161
|
-
|
|
162
|
-
});
|
|
152
|
+
res.headers.forEach((value, key) => out.setHeader(key, value));
|
|
153
|
+
|
|
163
154
|
if (res.body) {
|
|
164
155
|
Readable.fromWeb(res.body).pipe(out, { end: true });
|
|
165
156
|
} else {
|