alchemy 0.93.11 → 0.94.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 (83) hide show
  1. package/README.md +1 -1
  2. package/bin/alchemy.js +103 -103
  3. package/lib/aws/control/client.d.ts.map +1 -1
  4. package/lib/aws/control/client.js +2 -1
  5. package/lib/aws/control/client.js.map +1 -1
  6. package/lib/aws/control/resource.d.ts.map +1 -1
  7. package/lib/aws/control/resource.js +46 -5
  8. package/lib/aws/control/resource.js.map +1 -1
  9. package/lib/aws/ec2/utils.d.ts.map +1 -1
  10. package/lib/aws/ec2/utils.js +5 -3
  11. package/lib/aws/ec2/utils.js.map +1 -1
  12. package/lib/aws/utils.d.ts +8 -1
  13. package/lib/aws/utils.d.ts.map +1 -1
  14. package/lib/aws/utils.js +14 -1
  15. package/lib/aws/utils.js.map +1 -1
  16. package/lib/cloudflare/artifacts.d.ts +54 -0
  17. package/lib/cloudflare/artifacts.d.ts.map +1 -0
  18. package/lib/cloudflare/artifacts.js +14 -0
  19. package/lib/cloudflare/artifacts.js.map +1 -0
  20. package/lib/cloudflare/bindings.d.ts +16 -4
  21. package/lib/cloudflare/bindings.d.ts.map +1 -1
  22. package/lib/cloudflare/bindings.js.map +1 -1
  23. package/lib/cloudflare/bound.d.ts +2 -1
  24. package/lib/cloudflare/bound.d.ts.map +1 -1
  25. package/lib/cloudflare/bucket-custom-domain.d.ts.map +1 -1
  26. package/lib/cloudflare/bucket-custom-domain.js +22 -4
  27. package/lib/cloudflare/bucket-custom-domain.js.map +1 -1
  28. package/lib/cloudflare/d1-database.d.ts.map +1 -1
  29. package/lib/cloudflare/d1-database.js +8 -2
  30. package/lib/cloudflare/d1-database.js.map +1 -1
  31. package/lib/cloudflare/hyperdrive.d.ts +8 -0
  32. package/lib/cloudflare/hyperdrive.d.ts.map +1 -1
  33. package/lib/cloudflare/hyperdrive.js +3 -1
  34. package/lib/cloudflare/hyperdrive.js.map +1 -1
  35. package/lib/cloudflare/index.d.ts +1 -0
  36. package/lib/cloudflare/index.d.ts.map +1 -1
  37. package/lib/cloudflare/index.js +1 -0
  38. package/lib/cloudflare/index.js.map +1 -1
  39. package/lib/cloudflare/miniflare/build-worker-options.d.ts.map +1 -1
  40. package/lib/cloudflare/miniflare/build-worker-options.js +3 -0
  41. package/lib/cloudflare/miniflare/build-worker-options.js.map +1 -1
  42. package/lib/cloudflare/miniflare/miniflare-worker-proxy.d.ts.map +1 -1
  43. package/lib/cloudflare/miniflare/miniflare-worker-proxy.js +3 -1
  44. package/lib/cloudflare/miniflare/miniflare-worker-proxy.js.map +1 -1
  45. package/lib/cloudflare/miniflare/remote-binding-proxy.d.ts.map +1 -1
  46. package/lib/cloudflare/miniflare/remote-binding-proxy.js +16 -30
  47. package/lib/cloudflare/miniflare/remote-binding-proxy.js.map +1 -1
  48. package/lib/cloudflare/worker-metadata.d.ts +11 -0
  49. package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
  50. package/lib/cloudflare/worker-metadata.js +20 -1
  51. package/lib/cloudflare/worker-metadata.js.map +1 -1
  52. package/lib/cloudflare/worker.d.ts +37 -0
  53. package/lib/cloudflare/worker.d.ts.map +1 -1
  54. package/lib/cloudflare/worker.js +1 -1
  55. package/lib/cloudflare/worker.js.map +1 -1
  56. package/lib/cloudflare/wrangler.json.js +11 -0
  57. package/lib/cloudflare/wrangler.json.js.map +1 -1
  58. package/lib/cloudflare/zone.js +1 -1
  59. package/lib/cloudflare/zone.js.map +1 -1
  60. package/lib/scope.js +2 -2
  61. package/lib/scope.js.map +1 -1
  62. package/package.json +4 -4
  63. package/src/aws/control/client.ts +2 -1
  64. package/src/aws/control/resource.ts +67 -8
  65. package/src/aws/ec2/utils.ts +5 -3
  66. package/src/aws/utils.ts +20 -2
  67. package/src/cloudflare/artifacts.ts +69 -0
  68. package/src/cloudflare/bindings.ts +17 -2
  69. package/src/cloudflare/bound.ts +68 -65
  70. package/src/cloudflare/bucket-custom-domain.ts +33 -7
  71. package/src/cloudflare/d1-database.ts +12 -2
  72. package/src/cloudflare/hyperdrive.ts +13 -1
  73. package/src/cloudflare/index.ts +1 -0
  74. package/src/cloudflare/miniflare/build-worker-options.ts +5 -0
  75. package/src/cloudflare/miniflare/miniflare-worker-proxy.ts +3 -1
  76. package/src/cloudflare/miniflare/remote-binding-proxy.ts +20 -43
  77. package/src/cloudflare/worker-metadata.ts +33 -1
  78. package/src/cloudflare/worker.ts +41 -1
  79. package/src/cloudflare/wrangler.json.ts +19 -0
  80. package/src/cloudflare/zone.ts +1 -1
  81. package/src/scope.ts +2 -2
  82. package/workers/cloudflare-state-store.js +57 -57
  83. package/workers/tunnel-proxy.js +1 -1
