alchemy 0.83.0 → 0.83.2

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 (142) hide show
  1. package/bin/alchemy.js +19 -11
  2. package/bin/commands/deploy.ts +2 -0
  3. package/bin/commands/destroy.ts +1 -1
  4. package/bin/services/execute-alchemy.ts +11 -0
  5. package/bin/services/package-manager.ts +5 -1
  6. package/lib/alchemy.d.ts +8 -0
  7. package/lib/alchemy.d.ts.map +1 -1
  8. package/lib/alchemy.js +4 -0
  9. package/lib/alchemy.js.map +1 -1
  10. package/lib/aws/ec2/internet-gateway-attachment.d.ts.map +1 -1
  11. package/lib/aws/ec2/internet-gateway-attachment.js.map +1 -1
  12. package/lib/aws/function.js.map +1 -1
  13. package/lib/aws/oidc/github-oidc-provider.d.ts.map +1 -1
  14. package/lib/aws/oidc/github-oidc-provider.js.map +1 -1
  15. package/lib/cloudflare/api-error.d.ts +9 -0
  16. package/lib/cloudflare/api-error.d.ts.map +1 -1
  17. package/lib/cloudflare/api-error.js +1 -24
  18. package/lib/cloudflare/api-error.js.map +1 -1
  19. package/lib/cloudflare/api-response.d.ts.map +1 -1
  20. package/lib/cloudflare/api-response.js.map +1 -1
  21. package/lib/cloudflare/api-schema.d.ts.map +1 -1
  22. package/lib/cloudflare/api-schema.js.map +1 -1
  23. package/lib/cloudflare/api-shield.d.ts.map +1 -1
  24. package/lib/cloudflare/api-shield.js.map +1 -1
  25. package/lib/cloudflare/astro/astro.d.ts.map +1 -1
  26. package/lib/cloudflare/astro/astro.js.map +1 -1
  27. package/lib/cloudflare/astro/plugin.d.ts.map +1 -1
  28. package/lib/cloudflare/astro/plugin.js +15 -1
  29. package/lib/cloudflare/astro/plugin.js.map +1 -1
  30. package/lib/cloudflare/bucket-custom-domain.d.ts.map +1 -1
  31. package/lib/cloudflare/bucket-custom-domain.js.map +1 -1
  32. package/lib/cloudflare/bucket.d.ts +1 -1
  33. package/lib/cloudflare/bucket.d.ts.map +1 -1
  34. package/lib/cloudflare/bucket.js +1 -1
  35. package/lib/cloudflare/bucket.js.map +1 -1
  36. package/lib/cloudflare/bundle/plugin-wasm.d.ts.map +1 -1
  37. package/lib/cloudflare/bundle/plugin-wasm.js +4 -2
  38. package/lib/cloudflare/bundle/plugin-wasm.js.map +1 -1
  39. package/lib/cloudflare/container.d.ts.map +1 -1
  40. package/lib/cloudflare/container.js +2 -2
  41. package/lib/cloudflare/container.js.map +1 -1
  42. package/lib/cloudflare/dispatch-namespace.d.ts.map +1 -1
  43. package/lib/cloudflare/dispatch-namespace.js.map +1 -1
  44. package/lib/cloudflare/hyperdrive.d.ts +25 -0
  45. package/lib/cloudflare/hyperdrive.d.ts.map +1 -1
  46. package/lib/cloudflare/hyperdrive.js +26 -3
  47. package/lib/cloudflare/hyperdrive.js.map +1 -1
  48. package/lib/cloudflare/index.d.ts +1 -0
  49. package/lib/cloudflare/index.d.ts.map +1 -1
  50. package/lib/cloudflare/index.js +1 -0
  51. package/lib/cloudflare/index.js.map +1 -1
  52. package/lib/cloudflare/logpush-job.d.ts.map +1 -1
  53. package/lib/cloudflare/logpush-job.js +12 -7
  54. package/lib/cloudflare/logpush-job.js.map +1 -1
  55. package/lib/cloudflare/queue-consumer.d.ts +2 -2
  56. package/lib/cloudflare/ruleset.d.ts.map +1 -1
  57. package/lib/cloudflare/ruleset.js.map +1 -1
  58. package/lib/cloudflare/tunnel-route.d.ts.map +1 -1
  59. package/lib/cloudflare/tunnel-route.js.map +1 -1
  60. package/lib/cloudflare/website.d.ts.map +1 -1
  61. package/lib/cloudflare/website.js.map +1 -1
  62. package/lib/cloudflare/worker-assets.d.ts +1 -1
  63. package/lib/cloudflare/worker-assets.d.ts.map +1 -1
  64. package/lib/cloudflare/worker-assets.js +1 -1
  65. package/lib/cloudflare/worker-assets.js.map +1 -1
  66. package/lib/cloudflare/worker-bundle.d.ts.map +1 -1
  67. package/lib/cloudflare/worker-bundle.js.map +1 -1
  68. package/lib/cloudflare/worker-metadata.d.ts +3 -7
  69. package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
  70. package/lib/cloudflare/worker-metadata.js.map +1 -1
  71. package/lib/cloudflare/worker.d.ts +130 -17
  72. package/lib/cloudflare/worker.d.ts.map +1 -1
  73. package/lib/cloudflare/worker.js.map +1 -1
  74. package/lib/cloudflare/zone-settings.d.ts.map +1 -1
  75. package/lib/coinbase/evm-smart-account.d.ts.map +1 -1
  76. package/lib/coinbase/evm-smart-account.js.map +1 -1
  77. package/lib/coinbase/faucet.js.map +1 -1
  78. package/lib/context.d.ts.map +1 -1
  79. package/lib/context.js.map +1 -1
  80. package/lib/esbuild/bundle.d.ts.map +1 -1
  81. package/lib/esbuild/bundle.js.map +1 -1
  82. package/lib/scope.d.ts +9 -0
  83. package/lib/scope.d.ts.map +1 -1
  84. package/lib/scope.js +5 -3
  85. package/lib/scope.js.map +1 -1
  86. package/lib/sentry/client-key.js +1 -1
  87. package/lib/sentry/client-key.js.map +1 -1
  88. package/lib/serde.d.ts.map +1 -1
  89. package/lib/serde.js +4 -0
  90. package/lib/serde.js.map +1 -1
  91. package/lib/state/d1-state-store.js.map +1 -1
  92. package/lib/state/instrumented-state-store.d.ts.map +1 -1
  93. package/lib/util/api/client/types.gen.d.ts.map +1 -1
  94. package/lib/util/api/core/pathSerializer.gen.d.ts.map +1 -1
  95. package/lib/util/api/core/pathSerializer.gen.js.map +1 -1
  96. package/lib/util/idempotent-spawn.d.ts.map +1 -1
  97. package/lib/util/idempotent-spawn.js +1 -1
  98. package/lib/util/idempotent-spawn.js.map +1 -1
  99. package/package.json +11 -11
  100. package/src/alchemy.ts +13 -0
  101. package/src/aws/ec2/internet-gateway-attachment.ts +1 -2
  102. package/src/aws/function.ts +2 -3
  103. package/src/aws/oidc/github-oidc-provider.ts +4 -2
  104. package/src/cloudflare/api-error.ts +1 -35
  105. package/src/cloudflare/api-response.ts +3 -2
  106. package/src/cloudflare/api-schema.ts +3 -2
  107. package/src/cloudflare/api-shield.ts +3 -2
  108. package/src/cloudflare/astro/astro.ts +4 -2
  109. package/src/cloudflare/astro/plugin.ts +15 -1
  110. package/src/cloudflare/bucket-custom-domain.ts +1 -2
  111. package/src/cloudflare/bucket.ts +9 -2
  112. package/src/cloudflare/bundle/plugin-wasm.ts +4 -5
  113. package/src/cloudflare/container.ts +4 -3
  114. package/src/cloudflare/dispatch-namespace.ts +4 -2
  115. package/src/cloudflare/hyperdrive.ts +38 -3
  116. package/src/cloudflare/index.ts +1 -0
  117. package/src/cloudflare/logpush-job.ts +16 -13
  118. package/src/cloudflare/queue-consumer.ts +2 -2
  119. package/src/cloudflare/ruleset.ts +3 -2
  120. package/src/cloudflare/tunnel-route.ts +4 -2
  121. package/src/cloudflare/website.ts +4 -2
  122. package/src/cloudflare/worker-assets.ts +6 -1
  123. package/src/cloudflare/worker-bundle.ts +2 -1
  124. package/src/cloudflare/worker-metadata.ts +3 -6
  125. package/src/cloudflare/worker.ts +139 -17
  126. package/src/cloudflare/zone-settings.ts +1 -2
  127. package/src/coinbase/evm-smart-account.ts +1 -2
  128. package/src/coinbase/faucet.ts +3 -1
  129. package/src/context.ts +3 -2
  130. package/src/esbuild/bundle.ts +3 -2
  131. package/src/scope.ts +13 -2
  132. package/src/sentry/client-key.ts +5 -1
  133. package/src/serde.ts +4 -0
  134. package/src/state/d1-state-store.ts +2 -3
  135. package/src/state/instrumented-state-store.ts +3 -3
  136. package/src/util/api/client/types.gen.ts +4 -3
  137. package/src/util/api/core/pathSerializer.gen.ts +1 -2
  138. package/src/util/idempotent-spawn.ts +5 -1
  139. package/templates/astro/wrangler.jsonc +1 -1
  140. package/workers/cloudflare-state-store.js +57 -57
  141. package/workers/remote-binding-proxy.js +42 -15
  142. package/workers/tunnel-proxy.js +1 -1
