alchemy 0.92.2 → 0.93.1

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 (82) hide show
  1. package/bin/alchemy.js +2 -2
  2. package/lib/cloudflare/ai-search-namespace.d.ts +150 -0
  3. package/lib/cloudflare/ai-search-namespace.d.ts.map +1 -0
  4. package/lib/cloudflare/ai-search-namespace.js +278 -0
  5. package/lib/cloudflare/ai-search-namespace.js.map +1 -0
  6. package/lib/cloudflare/ai-search-token.d.ts +11 -1
  7. package/lib/cloudflare/ai-search-token.d.ts.map +1 -1
  8. package/lib/cloudflare/ai-search-token.js +35 -7
  9. package/lib/cloudflare/ai-search-token.js.map +1 -1
  10. package/lib/cloudflare/ai-search.d.ts +123 -18
  11. package/lib/cloudflare/ai-search.d.ts.map +1 -1
  12. package/lib/cloudflare/ai-search.js +222 -68
  13. package/lib/cloudflare/ai-search.js.map +1 -1
  14. package/lib/cloudflare/bindings.d.ts +29 -2
  15. package/lib/cloudflare/bindings.d.ts.map +1 -1
  16. package/lib/cloudflare/bindings.js.map +1 -1
  17. package/lib/cloudflare/bound.d.ts +4 -2
  18. package/lib/cloudflare/bound.d.ts.map +1 -1
  19. package/lib/cloudflare/container.d.ts +79 -6
  20. package/lib/cloudflare/container.d.ts.map +1 -1
  21. package/lib/cloudflare/container.js +130 -9
  22. package/lib/cloudflare/container.js.map +1 -1
  23. package/lib/cloudflare/custom-domain.d.ts +14 -0
  24. package/lib/cloudflare/custom-domain.d.ts.map +1 -1
  25. package/lib/cloudflare/custom-domain.js +3 -0
  26. package/lib/cloudflare/custom-domain.js.map +1 -1
  27. package/lib/cloudflare/index.d.ts +1 -0
  28. package/lib/cloudflare/index.d.ts.map +1 -1
  29. package/lib/cloudflare/index.js +1 -0
  30. package/lib/cloudflare/index.js.map +1 -1
  31. package/lib/cloudflare/miniflare/build-worker-options.d.ts.map +1 -1
  32. package/lib/cloudflare/miniflare/build-worker-options.js +43 -0
  33. package/lib/cloudflare/miniflare/build-worker-options.js.map +1 -1
  34. package/lib/cloudflare/miniflare/remote-binding-proxy.d.ts.map +1 -1
  35. package/lib/cloudflare/miniflare/remote-binding-proxy.js +31 -13
  36. package/lib/cloudflare/miniflare/remote-binding-proxy.js.map +1 -1
  37. package/lib/cloudflare/website.d.ts +4 -0
  38. package/lib/cloudflare/website.d.ts.map +1 -1
  39. package/lib/cloudflare/website.js +15 -1
  40. package/lib/cloudflare/website.js.map +1 -1
  41. package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
  42. package/lib/cloudflare/worker-metadata.js +25 -1
  43. package/lib/cloudflare/worker-metadata.js.map +1 -1
  44. package/lib/cloudflare/worker.d.ts +6 -0
  45. package/lib/cloudflare/worker.d.ts.map +1 -1
  46. package/lib/cloudflare/worker.js +3 -0
  47. package/lib/cloudflare/worker.js.map +1 -1
  48. package/lib/cloudflare/wrangler.json.d.ts.map +1 -1
  49. package/lib/cloudflare/wrangler.json.js +32 -0
  50. package/lib/cloudflare/wrangler.json.js.map +1 -1
  51. package/lib/docker/api.d.ts +4 -1
  52. package/lib/docker/api.d.ts.map +1 -1
  53. package/lib/docker/api.js +8 -2
  54. package/lib/docker/api.js.map +1 -1
  55. package/lib/docker/image.d.ts +1 -1
  56. package/lib/docker/image.d.ts.map +1 -1
  57. package/lib/docker/image.js +4 -44
  58. package/lib/docker/image.js.map +1 -1
  59. package/lib/docker/registry.d.ts +11 -0
  60. package/lib/docker/registry.d.ts.map +1 -0
  61. package/lib/docker/registry.js +41 -0
  62. package/lib/docker/registry.js.map +1 -0
  63. package/package.json +3 -3
  64. package/src/cloudflare/ai-search-namespace.ts +435 -0
  65. package/src/cloudflare/ai-search-token.ts +43 -9
  66. package/src/cloudflare/ai-search.ts +334 -73
  67. package/src/cloudflare/bindings.ts +33 -0
  68. package/src/cloudflare/bound.ts +87 -74
  69. package/src/cloudflare/container.ts +242 -18
  70. package/src/cloudflare/custom-domain.ts +25 -1
  71. package/src/cloudflare/index.ts +1 -0
  72. package/src/cloudflare/miniflare/build-worker-options.ts +50 -1
  73. package/src/cloudflare/miniflare/remote-binding-proxy.ts +58 -34
  74. package/src/cloudflare/website.ts +19 -1
  75. package/src/cloudflare/worker-metadata.ts +25 -1
  76. package/src/cloudflare/worker.ts +9 -0
  77. package/src/cloudflare/wrangler.json.ts +32 -0
  78. package/src/docker/api.ts +11 -2
  79. package/src/docker/image.ts +5 -56
  80. package/src/docker/registry.ts +62 -0
  81. package/workers/cloudflare-state-store.js +57 -57
  82. package/workers/tunnel-proxy.js +1 -1
