alchemy 0.55.3 → 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.
Files changed (74) hide show
  1. package/bin/alchemy.js +46 -246
  2. package/lib/alchemy.d.ts.map +1 -1
  3. package/lib/alchemy.js +7 -0
  4. package/lib/alchemy.js.map +1 -1
  5. package/lib/cloudflare/bundle/plugin-hot-reload.d.ts +1 -3
  6. package/lib/cloudflare/bundle/plugin-hot-reload.d.ts.map +1 -1
  7. package/lib/cloudflare/bundle/plugin-hot-reload.js +4 -1
  8. package/lib/cloudflare/bundle/plugin-hot-reload.js.map +1 -1
  9. package/lib/cloudflare/compatibility-date.gen.d.ts +1 -1
  10. package/lib/cloudflare/compatibility-date.gen.js +1 -1
  11. package/lib/cloudflare/compatibility-presets.d.ts +1 -1
  12. package/lib/cloudflare/compatibility-presets.js +1 -1
  13. package/lib/cloudflare/compatibility-presets.js.map +1 -1
  14. package/lib/cloudflare/local-dev-cloudflare-shim.d.ts +3 -1
  15. package/lib/cloudflare/local-dev-cloudflare-shim.d.ts.map +1 -1
  16. package/lib/cloudflare/local-dev-cloudflare-shim.js +10 -5
  17. package/lib/cloudflare/local-dev-cloudflare-shim.js.map +1 -1
  18. package/lib/cloudflare/miniflare/build-worker-options.d.ts +22 -0
  19. package/lib/cloudflare/miniflare/build-worker-options.d.ts.map +1 -0
  20. package/lib/cloudflare/miniflare/build-worker-options.js +394 -0
  21. package/lib/cloudflare/miniflare/build-worker-options.js.map +1 -0
  22. package/lib/cloudflare/miniflare/miniflare-controller.d.ts +23 -0
  23. package/lib/cloudflare/miniflare/miniflare-controller.d.ts.map +1 -0
  24. package/lib/cloudflare/miniflare/miniflare-controller.js +110 -0
  25. package/lib/cloudflare/miniflare/miniflare-controller.js.map +1 -0
  26. package/lib/cloudflare/miniflare/miniflare-worker-proxy.d.ts.map +1 -1
  27. package/lib/cloudflare/miniflare/miniflare-worker-proxy.js +5 -15
  28. package/lib/cloudflare/miniflare/miniflare-worker-proxy.js.map +1 -1
  29. package/lib/cloudflare/miniflare/remote-binding-proxy.d.ts +1 -5
  30. package/lib/cloudflare/miniflare/remote-binding-proxy.d.ts.map +1 -1
  31. package/lib/cloudflare/miniflare/remote-binding-proxy.js +17 -22
  32. package/lib/cloudflare/miniflare/remote-binding-proxy.js.map +1 -1
  33. package/lib/cloudflare/website.js +4 -4
  34. package/lib/cloudflare/website.js.map +1 -1
  35. package/lib/cloudflare/worker-bundle.d.ts +2 -0
  36. package/lib/cloudflare/worker-bundle.d.ts.map +1 -1
  37. package/lib/cloudflare/worker-bundle.js +19 -3
  38. package/lib/cloudflare/worker-bundle.js.map +1 -1
  39. package/lib/cloudflare/worker.d.ts.map +1 -1
  40. package/lib/cloudflare/worker.js +76 -97
  41. package/lib/cloudflare/worker.js.map +1 -1
  42. package/lib/context.d.ts +11 -0
  43. package/lib/context.d.ts.map +1 -1
  44. package/lib/context.js +8 -0
  45. package/lib/context.js.map +1 -1
  46. package/lib/scope.d.ts +12 -0
  47. package/lib/scope.d.ts.map +1 -1
  48. package/lib/scope.js +25 -0
  49. package/lib/scope.js.map +1 -1
  50. package/package.json +3 -2
  51. package/src/alchemy.ts +7 -0
  52. package/src/cloudflare/bundle/plugin-hot-reload.ts +4 -3
  53. package/src/cloudflare/compatibility-date.gen.ts +1 -1
  54. package/src/cloudflare/compatibility-presets.ts +1 -1
  55. package/src/cloudflare/local-dev-cloudflare-shim.ts +11 -5
  56. package/src/cloudflare/miniflare/build-worker-options.ts +454 -0
  57. package/src/cloudflare/miniflare/miniflare-controller.ts +128 -0
  58. package/src/cloudflare/miniflare/miniflare-worker-proxy.ts +6 -15
  59. package/src/cloudflare/miniflare/remote-binding-proxy.ts +18 -23
  60. package/src/cloudflare/website.ts +4 -4
  61. package/src/cloudflare/worker-bundle.ts +28 -3
  62. package/src/cloudflare/worker.ts +90 -125
  63. package/src/context.ts +19 -0
  64. package/src/scope.ts +27 -0
  65. package/lib/cloudflare/miniflare/miniflare-worker-options.d.ts +0 -14
  66. package/lib/cloudflare/miniflare/miniflare-worker-options.d.ts.map +0 -1
  67. package/lib/cloudflare/miniflare/miniflare-worker-options.js +0 -468
  68. package/lib/cloudflare/miniflare/miniflare-worker-options.js.map +0 -1
  69. package/lib/cloudflare/miniflare/miniflare.d.ts +0 -40
  70. package/lib/cloudflare/miniflare/miniflare.d.ts.map +0 -1
  71. package/lib/cloudflare/miniflare/miniflare.js +0 -142
  72. package/lib/cloudflare/miniflare/miniflare.js.map +0 -1
  73. package/src/cloudflare/miniflare/miniflare-worker-options.ts +0 -599
  74. package/src/cloudflare/miniflare/miniflare.ts +0 -183