@@ -7,13 +7,27 @@ const isAstroCheck =
7
7
  process.argv.includes("check");
8
8
 
9
9
  const alchemy = (options?: Options): AstroIntegration => {
10
- return cloudflare({
10
+ const integration = cloudflare({
11
11
  platformProxy: getPlatformProxyOptions(
12
12
  options?.platformProxy,
13
13
  !isAstroCheck,
14
14
  ),
15
15
  ...options,
16
16
  });
17
+ const setup = integration.hooks["astro:config:setup"];
18
+ integration.hooks["astro:config:setup"] = async (options) => {
19
+ options.updateConfig({
20
+ vite: {
21
+ server: {
22
+ watch: {
23
+ ignored: ["**/.alchemy/**"],
24
+ },
25
+ },
26
+ },
27
+ });
28
+ await setup?.(options);
29
+ };
30
+ return integration;
17
31
  };
18
32
 
19
33
  export default alchemy;
@@ -13,8 +13,7 @@ import { inferZoneIdFromPattern } from "./route.ts";
13
13
  import type { Zone } from "./zone.ts";
14
14
 
15
15
  export interface R2BucketCustomDomainProps
16
- extends CloudflareApiOptions,
17
- R2BucketCustomDomainOptions {
16
+ extends CloudflareApiOptions, R2BucketCustomDomainOptions {
18
17
  /**
19
18
  * The bucket to attach the domain to.
20
19
  */
@@ -546,7 +546,9 @@ export async function R2Bucket(
546
546
  options?: Pick<R2PutOptions, "httpMetadata">,
547
547
  ): Promise<PutR2ObjectResponse> => {
548
548
  if (isLocal) {
549
- return await (await localBucket()).put(
549
+ return await (
550
+ await localBucket()
551
+ ).put(
550
552
  key,
551
553
  typeof value === "string"
552
554
  ? value
@@ -1331,7 +1333,12 @@ function mapHeadersToHttpMetadata(headers: Headers): R2HTTPMetadata {
1331
1333
 
1332
1334
  export async function putObject(
1333
1335
  api: CloudflareApi,
1334
- { bucketName, key, object, options }: {
1336
+ {
1337
+ bucketName,
1338
+ key,
1339
+ object,
1340
+ options,
1341
+ }: {
1335
1342
  bucketName: string;
1336
1343
  key: string;
1337
1344
  object: PutObjectObject;
@@ -1,5 +1,6 @@
1
1
  import type esbuild from "esbuild";
2
2
  import assert from "node:assert";
3
+ import crypto from "node:crypto";
3
4
  import fs from "node:fs/promises";
4
5
  import path from "pathe";
5
6
  import type { WorkerBundle } from "../worker-bundle.ts";
@@ -35,11 +36,9 @@ export function createWasmPlugin() {
35
36
  build.initialOptions.outdir,
36
37
  );
37
38
 
38
- // Use relative path as module specifier for portability (note: the `?module` suffix is not needed in workerd)
39
- const specifier = path.relative(
40
- build.initialOptions.absWorkingDir,
41
- copyFrom,
42
- );
39
+ // Use path hash as module specifier for portability (note: the `?module` suffix is not needed in workerd)
40
+ const hash = crypto.createHash("sha256").update(copyFrom).digest("hex");
41
+ const specifier = `${hash}.wasm`;
43
42
 
44
43
  // Copy to outdir so it's included in the upload
45
44
  const copyTo = path.join(outdir, specifier);
@@ -16,7 +16,8 @@ import {
16
16
  * for Cloudflare API authentication.
17
17
  */
18
18
  export interface ContainerProps
19
- extends Omit<ImageProps, "registry" | "skipPush">,
19
+ extends
20
+ Omit<ImageProps, "registry" | "skipPush">,
20
21
  Partial<CloudflareApiOptions> {
21
22
  /**
22
23
  * The class name for the container binding.
@@ -890,7 +891,7 @@ export async function createContainerApplication(
890
891
  return result.result;
891
892
  }
892
893
 
893
- const error = result.errors.find((e) => e.code === 1000);
894
+ const error = result.errors.find((e) => e.code === 1608);
894
895
  if (error) {
895
896
  // WEIRD: json-encoded error message - might change when they release an official API
896
897
  const errorMessage = JSON.parse(error.message) as {
@@ -938,7 +939,7 @@ export async function createContainerApplication(
938
939
  }
939
940
 
940
941
  throw Error(
941
- `Failed to create container application: ${result.errors?.map((e: { message: string }) => e.message).join(", ") ?? "Unknown error"}`,
942
+ `Failed to create container application: ${result.errors?.map((e) => `[${e.code}] ${e.message}`).join(", ") ?? "Unknown error"}`,
942
943
  );
943
944
  }
944
945
  type Region =
@@ -45,8 +45,10 @@ export function isDispatchNamespace(
45
45
  /**
46
46
  * Output returned after Dispatch Namespace creation/update
47
47
  */
48
- export interface DispatchNamespace
49
- extends Omit<DispatchNamespaceProps, "delete"> {
48
+ export interface DispatchNamespace extends Omit<
49
+ DispatchNamespaceProps,
50
+ "delete"
51
+ > {
50
52
  type: "dispatch_namespace";
51
53
  /**
52
54
  * The name of the namespace
@@ -210,6 +210,12 @@ export interface HyperdriveProps extends CloudflareApiOptions {
210
210
  * @default origin
211
211
  */
212
212
  origin?: HyperdriveOriginInput;
213
+
214
+ /**
215
+ * Whether to run the hyperdrive remotely instead of locally
216
+ * @default false
217
+ */
218
+ remote?: boolean;
213
219
  };
214
220
  }
215
221
 
@@ -247,6 +253,11 @@ export type Hyperdrive = Omit<HyperdriveProps, "origin" | "dev"> & {
247
253
  * The connection string to use for local development
248
254
  */
249
255
  origin: Secret;
256
+
257
+ /**
258
+ * Whether the hyperdrive is running remotely
259
+ */
260
+ remote: boolean;
250
261
  };
251
262
 
252
263
  /**
@@ -333,6 +344,22 @@ export type Hyperdrive = Omit<HyperdriveProps, "origin" | "dev"> & {
333
344
  * user: "postgres"
334
345
  * }
335
346
  * });
347
+ *
348
+ * @example
349
+ * // Create a Hyperdrive that runs remotely even in local dev mode
350
+ * const remoteDevHyperdrive = await Hyperdrive("remote-dev-db", {
351
+ * name: "remote-dev-db",
352
+ * origin: {
353
+ * database: "postgres",
354
+ * host: "database.example.com",
355
+ * password: alchemy.secret(process.env.DB_PASSWORD),
356
+ * port: 5432,
357
+ * user: "postgres"
358
+ * },
359
+ * dev: {
360
+ * remote: true
361
+ * }
362
+ * });
336
363
  */
337
364
  export async function Hyperdrive(
338
365
  id: string,
@@ -343,6 +370,7 @@ export async function Hyperdrive(
343
370
  origin: toConnectionString(
344
371
  normalizeHyperdriveOrigin(props.dev?.origin ?? origin),
345
372
  ),
373
+ remote: props.dev?.remote ?? false,
346
374
  force: Scope.current.local,
347
375
  };
348
376
  return await _Hyperdrive(id, {
@@ -364,6 +392,7 @@ interface InternalHyperdriveProps extends CloudflareApiOptions {
364
392
  mtls?: HyperdriveMtls;
365
393
  dev: {
366
394
  origin: Secret;
395
+ remote?: boolean;
367
396
  force?: boolean;
368
397
  };
369
398
  adopt?: boolean;
@@ -382,7 +411,7 @@ const _Hyperdrive = Resource(
382
411
  const name =
383
412
  props.name ?? this.output?.name ?? this.scope.createPhysicalName(id);
384
413
 
385
- if (this.scope.local) {
414
+ if (this.scope.local && !props.dev?.remote) {
386
415
  return {
387
416
  id,
388
417
  hyperdriveId: hyperdriveId || "",
@@ -390,7 +419,10 @@ const _Hyperdrive = Resource(
390
419
  origin: props.origin,
391
420
  caching: props.caching,
392
421
  mtls: props.mtls,
393
- dev: props.dev,
422
+ dev: {
423
+ origin: props.dev.origin,
424
+ remote: false,
425
+ },
394
426
  type: "hyperdrive",
395
427
  };
396
428
  }
@@ -485,7 +517,10 @@ const _Hyperdrive = Resource(
485
517
  origin: props.origin,
486
518
  caching: result.caching,
487
519
  mtls: result.mtls,
488
- dev: props.dev,
520
+ dev: {
521
+ origin: props.dev.origin,
522
+ remote: props.dev.remote ?? false,
523
+ },
489
524
  type: "hyperdrive",
490
525
  };
491
526
  },
@@ -41,6 +41,7 @@ export * from "./hyperdrive.ts";
41
41
  export * from "./images.ts";
42
42
  export * from "./kv-namespace.ts";
43
43
  export * from "./logpush-job.ts";
44
+ export * from "./miniflare/miniflare-controller.ts";
44
45
  export * from "./next.ts";
45
46
  export * from "./nuxt/nuxt.ts";
46
47
  export * from "./orange.ts";
@@ -349,7 +349,7 @@ const _LogPushJob = Resource(
349
349
  "cloudflare::LogPushJob",
350
350
  async function (
351
351
  this: Context<LogPushJob>,
352
- _id: string,
352
+ id: string,
353
353
  props: Omit<LogPushJobProps, "destination"> & {
354
354
  destination: Secret<string> | R2Bucket;
355
355
  },
@@ -424,15 +424,22 @@ const _LogPushJob = Resource(
424
424
  accessKeyId = apiToken.accessKeyId.unencrypted;
425
425
  secretAccessKey = apiToken.secretAccessKey.unencrypted;
426
426
  } catch (err) {
427
- console.error(err);
428
- console.warn(
429
- "[Cloudflare Logpush Job] Warning: Cloudflare currently only allows the use of a Global API Key or an API Token with permission to create other API Tokens in order to generate new API Tokens programmatically.\n" +
430
- "Please use your Global API Key, or create an API Token with the necessary permissions. For more information, see: https://alchemy.run/guides/cloudflare/#api-token",
431
- );
432
- throw new Error(
433
- "Unable to create an API Token: Cloudflare only allows Global API Key or an API Token with permission to create other API Tokens. " +
434
- "See https://alchemy.run/guides/cloudflare/#api-token for more details.",
427
+ const message = [
428
+ `Cannot create LogPushJob "${id}" because credentials for destination R2 bucket "${bucket.name}" could not be created. This may be because of an authentication error.`,
429
+ ];
430
+ if (api.credentials.type === "api-token") {
431
+ message.push(
432
+ "Please ensure that your API token has the correct permissions, or use a global API key and email instead.",
433
+ );
434
+ } else if (api.credentials.type === "oauth") {
435
+ message.push(
436
+ "Please use a global API key and email, or create an API token with the necessary permissions.",
437
+ );
438
+ }
439
+ message.push(
440
+ "See https://alchemy.run/guides/cloudflare/ for more details.",
435
441
  );
442
+ throw new Error(message.join("\n"), { cause: err });
436
443
  }
437
444
  }
438
445
 
@@ -523,10 +530,6 @@ const _LogPushJob = Resource(
523
530
  },
524
531
  // if we just recently created an api token for R2, we can retry on 1002 which are caused by token propagation
525
532
  (error: CloudflareApiError) => {
526
- console.warn(
527
- "Received error when creating LogPush job, there may be a propagation delay",
528
- error.message,
529
- );
530
533
  return error.errorData?.[0]?.code === 1002;
531
534
  },
532
535
  30,
@@ -20,7 +20,7 @@ export interface QueueConsumerSettings {
20
20
 
21
21
  /**
22
22
  * Maximum number of concurrent consumer worker invocations
23
- * @default 2
23
+ * @default undefined
24
24
  */
25
25
  maxConcurrency?: number;
26
26
 
@@ -32,7 +32,7 @@ export interface QueueConsumerSettings {
32
32
 
33
33
  /**
34
34
  * Maximum time in milliseconds to wait for batch to fill
35
- * @default 500
35
+ * @default 5_000
36
36
  */
37
37
  maxWaitTimeMs?: number;
38
38
 
@@ -12,8 +12,9 @@ import { findZoneForHostname, type Zone } from "./zone.ts";
12
12
  /**
13
13
  * Properties for creating or updating a Ruleset
14
14
  */
15
- export interface RulesetProps<Phase extends RulePhase>
16
- extends Partial<CloudflareApiOptions> {
15
+ export interface RulesetProps<
16
+ Phase extends RulePhase,
17
+ > extends Partial<CloudflareApiOptions> {
17
18
  /**
18
19
  * The zone to apply the ruleset to
19
20
  */
@@ -88,8 +88,10 @@ export function isTunnelRoute(resource: any): resource is TunnelRoute {
88
88
  /**
89
89
  * Output returned after TunnelRoute creation/update
90
90
  */
91
- export interface TunnelRoute
92
- extends Omit<TunnelRouteProps, "delete" | "tunnel" | "routeId"> {
91
+ export interface TunnelRoute extends Omit<
92
+ TunnelRouteProps,
93
+ "delete" | "tunnel" | "routeId"
94
+ > {
93
95
  /**
94
96
  * The ID of the route
95
97
  */
@@ -18,8 +18,10 @@ import { computeWorkerDevDomain } from "./worker-subdomain.ts";
18
18
  import { type AssetsConfig, Worker, type WorkerProps } from "./worker.ts";
19
19
  import { WranglerJson, type WranglerJsonSpec } from "./wrangler.json.ts";
20
20
 
21
- export interface WebsiteProps<B extends Bindings>
22
- extends Omit<WorkerProps<B>, "assets" | "dev"> {
21
+ export interface WebsiteProps<B extends Bindings> extends Omit<
22
+ WorkerProps<B>,
23
+ "assets" | "dev"
24
+ > {
23
25
  /**
24
26
  * Configuration for the build command
25
27
  *
@@ -31,7 +31,12 @@ interface FileMetadata {
31
31
  */
32
32
  export async function uploadAssets(
33
33
  api: CloudflareApi,
34
- { workerName, assets, assetConfig, namespace }: {
34
+ {
35
+ workerName,
36
+ assets,
37
+ assetConfig,
38
+ namespace,
39
+ }: {
35
40
  workerName: string;
36
41
  assets: Assets;
37
42
  assetConfig?: WorkerProps["assets"];
@@ -242,7 +242,8 @@ export namespace WorkerBundleSource {
242
242
  }
243
243
 
244
244
  export interface ESBuildProps
245
- extends WorkerBundleSource.BaseProps,
245
+ extends
246
+ WorkerBundleSource.BaseProps,
246
247
  Omit<
247
248
  esbuild.BuildOptions,
248
249
  "entryPoints" | "format" | "absWorkingDir" | "outdir"
@@ -24,6 +24,7 @@ import {
24
24
  isWorker,
25
25
  Worker,
26
26
  type AssetsConfig,
27
+ type WorkerPlacement,
27
28
  type WorkerProps,
28
29
  } from "./worker.ts";
29
30
 
@@ -217,9 +218,7 @@ export interface WorkerMetadata {
217
218
  suspended: boolean;
218
219
  }[];
219
220
  containers?: { class_name: string }[];
220
- placement?: {
221
- mode: "smart";
222
- };
221
+ placement?: WorkerPlacement;
223
222
  limits?: {
224
223
  cpu_ms?: number;
225
224
  };
@@ -240,9 +239,7 @@ export async function prepareWorkerMetadata(
240
239
  };
241
240
  tags?: string[];
242
241
  unstable_cacheWorkerSettings?: boolean;
243
- placement?: {
244
- mode: "smart";
245
- };
242
+ placement?: WorkerPlacement;
246
243
  },
247
244
  ): Promise<WorkerMetadata> {
248
245
  const oldSettings = await (
@@ -365,22 +365,16 @@ export interface BaseWorkerProps<
365
365
  };
366
366
 
367
367
  /**
368
- * Smart placement configuration for the worker.
368
+ * Placement configuration for the worker.
369
369
  *
370
- * Controls how Cloudflare places the worker across its network for optimal performance.
370
+ * Controls where your Worker runs to reduce latency to back-end infrastructure.
371
+ * Only one placement option can be specified (mutually exclusive).
371
372
  *
372
- * When omitted, smart placement is disabled (default behavior).
373
+ * When omitted, the Worker runs in the data center closest to the incoming request.
374
+ *
375
+ * @see https://developers.cloudflare.com/workers/configuration/smart-placement/
373
376
  */
374
- placement?: {
375
- /**
376
- * The placement mode for the worker.
377
- *
378
- * - "smart": Automatically optimize placement based on performance metrics
379
- *
380
- * @default undefined (smart placement disabled)
381
- */
382
- mode: "smart";
383
- };
377
+ placement?: WorkerPlacement;
384
378
 
385
379
  limits?: {
386
380
  /**
@@ -477,6 +471,136 @@ export interface WorkerObservability {
477
471
  };
478
472
  }
479
473
 
474
+ /**
475
+ * Worker placement configuration for controlling where your Worker runs.
476
+ *
477
+ * Only one placement option can be specified at a time (mutually exclusive).
478
+ *
479
+ * @see https://developers.cloudflare.com/workers/configuration/smart-placement/
480
+ */
481
+ export type WorkerPlacement =
482
+ | WorkerPlacementSmart
483
+ | WorkerPlacementRegion
484
+ | WorkerPlacementHost
485
+ | WorkerPlacementHostname;
486
+
487
+ /**
488
+ * Smart placement mode - Cloudflare automatically places your Worker
489
+ * closest to the upstream with the most requests.
490
+ *
491
+ * Use when your Worker connects to multiple back-end services or you
492
+ * don't know the exact location of your infrastructure.
493
+ *
494
+ * @example
495
+ * ```ts
496
+ * const worker = await Worker("api", {
497
+ * entrypoint: "./src/worker.ts",
498
+ * placement: { mode: "smart" }
499
+ * });
500
+ * ```
501
+ */
502
+ export interface WorkerPlacementSmart {
503
+ /**
504
+ * Enable smart placement mode.
505
+ *
506
+ * Cloudflare automatically analyzes your Worker's traffic patterns and
507
+ * places it in an optimal location based on performance metrics.
508
+ */
509
+ mode: "smart";
510
+ }
511
+
512
+ /**
513
+ * Region-based placement - place your Worker closest to a specific
514
+ * cloud provider region.
515
+ *
516
+ * Use when your back-end infrastructure runs in a known AWS, GCP, or Azure region.
517
+ *
518
+ * @example
519
+ * ```ts
520
+ * const worker = await Worker("api", {
521
+ * entrypoint: "./src/worker.ts",
522
+ * placement: { region: "aws:us-east-1" }
523
+ * });
524
+ * ```
525
+ */
526
+ export interface WorkerPlacementRegion {
527
+ /**
528
+ * Cloud provider region to place your Worker closest to.
529
+ *
530
+ * Format: `{provider}:{region}`
531
+ *
532
+ * Supported providers:
533
+ * - AWS: `aws:us-east-1`, `aws:us-west-2`, `aws:eu-central-1`, etc.
534
+ * - GCP: `gcp:us-east4`, `gcp:europe-west1`, `gcp:asia-east1`, etc.
535
+ * - Azure: `azure:westeurope`, `azure:eastus`, `azure:southeastasia`, etc.
536
+ *
537
+ * @see https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html
538
+ * @see https://cloud.google.com/compute/docs/regions-zones
539
+ * @see https://learn.microsoft.com/en-us/azure/reliability/regions-list
540
+ *
541
+ * @example "aws:us-east-1"
542
+ * @example "gcp:us-east4"
543
+ * @example "azure:westeurope"
544
+ */
545
+ region: string;
546
+ }
547
+
548
+ /**
549
+ * Host-based placement (layer 4) - place your Worker closest to a specific
550
+ * TCP endpoint.
551
+ *
552
+ * Use when your infrastructure is not in a major cloud provider and you need
553
+ * to probe a TCP service (like a database) to determine optimal placement.
554
+ *
555
+ * @example
556
+ * ```ts
557
+ * const worker = await Worker("api", {
558
+ * entrypoint: "./src/worker.ts",
559
+ * placement: { host: "my-database.example.com:5432" }
560
+ * });
561
+ * ```
562
+ */
563
+ export interface WorkerPlacementHost {
564
+ /**
565
+ * Host endpoint to probe (TCP/layer 4) for placement.
566
+ *
567
+ * Cloudflare uses TCP CONNECT checks to measure latency and selects
568
+ * the best data center. Use this for database hosts or other TCP services.
569
+ *
570
+ * Format: `hostname:port`
571
+ *
572
+ * @example "my_database_host.com:5432"
573
+ */
574
+ host: string;
575
+ }
576
+
577
+ /**
578
+ * Hostname-based placement (layer 7) - place your Worker closest to a specific
579
+ * HTTP endpoint.
580
+ *
581
+ * Use when your infrastructure is not in a major cloud provider and you need
582
+ * to probe an HTTP service (like an API) to determine optimal placement.
583
+ *
584
+ * @example
585
+ * ```ts
586
+ * const worker = await Worker("api", {
587
+ * entrypoint: "./src/worker.ts",
588
+ * placement: { hostname: "my-api.example.com" }
589
+ * });
590
+ * ```
591
+ */
592
+ export interface WorkerPlacementHostname {
593
+ /**
594
+ * Hostname to probe (HTTP/layer 7) for placement.
595
+ *
596
+ * Cloudflare uses HTTP HEAD checks to measure latency and selects
597
+ * the best data center. Use this for API endpoints or other HTTP services.
598
+ *
599
+ * @example "my_api_server.com"
600
+ */
601
+ hostname: string;
602
+ }
603
+
480
604
  export interface InlineWorkerProps<
481
605
  B extends Bindings | undefined = Bindings,
482
606
  RPC extends Rpc.WorkerEntrypointBranded = Rpc.WorkerEntrypointBranded,
@@ -630,11 +754,9 @@ export type Worker<
630
754
  version?: string;
631
755
 
632
756
  /**
633
- * Smart placement configuration for the worker
757
+ * Placement configuration for the worker
634
758
  */
635
- placement?: {
636
- mode: "smart";
637
- };
759
+ placement?: WorkerPlacement;
638
760
 
639
761
  /**
640
762
  * Whether the worker has a remote deployment
@@ -89,8 +89,7 @@ export interface AutomaticHTTPSRewritesSetting extends CloudflareSettingBase {
89
89
  /**
90
90
  * Automatic Platform Optimization setting
91
91
  */
92
- export interface AutomaticPlatformOptimizationSetting
93
- extends CloudflareSettingBase {
92
+ export interface AutomaticPlatformOptimizationSetting extends CloudflareSettingBase {
94
93
  id: "automatic_platform_optimization";
95
94
  value: {
96
95
  enabled: boolean;
@@ -48,8 +48,7 @@ export interface EvmSmartAccountProps extends CoinbaseClientOptions {
48
48
  faucet?: FaucetConfig;
49
49
  }
50
50
 
51
- export interface EvmSmartAccount
52
- extends Resource<"coinbase::evm-smart-account"> {
51
+ export interface EvmSmartAccount extends Resource<"coinbase::evm-smart-account"> {
53
52
  /**
54
53
  * The smart account name in CDP
55
54
  */
@@ -141,7 +141,9 @@ for (const account of accounts) {
141
141
 
142
142
  // Skip if already processed in this run
143
143
  if (funded.has(key)) {
144
- console.log(` ⏭️ Skipping ${token} on ${network} (already requested)`);
144
+ console.log(
145
+ ` ⏭️ Skipping ${token} on ${network} (already requested)`,
146
+ );
145
147
  continue;
146
148
  }
147
149
 
package/src/context.ts CHANGED
@@ -17,8 +17,9 @@ export type Context<
17
17
  Props extends ResourceProps = ResourceProps,
18
18
  > = CreateContext<Out> | UpdateContext<Out, Props> | DeleteContext<Out, Props>;
19
19
 
20
- export interface CreateContext<Out extends ResourceAttributes>
21
- extends BaseContext<Out> {
20
+ export interface CreateContext<
21
+ Out extends ResourceAttributes,
22
+ > extends BaseContext<Out> {
22
23
  phase: "create";
23
24
  output?: undefined;
24
25
  props?: undefined;
@@ -73,8 +73,9 @@ export interface BundleProps extends Partial<esbuild.BuildOptions> {
73
73
  /**
74
74
  * Output returned after bundle creation/update
75
75
  */
76
- export interface Bundle<P extends BundleProps = BundleProps>
77
- extends BundleProps {
76
+ export interface Bundle<
77
+ P extends BundleProps = BundleProps,
78
+ > extends BundleProps {
78
79
  /**
79
80
  * Path to the bundled file
80
81
  * Absolute or relative path to the generated bundle