@@ -5,6 +5,8 @@
5
5
  */
6
6
  import type { Secret } from "../secret.ts";
7
7
  import type { Ai } from "./ai.ts";
8
+ import type { AiSearchNamespace } from "./ai-search-namespace.ts";
9
+ import type { AiSearch } from "./ai-search.ts";
8
10
  import type { AnalyticsEngineDataset } from "./analytics-engine.ts";
9
11
  import type { Assets } from "./assets.ts";
10
12
  import type { Bound } from "./bound.ts";
@@ -49,6 +51,8 @@ export declare namespace Bindings {
49
51
  */
50
52
  export type Binding =
51
53
  | Ai
54
+ | AiSearch
55
+ | AiSearchNamespace
52
56
  | Assets
53
57
  | Container
54
58
  | CloudflareSecret
@@ -120,6 +124,8 @@ export function Json<const T>(value: T): Json<T> {
120
124
  */
121
125
  export type WorkerBindingSpec =
122
126
  | WorkerBindingAI
127
+ | WorkerBindingAiSearch
128
+ | WorkerBindingAiSearchNamespace
123
129
  | WorkerBindingAnalyticsEngine
124
130
  | WorkerBindingAssets
125
131
  | WorkerBindingBrowserRendering
@@ -161,6 +167,33 @@ export interface WorkerBindingAI {
161
167
  type: "ai";
162
168
  }
163
169
 
170
+ /**
171
+ * AI Search single instance binding type.
172
+ * Binds directly to one specific AI Search instance (always scoped to default namespace).
173
+ */
174
+ export interface WorkerBindingAiSearch {
175
+ /** The name of the binding */
176
+ name: string;
177
+ /** Type identifier for AI Search single instance binding */
178
+ type: "ai_search";
179
+ /** The AI Search instance name */
180
+ instance_name: string;
181
+ }
182
+
183
+ /**
184
+ * AI Search namespace binding type.
185
+ * Scoped to a user-defined namespace of AI Search instances.
186
+ * Grants full access (CRUD + search + chat) to all instances within the namespace.
187
+ */
188
+ export interface WorkerBindingAiSearchNamespace {
189
+ /** The name of the binding */
190
+ name: string;
191
+ /** Type identifier for AI Search namespace binding */
192
+ type: "ai_search_namespace";
193
+ /** The namespace name */
194
+ namespace: string;
195
+ }
196
+
164
197
  /**
165
198
  * Analytics Engine binding type
166
199
  */
@@ -1,6 +1,9 @@
1
+ /// <reference types="@cloudflare/workers-types" />
1
2
  import type { Pipeline } from "cloudflare:pipelines";
2
3
  import type { Secret } from "../secret.ts";
3
4
  import type { Ai as _Ai } from "./ai.ts";
5
+ import type { AiSearchNamespace as _AiSearchNamespace } from "./ai-search-namespace.ts";
6
+ import type { AiSearch as _AiSearch } from "./ai-search.ts";
4
7
  import type { AnalyticsEngineDataset as _AnalyticsEngineDataset } from "./analytics-engine.ts";
5
8
  import type { Assets } from "./assets.ts";
6
9
  import type { Binding, Json, Self } from "./bindings.ts";
@@ -9,6 +12,7 @@ import type { R2Bucket as _R2Bucket } from "./bucket.ts";
9
12
  import type { Container as _Container } from "./container.ts";
10
13
  import type { D1Database as _D1Database } from "./d1-database.ts";
11
14
  import type { DurableObjectNamespace as _DurableObjectNamespace } from "./durable-object-namespace.ts";
15
+ import type { EmailSender as _EmailSender } from "./email-sender.ts";
12
16
  import type { HyperdriveRef } from "./hyperdrive-ref.ts";
13
17
  import type { Hyperdrive as _Hyperdrive } from "./hyperdrive.ts";
14
18
  import type { Images as _Images } from "./images.ts";
@@ -18,7 +22,6 @@ import type { RateLimit as _RateLimit } from "./rate-limit.ts";
18
22
  import type { SecretKey } from "./secret-key.ts";
19
23
  import type { SecretRef as CloudflareSecretRef } from "./secret-ref.ts";
20
24
  import type { Secret as CloudflareSecret } from "./secret.ts";
21
- import type { EmailSender as _EmailSender } from "./email-sender.ts";
22
25
  import type { VectorizeIndex as _VectorizeIndex } from "./vectorize-index.ts";
23
26
  import type { VersionMetadata as _VersionMetadata } from "./version-metadata.ts";
24
27
  import type { VpcService as _VpcService } from "./vpc-service.ts";
@@ -39,78 +42,88 @@ type BoundWorker<
39
42
  >]: Rpc.Provider<RPC, "fetch" | "connect">[property];
40
43
  };