@@ -21,12 +21,6 @@ type WranglerSessionConfig =
21
21
  minimal_mode: boolean;
22
22
  };
23
23
 
24
- interface WorkersPreviewSession {
25
- inspector_websocket: string;
26
- prewarm: string;
27
- token: string;
28
- }
29
-
30
24
  export interface RemoteBindingProxy {
31
25
  server: HTTPServer;
32
26
  bindings: WorkerBindingSpec[];
@@ -135,7 +129,7 @@ async function createWorkersPreviewToken(
135
129
  session: WranglerSessionConfig;
136
130
  },
137
131
  ) {
138
- const session = await createWorkersPreviewSession(api);
132
+ const previewUploadToken = await createPreviewUploadToken(api);
139
133
  const formData = await WorkerBundle.toFormData(input.bundle);
140
134
  formData.append("metadata", JSON.stringify(input.metadata));
141
135
  formData.append("wrangler-session-config", JSON.stringify(input.session));
@@ -146,59 +140,42 @@ async function createWorkersPreviewToken(
146
140
  formData,
147
141
  {
148
142
  headers: {
149
- "cf-preview-upload-config-token": session.token,
143
+ "cf-preview-upload-config-token": previewUploadToken,
150
144
  },
151
145
  },
152
146
  ),
153
147
  );
154
- // Fire and forget prewarm call
155
- // (see https://github.com/cloudflare/workers-sdk/blob/6c6afbd6072b96e78e67d3a863ed849c6aa49472/packages/wrangler/src/dev/create-worker-preview.ts#L338)
156
- void prewarm(session.prewarm, res.preview_token);
157
148
  return res.preview_token;
158
149
  }
159
150
 