@@ -1,599 +0,0 @@
1
- import {
2
- kCurrentWorker,
3
- type RemoteProxyConnectionString,
4
- type WorkerOptions,
5
- } from "miniflare";
6
- import assert from "node:assert";
7
- import { assertNever } from "../../util/assert-never.ts";
8
- import { logger } from "../../util/logger.ts";
9
- import { Self, type Binding, type WorkerBindingSpec } from "../bindings.ts";
10
- import type { WorkerBundle } from "../worker-bundle.ts";
11
- import type { WorkerProps } from "../worker.ts";
12
-
13
- export type MiniflareWorkerOptions = Pick<
14
- WorkerProps,
15
- | "bindings"
16
- | "eventSources"
17
- | "compatibilityDate"
18
- | "compatibilityFlags"
19
- | "format"
20
- | "assets"
21
- > & {
22
- name: string;
23
- bundle: WorkerBundle;
24
- port?: number;
25
- };
26
-
27
- type BindingType = Exclude<Binding, string | Self>["type"];
28
-
29
- const REMOTE_ONLY_BINDING_TYPES = [
30
- "ai",
31
- "ai_gateway",
32
- "browser",
33
- "dispatch_namespace",
34
- "vectorize",
35
- ] satisfies BindingType[];
36
- const REMOTE_OPTIONAL_BINDING_TYPES = [
37
- "d1",
38
- // "durable_object_namespace", This is supported in Miniflare but needs some wrangling to make it work with a remote proxy.
39
- "images",
40
- "kv_namespace",
41
- "r2_bucket",
42
- "queue",
43
- "service",
44
- // "workflow", same thing
45
- ] satisfies BindingType[];
46
-
47
- type RemoteBindingType =
48
- | (typeof REMOTE_ONLY_BINDING_TYPES)[number]
49
- | (typeof REMOTE_OPTIONAL_BINDING_TYPES)[number];
50
-
51
- type RemoteBinding = Extract<Binding, { type: RemoteBindingType }>;
52
-
53
- export function buildRemoteBindings(
54
- input: Pick<MiniflareWorkerOptions, "bindings">,
55
- ) {
56
- const bindings: WorkerBindingSpec[] = [];
57
- for (const [name, binding] of Object.entries(input.bindings ?? {})) {
58
- if (isRemoteOnlyBinding(binding)) {
59
- bindings.push(buildRemoteBinding(name, binding));
60
- } else if (isRemoteOptionalBinding(binding) && isRemoteEnabled(binding)) {
61
- bindings.push(buildRemoteBinding(name, binding));
62
- }
63
- }
64
- return bindings;
65
- }
66
-
67
- function isRemoteOptionalBinding(binding: Binding): binding is RemoteBinding {
68
- return (
69
- typeof binding !== "string" &&
70
- binding !== Self &&
71
- typeof binding === "object" &&
72
- "type" in binding &&
73
- REMOTE_OPTIONAL_BINDING_TYPES.includes(binding.type as any)
74
- );
75
- }
76
-
77
- function isRemoteOnlyBinding(binding: Binding): binding is RemoteBinding {
78
- return (
79
- typeof binding !== "string" &&
80
- binding !== Self &&
81
- typeof binding === "object" &&
82
- "type" in binding &&
83
- REMOTE_ONLY_BINDING_TYPES.includes(binding.type as any)
84
- );
85
- }
86
-
87
- function isRemoteEnabled(binding: RemoteBinding): boolean {
88
- return (
89
- "dev" in binding &&
90
- typeof binding.dev === "object" &&
91
- "remote" in binding.dev &&
92
- !!binding.dev.remote
93
- );
94
- }
95
-
96
- function buildRemoteBinding(
97
- name: string,
98
- binding: RemoteBinding,
99
- ): WorkerBindingSpec & { raw?: true } {
100
- switch (binding.type) {
101
- case "ai": {
102
- return {
103
- type: "ai",
104
- name,
105
- raw: true,
106
- };
107
- }
108
- case "ai_gateway": {
109
- return {
110
- type: "ai",
111
- name,
112
- raw: true,
113
- };
114
- }
115
- case "browser": {
116
- return {
117
- type: "browser",
118
- name,
119
- raw: true,
120
- };
121
- }
122
- case "d1": {
123
- return {
124
- type: "d1",
125
- name,
126
- id: binding.id,
127
- raw: true,
128
- };
129
- }
130
- case "dispatch_namespace": {
131
- return {
132
- type: "dispatch_namespace",
133
- name,
134
- namespace: binding.namespace,
135
- raw: true,
136
- };
137
- }
138
- // case "durable_object_namespace": {
139
- // return {
140
- // type: "durable_object_namespace",
141
- // name,
142
- // class_name: binding.className,
143
- // script_name: binding.scriptName,
144
- // raw: true,
145
- // };
146
- // }
147
- case "images": {
148
- return {
149
- type: "images",
150
- name,
151
- raw: true,
152
- };
153
- }
154
- case "kv_namespace": {
155
- return {
156
- type: "kv_namespace",
157
- name,
158
- namespace_id:
159
- "namespaceId" in binding ? binding.namespaceId : binding.id,
160
- raw: true,
161
- };
162
- }
163
- case "queue": {
164
- return {
165
- type: "queue",
166
- name,
167
- queue_name: binding.name,
168
- raw: true,
169
- };
170
- }
171
- case "r2_bucket": {
172
- return {
173
- type: "r2_bucket",
174
- name,
175
- bucket_name: binding.name,
176
- raw: true,
177
- };
178
- }
179
- case "service": {
180
- return {
181
- type: "service",
182
- name,
183
- service: "service" in binding ? binding.service : binding.name,
184
- environment: "environment" in binding ? binding.environment : undefined,
185
- };
186
- }
187
- case "vectorize": {
188
- return {
189
- type: "vectorize",
190
- name,
191
- index_name: binding.name,
192
- raw: true,
193
- };
194
- }
195
- // case "workflow": {
196
- // return {
197
- // type: "workflow",
198
- // name,
199
- // workflow_name: binding.workflowName,
200
- // class_name: binding.className,
201
- // script_name: binding.scriptName,
202
- // raw: true,
203
- // };
204
- // }
205
- default: {
206
- assertNever(binding);
207
- }
208
- }
209
- }
210
-
211
- const moduleTypes = {
212
- esm: "ESModule",
213
- cjs: "CommonJS",
214
- text: "Text",
215
- data: "Data",
216
- wasm: "CompiledWasm",
217
- sourcemap: "Text",
218
- } as const;
219
-
220
- function parseModules(bundle: WorkerBundle) {
221
- const modules = bundle.modules.map((module) => ({
222
- type: moduleTypes[module.type],
223
- path: module.path,
224
- contents: module.content,
225
- }));
226
- const entry = modules.find((module) => module.path === bundle.entrypoint);
227
- if (!entry) {
228
- throw new Error(`Entrypoint "${bundle.entrypoint}" not found in bundle.`);
229
- }
230
- return [entry, ...modules.filter((module) => module.path !== entry.path)];
231
- }
232
-
233
- export function buildMiniflareWorkerOptions({
234
- name: workerName,
235
- assets,
236
- bundle,
237
- bindings,
238
- eventSources,
239
- compatibilityDate,
240
- compatibilityFlags,
241
- remoteProxyConnectionString,
242
- }: MiniflareWorkerOptions & {
243
- remoteProxyConnectionString: RemoteProxyConnectionString | undefined;
244
- }): WorkerOptions {
245
- const options: WorkerOptions = {
246
- name: workerName,
247
- modules: parseModules(bundle),
248
- rootPath: bundle.root,
249
- compatibilityDate,
250
- compatibilityFlags,
251
- // TODO: Setting `proxy: true` here causes the following error when connecting via a websocket:
252
- // workerd/io/worker.c++:2164: info: uncaught exception; source = Uncaught (in promise); stack = TypeError: Invalid URL string.
253
- unsafeDirectSockets: [{ proxy: false }],
254
- containerEngine: {
255
- localDocker: {
256
- socketPath:
257
- process.platform === "win32"
258
- ? "//./pipe/docker_engine"
259
- : "unix:///var/run/docker.sock",
260
- },
261
- },
262
- };
263
- for (const [name, binding] of Object.entries(bindings ?? {})) {
264
- if (typeof binding === "string") {
265
- options.bindings = {
266
- ...options.bindings,
267
- [name]: binding,
268
- };
269
- continue;
270
- }
271
- if (binding === Self) {
272
- options.serviceBindings = {
273
- ...((options.serviceBindings as Record<string, string> | undefined) ??
274
- {}),
275
- [name]: kCurrentWorker,
276
- };
277
- continue;
278
- }
279
- switch (binding.type) {
280
- case "ai": {
281
- assert(
282
- remoteProxyConnectionString,
283
- `Binding "${name}" of type "${binding.type}" requires a remoteProxyConnectionString, but none was provided.`,
284
- );
285
- options.ai = {
286
- binding: name,
287
- remoteProxyConnectionString,
288
- };
289
- break;
290
- }
291
- case "ai_gateway": {
292
- assert(
293
- remoteProxyConnectionString,
294
- `Binding "${name}" of type "${binding.type}" requires a remoteProxyConnectionString, but none was provided.`,
295
- );
296
- options.ai = {
297
- binding: name,
298
- remoteProxyConnectionString,
299
- };
300
- break;
301
- }
302
- case "analytics_engine": {
303
- (options.analyticsEngineDatasets ??= {})[name] = {
304
- dataset: binding.dataset,
305
- };
306
- break;
307
- }
308
- case "assets": {
309
- options.assets = {
310
- binding: name,
311
- directory: binding.path,
312
- routerConfig: {
313
- invoke_user_worker_ahead_of_assets:
314
- assets?.run_worker_first === true,
315
- },
316
- assetConfig: {
317
- html_handling: assets?.html_handling,
318
- not_found_handling: assets?.not_found_handling,
319
- },
320
- };
321
- break;
322
- }
323
- case "browser": {
324
- assert(
325
- remoteProxyConnectionString,
326
- `Binding "${name}" of type "${binding.type}" requires a remoteProxyConnectionString, but none was provided.`,
327
- );
328
- options.browserRendering = {
329
- binding: name,
330
- remoteProxyConnectionString,
331
- };
332
- break;
333
- }
334
- case "d1": {
335
- (
336
- (options.d1Databases ??= {}) as Record<
337
- string,
338
- {
339
- id: string;
340
- remoteProxyConnectionString?: RemoteProxyConnectionString;
341
- }
342
- >
343
- )[name] = {
344
- id: binding.id,
345
- remoteProxyConnectionString: binding.dev?.remote
346
- ? remoteProxyConnectionString
347
- : undefined,
348
- };
349
- break;
350
- }
351
- case "dispatch_namespace": {
352
- assert(
353
- remoteProxyConnectionString,
354
- `Binding "${name}" of type "${binding.type}" requires a remoteProxyConnectionString, but none was provided.`,
355
- );
356
- (options.dispatchNamespaces ??= {})[name] = {
357
- namespace: binding.namespace,
358
- remoteProxyConnectionString,
359
- };
360
- break;
361
- }
362
- case "durable_object_namespace": {
363
- (options.durableObjects ??= {})[name] = {
364
- className: binding.className,
365
- scriptName: binding.scriptName,
366
- useSQLite: binding.sqlite,
367
- // namespaceId: binding.namespaceId,
368
- // unsafeUniqueKey?: string | typeof kUnsafeEphemeralUniqueKey | undefined;
369
- // unsafePreventEviction?: boolean | undefined;
370
- // remoteProxyConnectionString: binding.local
371
- // ? undefined
372
- // : remoteProxyConnectionString,
373
- };
374
- if (!binding.scriptName || binding.scriptName === workerName) {
375
- options.unsafeDirectSockets!.push({
376
- entrypoint: binding.className,
377
- proxy: true,
378
- });
379
- }
380
- break;
381
- }
382
- case "hyperdrive": {
383
- if ("access_client_id" in binding.origin) {
384
- throw new Error(
385
- `Hyperdrive with Cloudflare Access is not supported for locally emulated workers. Worker "${name}" is locally emulated but is bound to Hyperdrive "${name}", which has Cloudflare Access enabled.`,
386
- );
387
- }
388
- logger.warnOnce(
389
- `Hyperdrive bindings in locally emulated workers are experimental and may not work as expected. Worker "${name}" is locally emulated and bound to Hyperdrive "${name}".`,
390
- );
391
- const {
392
- scheme = "postgres",
393
- port = 5432,
394
- password,
395
- database,
396
- host,
397
- user,
398
- } = binding.origin;
399
- const connectionString = new URL(
400
- `${scheme}://${user}:${password.unencrypted}@${host}:${port}/${database}?sslmode=${binding.mtls?.sslmode ?? "verify-full"}`,
401
- );
402
- (options.bindings ??= {})[name] = {
403
- connectionString: connectionString.toString(),
404
- database,
405
- host,
406
- password: password.unencrypted,
407
- port,
408
- scheme,
409
- user,
410
- };
411
- break;
412
- }
413
- case "images": {
414
- options.images = {
415
- binding: name,
416
- remoteProxyConnectionString: binding.dev?.remote
417
- ? remoteProxyConnectionString
418
- : undefined,
419
- };
420
- break;
421
- }
422
- case "json": {
423
- (options.bindings ??= {})[name] = binding.json;
424
- break;
425
- }
426
- case "kv_namespace": {
427
- const normalized =
428
- "id" in binding
429
- ? { id: binding.id }
430
- : { id: binding.namespaceId, dev: binding.dev };
431
- (
432
- (options.kvNamespaces ??= {}) as Record<
433
- string,
434
- {
435
- id: string;
436
- remoteProxyConnectionString?: RemoteProxyConnectionString;
437
- }
438
- >
439
- )[name] = {
440
- id: normalized.id,
441
- remoteProxyConnectionString: normalized.dev?.remote
442
- ? remoteProxyConnectionString
443
- : undefined,
444
- };
445
- break;
446
- }
447
- case "pipeline": {
448
- ((options.pipelines ??= {}) as Record<string, string>)[name] =
449
- binding.id;
450
- break;
451
- }
452
- case "queue": {
453
- (
454
- (options.queueProducers ??= {}) as Record<
455
- string,
456
- {
457
- queueName: string;
458
- deliveryDelay?: number;
459
- remoteProxyConnectionString?: RemoteProxyConnectionString;
460
- }
461
- >
462
- )[name] = {
463
- queueName: binding.name,
464
- deliveryDelay: binding.settings?.deliveryDelay,
465
- remoteProxyConnectionString: binding.dev?.remote
466
- ? remoteProxyConnectionString
467
- : undefined,
468
- };
469
- break;
470
- }
471
- case "r2_bucket": {
472
- (
473
- (options.r2Buckets ??= {}) as Record<
474
- string,
475
- {
476
- id: string;
477
- remoteProxyConnectionString?: RemoteProxyConnectionString;
478
- }
479
- >
480
- )[name] = {
481
- id: binding.name,
482
- remoteProxyConnectionString: binding.dev?.remote
483
- ? remoteProxyConnectionString
484
- : undefined,
485
- };
486
- break;
487
- }
488
- case "secret": {
489
- (options.bindings ??= {})[name] = binding.unencrypted;
490
- break;
491
- }
492
- case "secrets_store_secret": {
493
- options.secretsStoreSecrets = {
494
- ...((options.secretsStoreSecrets as
495
- | Record<string, { store_id: string; secret_name: string }>
496
- | undefined) ?? {}),
497
- [name]: {
498
- store_id: binding.storeId,
499
- secret_name: binding.name,
500
- },
501
- };
502
- break;
503
- }
504
- case "secret_key": {
505
- throw new Error(
506
- `Secret key bindings are not supported for locally emulated workers. Worker "${name}" is locally emulated but is bound to secret key "${name}".`,
507
- );
508
- }
509
- case "service": {
510
- if (!("id" in binding)) {
511
- throw new Error(
512
- `Service bindings must have an id. Worker "${name}" is bound to service "${name}" but does not have an id.`,
513
- );
514
- }
515
- if (isRemoteEnabled(binding)) {
516
- (options.serviceBindings ??= {})[name] = {
517
- name: binding.name,
518
- remoteProxyConnectionString,
519
- };
520
- } else {
521
- (options.serviceBindings ??= {})[name] = binding.name;
522
- }
523
- break;
524
- }
525
- case "vectorize": {
526
- assert(
527
- remoteProxyConnectionString,
528
- `Binding "${name}" of type "${binding.type}" requires a remoteProxyConnectionString, but none was provided.`,
529
- );
530
- (options.vectorize ??= {})[name] = {
531
- index_name: binding.name,
532
- remoteProxyConnectionString,
533
- };
534
- break;
535
- }
536
- case "version_metadata": {
537
- // This is how Wrangler does it:
538
- // https://github.com/cloudflare/workers-sdk/blob/70ba9fbf905a9ba5fe158d0bc8d48f6bf31712a2/packages/wrangler/src/dev/miniflare.ts#L881
539
- (options.bindings ??= {})[name] = {
540
- id: crypto.randomUUID(),
541
- tag: "",
542
- timestamp: "0",
543
- };
544
- break;
545
- }
546
- case "workflow": {
547
- (options.workflows ??= {})[name] = {
548
- name: binding.workflowName,
549
- className: binding.className,
550
- scriptName: binding.scriptName,
551
- // remoteProxyConnectionString:
552
- // "local" in binding && binding.local
553
- // ? undefined
554
- // : remoteProxyConnectionString,
555
- };
556
- break;
557
- }
558
- case "container": {
559
- if (binding.dev?.remote) {
560
- throw new Error(
561
- `Container bindings with remote: true are not supported for locally emulated workers. Worker "${name}" is locally emulated but is bound to container "${name}" with remote: true.`,
562
- );
563
- }
564
- (options.durableObjects ??= {})[name] = {
565
- className: binding.className,
566
- scriptName: binding.scriptName,
567
- useSQLite: binding.sqlite,
568
- container: {
569
- imageName: binding.image.imageRef,
570
- },
571
- };
572
- if (!binding.scriptName || binding.scriptName === workerName) {
573
- options.unsafeDirectSockets!.push({
574
- entrypoint: binding.className,
575
- proxy: true,
576
- });
577
- }
578
- break;
579
- }
580
- case "ratelimit": {
581
- (options.ratelimits ??= {})[name] = binding;
582
- break;
583
- }
584
- default: {
585
- assertNever(binding);
586
- }
587
- }
588
- }
589
- for (const eventSource of eventSources ?? []) {
590
- const queue = "queue" in eventSource ? eventSource.queue : eventSource;
591
- if (queue.dev?.remote !== false) {
592
- throw new Error(
593
- `Locally emulated workers cannot consume remote queues. Worker "${workerName}" is locally emulated but is consuming remote queue "${queue.name}".`,
594
- );
595
- }
596
- ((options.queueConsumers ??= []) as string[]).push(queue.name);
597
- }
598
- return options;
599
- }