41
44
 
42
- export type Bound<T extends Binding> =
43
- T extends _DurableObjectNamespace<infer O>
44
- ? DurableObjectNamespace<O & Rpc.DurableObjectBranded>
45
- : T extends { type: "kv_namespace" }
46
- ? KVNamespace
47
- : T extends WorkerStub<infer RPC>
48
- ? BoundWorker<RPC>
49
- : T extends _Worker<any, infer RPC> | WorkerRef<infer RPC>
45
+ // NOTE: AiSearch / AiSearchNamespace MUST come FIRST in this conditional
46
+ // chain, before any structural discriminator like `{ type: "kv_namespace" }`.
47
+ // `AiSearch.type` is the data-source type (`"r2" | "web-crawler"`), NOT a
48
+ // binding-type discriminator so we match by *resource-type import* via the
49
+ // `_AiSearch` / `_AiSearchNamespace` aliases (underscore-prefixed to avoid
50
+ // colliding with the identically-named Cloudflare runtime classes on the
51
+ // right-hand side of the conditional).
52
+ export type Bound<T extends Binding> = T extends _AiSearch
53
+ ? AiSearchInstance
54
+ : T extends _AiSearchNamespace
55
+ ? AiSearchNamespace
56
+ : T extends _DurableObjectNamespace<infer O>
57
+ ? DurableObjectNamespace<O & Rpc.DurableObjectBranded>
58
+ : T extends { type: "kv_namespace" }
59
+ ? KVNamespace
60
+ : T extends WorkerStub<infer RPC>
50
61
  ? BoundWorker<RPC>