160
- async function prewarm(url: string, previewToken: string) {
161
- try {
162
- // Pass the preview token to the Access probe — prewarm targets a preview
163
- // worker URL, which only surfaces an Access 302 when the request carries
164
- // the preview token.
165
- const accessToken = await getAccessToken(
166
- new URL(url).hostname,
167
- previewToken,
168
- );
169
- await fetch(url, {
170
- method: "POST",
171
- headers: {
172
- "cf-workers-preview-token": previewToken,
173
- ...(accessToken ? { cookie: `CF_Authorization=${accessToken}` } : {}),
174
- },
175
- });
176
- } catch {
177
- // Ignore prewarm errors
178
- }
179
- }
180
-
181
- async function createWorkersPreviewSession(api: CloudflareApi) {
182
- const { exchange_url } = await extractCloudflareResult<{
183
- exchange_url: string;
151
+ async function createPreviewUploadToken(api: CloudflareApi) {
152
+ const { token, exchange_url } = await extractCloudflareResult<{
153
+ exchange_url: string | null | undefined;
184
154
  token: string;
185
155
  }>(
186
156
  "create workers preview session",
187
157
  api.get(`/accounts/${api.accountId}/workers/subdomain/edge-preview`),
188
158
  );
159
+ if (!exchange_url) {
160
+ return token;
161
+ }
189
162
  const accessToken = await getAccessToken(new URL(exchange_url).hostname);
190
- const res = await fetch(exchange_url, {
163
+ const json = await fetch(exchange_url, {
191
164
  headers: {
192
165
  ...(accessToken ? { cookie: `CF_Authorization=${accessToken}` } : {}),
193
166
  },
194
- });
195
- if (!res.ok) {
196
- throw new Error(
197
- `Failed to create workers preview session: ${res.status} ${res.statusText}`,
198
- );
167
+ })
168
+ .then((res) => res.json())
169
+ .catch(() => undefined);
170
+ if (
171
+ typeof json === "object" &&
172
+ json !== null &&
173
+ "token" in json &&
174
+ typeof json.token === "string"
175
+ ) {
176
+ return json.token;
199
177
  }
200
- const json: WorkersPreviewSession = await res.json();
201
- return json;
178
+ return token;
202
179
  }
203
180
 
204
181
  /**
@@ -206,6 +206,17 @@ export interface WorkerMetadata {
206
206
  };
207
207
  };
208
208
  logpush?: boolean;
209
+ /**
210
+ * Workers Cache configuration.
211
+ *
212
+ * The wire shape matches what wrangler (>= 4.69) uploads.
213
+ *
214
+ * @see https://developers.cloudflare.com/workers/cache/configuration/
215
+ */
216
+ cache_options?: {
217
+ enabled: boolean;
218
+ cross_version_cache?: boolean;
219
+ };
209
220
  migrations?: SingleStepMigration;
210
221
  main_module?: string;
211
222
  body_part?: string;
@@ -355,6 +366,19 @@ export async function prepareWorkerMetadata(
355
366
  enabled: observability?.enabled !== false,
356
367
  },
357
368
  logpush: props.logpush ?? false,
369
+ cache_options:
370
+ props.cache != null
371
+ ? {
372
+ enabled:
373
+ typeof props.cache === "boolean"
374
+ ? props.cache
375
+ : (props.cache.enabled ?? true),
376
+ cross_version_cache:
377
+ typeof props.cache === "object"
378
+ ? props.cache.crossVersionCache
379
+ : undefined,
380
+ }
381
+ : undefined,
358
382
  // TODO(sam): base64 encode instead? 0 collision risk vs readability.
359
383
  tags: [
360
384
  // encode a mapping table of Durable Object stable ID -> binding name
@@ -516,6 +540,12 @@ export async function prepareWorkerMetadata(
516
540
  jurisdiction:
517
541
  binding.jurisdiction === "default" ? undefined : binding.jurisdiction,
518
542
  });
543
+ } else if (binding.type === "artifacts") {
544
+ meta.bindings.push({
545
+ type: "artifacts",
546
+ name: bindingName,
547
+ namespace: binding.namespace,
548
+ });
519
549
  } else if (binding.type === "send_email") {
520
550
  meta.bindings.push({
521
551
  type: "send_email",
@@ -597,7 +627,9 @@ export async function prepareWorkerMetadata(
597
627
  meta.bindings.push({
598
628
  type: "json",
599
629
  name: bindingName,
600
- json: JSON.stringify(binding.json),
630
+ // send the raw value (like wrangler does) so the runtime injects the
631
+ // native type; stringifying here would inject a string instead
632
+ json: binding.json,
601
633
  });
602
634
  } else if (binding.type === "analytics_engine") {
603
635
  meta.bindings.push({
@@ -249,6 +249,21 @@ export interface BaseWorkerProps<
249
249
  */
250
250
  assets?: AssetsConfig;
251
251
 
252
+ /**
253
+ * Enable Workers Cache — a Worker-owned edge cache in front of this Worker.
254
+ *
255
+ * When enabled, Cloudflare checks the cache before invoking the Worker and
256
+ * serves matching responses directly from the edge. Caching is controlled
257
+ * with standard response headers (`Cache-Control`, `Cache-Tag`, `Vary`) and
258
+ * entries can be purged at runtime with `ctx.cache.purge()`.
259
+ *
260
+ * Pass `true` as shorthand for `{ enabled: true }`.
261
+ *
262
+ * @see https://developers.cloudflare.com/workers/cache/
263
+ * @default undefined - Workers Cache is disabled
264
+ */
265
+ cache?: boolean | WorkerCacheOptions;
266
+
252
267
  /**
253
268
  * Cron expressions for the trigger.
254
269
  *
@@ -431,6 +446,31 @@ export interface BaseWorkerProps<
431
446
  tailConsumers?: Array<Worker | { service: string }>;
432
447
  }
433
448
 
449
+ /**
450
+ * Configuration for Workers Cache — the edge cache that sits in front of a
451
+ * Worker's `fetch()` invocations.
452
+ *
453
+ * @see https://developers.cloudflare.com/workers/cache/configuration/
454
+ */
455
+ export interface WorkerCacheOptions {
456
+ /**
457
+ * If Workers Cache is enabled for this Worker
458
+ *
459
+ * @default true
460
+ */
461
+ enabled?: boolean;
462
+
463
+ /**
464
+ * Whether cached responses may be reused across Worker versions.
465
+ *
466
+ * By default the cache is scoped to a single Worker version, so every
467
+ * deployment starts cold. Enable this when responses are version-agnostic.
468
+ *
469
+ * @default false
470
+ */
471
+ crossVersionCache?: boolean;
472
+ }
473
+
434
474
  export interface WorkerObservability {
435
475
  /**
436
476
  * If observability is enabled for this Worker
@@ -1601,7 +1641,7 @@ async function provisionResources<B extends Bindings>(
1601
1641
  // In local dev mode, queue.id is "" (no Cloudflare API call).
1602
1642
  // Use queue.dev.id (resource ID, e.g. "email-queue") to avoid
1603
1643
  // all consumers colliding on the same "-consumer" resource ID.
1604
- // See: https://github.com/alchemy-run/alchemy/issues/1363
1644
+ // See: https://github.com/alchemy-run/alchemy-async/issues/1363
1605
1645
  const queueConsumerId = options.local
1606
1646
  ? eventSource.queue.dev?.id || eventSource.queue.id
1607
1647
  : eventSource.queue.id;
@@ -264,6 +264,11 @@ async function processBindings(
264
264
  const kvNamespaces: WranglerJsonConfig["kv_namespaces"] = [];
265
265
  const durableObjects: WranglerJsonConfig["durable_objects"]["bindings"] = [];
266
266
  const r2Buckets: WranglerJsonConfig["r2_buckets"] = [];
267
+ const artifacts: {
268
+ binding: string;
269
+ namespace: string;
270
+ remote?: boolean;
271
+ }[] = [];
267
272
  const services: WranglerJsonConfig["services"] = [];
268
273
  const sendEmailBindings: WranglerJsonConfig["send_email"] = [];
269
274
  const secrets: string[] = [];
@@ -422,6 +427,12 @@ async function processBindings(
422
427
  allowed_sender_addresses: binding.allowedSenderAddresses,
423
428
  ...(binding.dev?.remote ? { remote: true } : {}),
424
429
  });
430
+ } else if (binding.type === "artifacts") {
431
+ artifacts.push({
432
+ binding: bindingName,
433
+ namespace: binding.namespace,
434
+ remote: binding.dev?.remote ?? true,
435
+ });
425
436
  } else if (binding.type === "secret") {
426
437
  // Secret binding
427
438
  secrets.push(bindingName);
@@ -598,6 +609,14 @@ async function processBindings(
598
609
  spec.r2_buckets = r2Buckets;
599
610
  }
600
611
 
612
+ if (artifacts.length > 0) {
613
+ (
614
+ spec as WranglerJsonSpec & {
615
+ artifacts?: typeof artifacts;
616
+ }
617
+ ).artifacts = artifacts;
618
+ }
619
+
601
620
  if (services.length > 0) {
602
621
  spec.services = services;
603
622
  }
@@ -415,7 +415,7 @@ export const Zone = Resource(
415
415
 
416
416
  // Add a small delay to ensure settings are propagated
417
417
  // TODO(michael): do we need this?
418
- // https://github.com/alchemy-run/alchemy/issues/681
418
+ // https://github.com/alchemy-run/alchemy-async/issues/681
419
419
  await new Promise((resolve) => setTimeout(resolve, 2000));
420
420
 
421
421
  // Update Bot Management configuration if provided
package/src/scope.ts CHANGED
@@ -324,7 +324,7 @@ export class Scope {
324
324
  destroyStrategy ?? this.parent?.destroyStrategy ?? "sequential";
325
325
  if (this.local) {
326
326
  this.logger.warnOnce(
327
- "Development mode is in beta. Please report any issues to https://github.com/alchemy-run/alchemy/issues.",
327
+ "Development mode is in beta. Please report any issues to https://github.com/alchemy-run/alchemy-async/issues.",
328
328
  );
329
329
  }
330
330
 
@@ -652,7 +652,7 @@ export class Scope {
652
652
  throw e;
653
653
  })) ?? [];
654
654
 
655
- //todo(michael): remove once we deprecate doss; see: https://github.com/alchemy-run/alchemy/issues/585
655
+ //todo(michael): remove once we deprecate doss; see: https://github.com/alchemy-run/alchemy-async/issues/585
656
656
  let hasCorruptedResources = false;
657
657
  if (pendingDeletions) {
658
658
  for (const { resource, oldProps } of pendingDeletions) {