51
- : T extends { type: "service" }
52
- ? Service
53
- : T extends _R2Bucket
54
- ? R2Bucket
55
- : T extends _Hyperdrive | HyperdriveRef
56
- ? Hyperdrive
57
- : T extends Secret
58
- ? string
59
- : T extends CloudflareSecret | CloudflareSecretRef
60
- ? SecretsStoreSecret
61
- : T extends _EmailSender
62
- ? SendEmail
63
- : T extends SecretKey
64
- ? CryptoKey
65
- : T extends Assets
66
- ? Service
67
- : T extends _Workflow<infer P>
68
- ? Workflow<P>
69
- : T extends _D1Database
70
- ? D1Database
71
- : T extends DispatchNamespace
72
- ? DispatchNamespace
73
- : T extends _WorkerLoader
74
- ? WorkerLoader
75
- : T extends _VectorizeIndex
76
- ? VectorizeIndex
77
- : T extends _Queue<infer Body>
78
- ? Queue<Body>
79
- : T extends _AnalyticsEngineDataset
80
- ? AnalyticsEngineDataset
81
- : T extends _Pipeline<infer R>
82
- ? Pipeline<R>
83
- : T extends _RateLimit
84
- ? RateLimit
85
- : T extends string
86
- ? T
87
- : T extends BrowserRendering
88
- ? Fetcher
89
- : T extends _Ai<infer M>
90
- ? Ai<M>
91
- : T extends _Images
92
- ? ImagesBinding
93
- : T extends _VersionMetadata
94
- ? WorkerVersionMetadata
95
- : T extends
96
- | _Worker.DevDomain
97
- | _Worker.DevUrl
98
- ? string
99
- : T extends Self
100
- ? Service
101
- : T extends Json<
102
- infer T
103
- >
104
- ? T
105
- : T extends _Container<
106
- infer Obj
107
- >
108
- ? DurableObjectNamespace<
109
- Obj &
110
- Rpc.DurableObjectBranded
62
+ : T extends _Worker<any, infer RPC> | WorkerRef<infer RPC>
63
+ ? BoundWorker<RPC>
64
+ : T extends { type: "service" }
65
+ ? Service
66
+ : T extends _R2Bucket
67
+ ? R2Bucket
68
+ : T extends _Hyperdrive | HyperdriveRef
69
+ ? Hyperdrive
70
+ : T extends Secret
71
+ ? string
72
+ : T extends CloudflareSecret | CloudflareSecretRef
73
+ ? SecretsStoreSecret
74
+ : T extends _EmailSender
75
+ ? SendEmail
76
+ : T extends SecretKey
77
+ ? CryptoKey
78
+ : T extends Assets
79
+ ? Service
80
+ : T extends _Workflow<infer P>
81
+ ? Workflow<P>
82
+ : T extends _D1Database
83
+ ? D1Database
84
+ : T extends DispatchNamespace
85
+ ? DispatchNamespace
86
+ : T extends _WorkerLoader
87
+ ? WorkerLoader
88
+ : T extends _VectorizeIndex
89
+ ? VectorizeIndex
90
+ : T extends _Queue<infer Body>
91
+ ? Queue<Body>
92
+ : T extends _AnalyticsEngineDataset
93
+ ? AnalyticsEngineDataset
94
+ : T extends _Pipeline<infer R>
95
+ ? Pipeline<R>
96
+ : T extends _RateLimit
97
+ ? RateLimit
98
+ : T extends string
99
+ ? T
100
+ : T extends BrowserRendering
101
+ ? Fetcher
102
+ : T extends _Ai<infer M>
103
+ ? Ai<M>
104
+ : T extends _Images
105
+ ? ImagesBinding
106
+ : T extends _VersionMetadata
107
+ ? WorkerVersionMetadata
108
+ : T extends
109
+ | _Worker.DevDomain
110
+ | _Worker.DevUrl
111
+ ? string
112
+ : T extends Self
113
+ ? Service
114
+ : T extends Json<
115
+ infer T
111
116
  >
112
- : T extends _VpcService
113
- ? Fetcher
114
- : T extends undefined
115
- ? undefined
116
- : Service;
117
+ ? T
118
+ : T extends _Container<
119
+ infer Obj
120
+ >
121
+ ? DurableObjectNamespace<
122
+ Obj &
123
+ Rpc.DurableObjectBranded
124
+ >
125
+ : T extends _VpcService
126
+ ? Fetcher
127
+ : T extends undefined
128
+ ? undefined
129
+ : Service;
@@ -1,30 +1,35 @@
1
1
  import type { Context } from "../context.ts";
2
- import { Image, type ImageProps } from "../docker/image.ts";
2
+ import { DockerApi } from "../docker/api.ts";
3
+ import {
4
+ Image,
5
+ type DockerBuildOptions,
6
+ type ImageProps,
7
+ } from "../docker/image.ts";
8
+ import { pushImageToRegistry } from "../docker/registry.ts";
9
+ import type { RemoteImage } from "../docker/remote-image.ts";
3
10
  import { Resource } from "../resource.ts";
4
11
  import { Scope } from "../scope.ts";
5
- import { secret } from "../secret.ts";
6
12
  import {
13
+ createCloudflareApi,
7
14
  type CloudflareApi,
8
15
  type CloudflareApiOptions,
9
- createCloudflareApi,
10
16
  } from "./api.ts";
11
17
 
12
18
  /**
13
- * Properties for creating a Container binding or ContainerApplication
14
- *
15
- * Extends ImageProps for Docker image configuration and CloudflareApiOptions
16
- * for Cloudflare API authentication.
19
+ * Common properties shared between build and image container configurations
17
20
  */
18
- export interface ContainerProps
19
- extends
20
- Omit<ImageProps, "registry" | "skipPush">,
21
- Partial<CloudflareApiOptions> {
21
+ interface ContainerPropsBase extends Partial<CloudflareApiOptions> {
22
22
  /**
23
23
  * The class name for the container binding.
24
24
  * This is used to identify the container class in Worker bindings.
25
25
  */
26
26
  className: string;
27
27
 
28
+ /**
29
+ * Tag for the image (e.g., "latest")
30
+ */
31
+ tag?: string;
32
+
28
33
  /**
29
34
  * Maximum number of container instances that can be running.
30
35
  * Controls horizontal scaling limits.
@@ -92,6 +97,75 @@ export interface ContainerProps
92
97
  rollout?: ContainerApplicationRollout;
93
98
  }
94
99
 
100
+ /**
101
+ * Container configuration using an existing image reference
102
+ */
103
+ interface ContainerPropsWithImage extends ContainerPropsBase {
104
+ /**
105
+ * Name for the container application.
106
+ *
107
+ * @default ${app}-${stage}-${id}
108
+ */
109
+ name?: string;
110
+
111
+ /**
112
+ * Image name or reference (e.g., "nginx:alpine")
113
+ *
114
+ * Use this when you want to deploy an existing image rather than building one.
115
+ * Cannot be used together with `build`.
116
+ */
117
+ image: string | Image | RemoteImage;
118
+
119
+ build?: never;
120
+ }
121
+
122
+ /**
123
+ * Container configuration that builds a Docker image
124
+ */
125
+ interface ContainerPropsWithBuild extends ContainerPropsBase {
126
+ /**
127
+ * Name for the container application and image repository.
128
+ *
129
+ * @default ${app}-${stage}-${id}
130
+ */
131
+ name?: string;
132
+
133
+ /**
134
+ * Build configuration for the Docker image.
135
+ *
136
+ * Use this when you want to build an image from a Dockerfile.
137
+ * Cannot be used together with `image`.
138
+ */
139
+ build: DockerBuildOptions;
140
+
141
+ image?: never;
142
+ }
143
+
144
+ /**
145
+ * Properties for creating a Container binding or ContainerApplication
146
+ *
147
+ * Either provide `image` to use an existing image, or `build` to build from a Dockerfile.
148
+ * These options are mutually exclusive.
149
+ *
150
+ * @example
151
+ * // Using an existing image
152
+ * const container = await Container("my-container", {
153
+ * className: "MyContainer",
154
+ * image: "nginx:alpine"
155
+ * });
156
+ *
157
+ * @example
158
+ * // Building from a Dockerfile
159
+ * const container = await Container("my-container", {
160
+ * className: "MyContainer",
161
+ * build: {
162
+ * context: "./app",
163
+ * dockerfile: "Dockerfile"
164
+ * }
165
+ * });
166
+ */
167
+ export type ContainerProps = ContainerPropsWithImage | ContainerPropsWithBuild;
168
+
95
169
  /**
96
170
  * Instance types for Cloudflare Container deployments.
97
171
  *
@@ -202,10 +276,60 @@ export type Container<T = any> = {
202
276
  __phantom?: T;
203
277
  };
204
278
 
279
+ /**
280
+ * Normalize an image reference for Cloudflare Container deployments.
281
+ *
282
+ * Follows wrangler's resolveImageName logic:
283
+ * - Short names like "myapp:v1" → "registry.cloudflare.com/{accountId}/myapp:v1"
284
+ * - CF registry without accountId like "registry.cloudflare.com/myapp:v1" → adds accountId
285
+ * - External registries like "docker.io/nginx:1.25" → pass through unchanged
286
+ */
287
+ export function resolveImageName(accountId: string, image: string): string {
288
+ const cfRegistry = getCloudflareContainerRegistry();
289
+
290
+ // Check if image has a registry prefix (contains a dot in the first segment)
291
+ const segments = image.split("/");
292
+ const hasRegistryPrefix = segments.length > 1 && segments[0].includes(".");
293
+
294
+ if (!hasRegistryPrefix) {
295
+ // Short name like "myapp:v1" → add CF registry + accountId
296
+ return `${cfRegistry}/${accountId}/${image}`;
297
+ }
298
+
299
+ if (image.startsWith(`${cfRegistry}/`)) {
300
+ // CF registry image - check if accountId is present
301
+ const afterRegistry = image.slice(`${cfRegistry}/`.length);
302
+ const segments = afterRegistry.split("/");
303
+
304
+ // If only one segment (e.g., "myapp:tag"), add accountId
305
+ // If first segment doesn't look like an accountId (32 hex chars), add it
306
+ if (segments.length === 1) {
307
+ return `${cfRegistry}/${accountId}/${afterRegistry}`;
308
+ }
309
+
310
+ // Check if first segment is the accountId (32 hex chars)
311
+ const possibleAccountId = segments[0];
312
+ const isAccountId = /^[a-f0-9]{32}$/.test(possibleAccountId);
313
+
314
+ if (!isAccountId) {
315
+ // First segment is not an accountId, prepend it
316
+ return `${cfRegistry}/${accountId}/${afterRegistry}`;
317
+ }
318
+ }
319
+
320
+ // External registry or already fully-qualified CF registry → pass through
321
+ return image;
322
+ }
323
+
205
324
  export async function Container<T>(
206
325
  id: string,
207
326
  props: ContainerProps,
208
327
  ): Promise<Container<T>> {
328
+ // Validate that build and image are mutually exclusive
329
+ if (props.build && props.image) {
330
+ throw new Error("Container: specify either `build` or `image`, not both.");
331
+ }
332
+
209
333
  const scope = Scope.current;
210
334
  const name = props.name ?? scope.createPhysicalName(id);
211
335
  const tag =
@@ -230,6 +354,100 @@ export async function Container<T>(
230
354
  };
231
355
 
232
356
  const isDev = scope.local && !props.dev?.remote;
357
+
358
+ // Prebuilt image path: use as-is, no Docker pull/push
359
+ // This matches wrangler's behavior where registry URIs are passed directly
360
+ // to the Cloudflare API without any local Docker operations
361
+ if (props.image && !props.build) {
362
+ const rawImageRef =
363
+ typeof props.image === "string" ? props.image : props.image.imageRef;
364
+
365
+ if (isDev) {
366
+ // For local dev with prebuilt images, we need to pull and re-tag
367
+ const dockerApi = new DockerApi();
368
+ const devImageRef = `cloudflare-dev/${name}:${tag}`;
369
+
370
+ if (isCloudflareRegistryLink(rawImageRef)) {
371
+ // For CF registry images, authenticate before pulling
372
+ const api = await createCloudflareApi(props);
373
+ const credentials = await getContainerCredentials(api);
374
+ const cfRegistry = getCloudflareContainerRegistry();
375
+
376
+ await dockerApi.login(
377
+ cfRegistry,
378
+ credentials.username || credentials.user!,
379
+ credentials.password,
380
+ );
381
+
382
+ try {
383
+ // CF Containers run on linux/amd64, so we need to pull that platform
384
+ await dockerApi.pullImage(rawImageRef, { platform: "linux/amd64" });
385
+ await dockerApi.tagImage(rawImageRef, devImageRef);
386
+ } finally {
387
+ await dockerApi.logout(cfRegistry);
388
+ }
389
+
390
+ return {
391
+ ...output,
392
+ image: {
393
+ kind: "Image",
394
+ name: `cloudflare-dev/${name}`,
395
+ imageRef: devImageRef,
396
+ tag,
397
+ builtAt: Date.now(),
398
+ build: undefined,
399
+ },
400
+ };
401
+ }
402
+
403
+ // For external registry images in dev mode, pull and re-tag for Miniflare
404
+ const image = await Image(id, {
405
+ image: props.image,
406
+ tag,
407
+ });
408
+
409
+ await dockerApi.tagImage(image.imageRef, devImageRef);
410
+
411
+ return {
412
+ ...output,
413
+ image: {
414
+ ...image,
415
+ name: `cloudflare-dev/${name}`,
416
+ imageRef: devImageRef,
417
+ },
418
+ };
419
+ }
420
+
421
+ // Non-dev mode: normalize the image reference and use directly
422
+ const api = await createCloudflareApi(props);
423
+ const imageRef = resolveImageName(api.accountId, rawImageRef);
424
+
425
+ // Extract name and tag from the resolved reference
426
+ const [refWithoutDigest] = imageRef.split("@");
427
+ const lastColonIndex = refWithoutDigest.lastIndexOf(":");
428
+ const namePart =
429
+ lastColonIndex > -1
430
+ ? refWithoutDigest.slice(0, lastColonIndex)
431
+ : refWithoutDigest;
432
+ const tagPart =
433
+ lastColonIndex > -1 ? refWithoutDigest.slice(lastColonIndex + 1) : tag;
434
+
435
+ const image: Image = {
436
+ kind: "Image",
437
+ name: namePart,
438
+ imageRef,
439
+ tag: tagPart,
440
+ builtAt: Date.now(),
441
+ build: undefined,
442
+ };
443
+
444
+ return {
445
+ ...output,
446
+ image,
447
+ };
448
+ }
449
+
450
+ // Build path: build locally and push to Cloudflare registry
233
451
  if (isDev) {
234
452
  const image = await Image(id, {
235
453
  ...props,
@@ -249,7 +467,6 @@ export async function Container<T>(
249
467
  }
250
468
 
251
469
  const api = await createCloudflareApi(props);
252
- const credentials = await getContainerCredentials(api);
253
470
 
254
471
  const image = await Image(id, {
255
472
  name: `${api.accountId}/${name}`,
@@ -265,16 +482,23 @@ export async function Container<T>(
265
482
  context: process.cwd(),
266
483
  },
267
484
  image: props.image,
268
- registry: {
269
- server: "registry.cloudflare.com",
270
- username: credentials.username || credentials.user!,
271
- password: secret(credentials.password),
272
- },
485
+ skipPush: true,
273
486
  } as ImageProps);
274
487
 
488
+ const credentials = await getContainerCredentials(api);
489
+ const pushedImage = await pushImageToRegistry(image.imageRef, {
490
+ server: "registry.cloudflare.com",
491
+ username: credentials.username || credentials.user!,
492
+ password: credentials.password,
493
+ });
494
+
275
495
  return {
276
496
  ...output,
277
- image,
497
+ image: {
498
+ ...image,
499
+ imageRef: pushedImage.imageRef,
500
+ repoDigest: pushedImage.repoDigest,
501
+ },
278
502
  };
279
503
  }
280
504
 
@@ -44,6 +44,21 @@ export interface CustomDomainProps extends CloudflareApiOptions {
44
44
  */
45
45
  adopt?: boolean;
46
46
 
47
+ /**
48
+ * If true, forcibly transfer the hostname when it is currently bound to a
49
+ * different Worker service. Without this flag, Cloudflare rejects the
50
+ * upsert with HTTP 409 / error code 100116 "Hostname '<host>' already in
51
+ * use by other custom domain".
52
+ *
53
+ * This is destructive to the previously bound Worker (its traffic on this
54
+ * hostname stops immediately), so it is opt-in. Only relevant when the
55
+ * existing binding points at a different `workerName` or `environment`.
56
+ *
57
+ * @see https://developers.cloudflare.com/api/resources/workers/subresources/domains/methods/update/
58
+ * @default false
59
+ */
60
+ overrideExistingOrigin?: boolean;
61
+
47
62
  /**
48
63
  * Whether to delete the custom domain when removed from Alchemy.
49
64
  * If set to false, the custom domain will remain but the resource will be removed from state.
@@ -269,12 +284,21 @@ async function ensureCustomDomain(
269
284
  if (!bindingExists || needsUpdate) {
270
285
  operationPerformed = bindingExists ? "update" : "create";
271
286
 
272
- const putPayload = {
287
+ const putPayload: {
288
+ zone_id: string;
289
+ hostname: string;
290
+ service: string;
291
+ environment: string;
292
+ override_existing_origin?: boolean;
293
+ } = {
273
294
  zone_id: props.zoneId,
274
295
  hostname: domainHostname,
275
296
  service: props.workerName,
276
297
  environment: environment,
277
298
  };
299
+ if (props.overrideExistingOrigin !== undefined) {
300
+ putPayload.override_existing_origin = props.overrideExistingOrigin;
301
+ }
278
302
 
279
303
  const putResponse = await api.put(
280
304
  `/accounts/${api.accountId}/workers/domains`,
@@ -4,6 +4,7 @@ export * from "./account-api-token.ts";
4
4
  export * from "./account-id.ts";
5
5
  export * from "./ai-crawler.ts";
6
6
  export * from "./ai-gateway.ts";
7
+ export * from "./ai-search-namespace.ts";
7
8
  export * from "./ai-search-token.ts";
8
9
  export * from "./ai-search.ts";
9
10
  export * from "./ai.ts";