alchemy 0.92.2 → 0.93.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 +1 -1
- package/lib/cloudflare/ai-search-namespace.d.ts +150 -0
- package/lib/cloudflare/ai-search-namespace.d.ts.map +1 -0
- package/lib/cloudflare/ai-search-namespace.js +281 -0
- package/lib/cloudflare/ai-search-namespace.js.map +1 -0
- package/lib/cloudflare/ai-search-token.d.ts +11 -1
- package/lib/cloudflare/ai-search-token.d.ts.map +1 -1
- package/lib/cloudflare/ai-search-token.js +35 -7
- package/lib/cloudflare/ai-search-token.js.map +1 -1
- package/lib/cloudflare/ai-search.d.ts +123 -18
- package/lib/cloudflare/ai-search.d.ts.map +1 -1
- package/lib/cloudflare/ai-search.js +239 -68
- package/lib/cloudflare/ai-search.js.map +1 -1
- package/lib/cloudflare/bindings.d.ts +29 -2
- package/lib/cloudflare/bindings.d.ts.map +1 -1
- package/lib/cloudflare/bindings.js.map +1 -1
- package/lib/cloudflare/bound.d.ts +4 -2
- package/lib/cloudflare/bound.d.ts.map +1 -1
- package/lib/cloudflare/container.d.ts +79 -6
- package/lib/cloudflare/container.d.ts.map +1 -1
- package/lib/cloudflare/container.js +117 -2
- package/lib/cloudflare/container.js.map +1 -1
- package/lib/cloudflare/index.d.ts +1 -0
- package/lib/cloudflare/index.d.ts.map +1 -1
- package/lib/cloudflare/index.js +1 -0
- package/lib/cloudflare/index.js.map +1 -1
- package/lib/cloudflare/miniflare/build-worker-options.d.ts.map +1 -1
- package/lib/cloudflare/miniflare/build-worker-options.js +37 -0
- package/lib/cloudflare/miniflare/build-worker-options.js.map +1 -1
- package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
- package/lib/cloudflare/worker-metadata.js +25 -1
- package/lib/cloudflare/worker-metadata.js.map +1 -1
- package/lib/cloudflare/wrangler.json.d.ts.map +1 -1
- package/lib/cloudflare/wrangler.json.js +32 -0
- package/lib/cloudflare/wrangler.json.js.map +1 -1
- package/lib/docker/api.d.ts +4 -1
- package/lib/docker/api.d.ts.map +1 -1
- package/lib/docker/api.js +8 -2
- package/lib/docker/api.js.map +1 -1
- package/package.json +2 -2
- package/src/cloudflare/ai-search-namespace.ts +439 -0
- package/src/cloudflare/ai-search-token.ts +43 -9
- package/src/cloudflare/ai-search.ts +353 -73
- package/src/cloudflare/bindings.ts +33 -0
- package/src/cloudflare/bound.ts +87 -74
- package/src/cloudflare/container.ts +228 -11
- package/src/cloudflare/index.ts +1 -0
- package/src/cloudflare/miniflare/build-worker-options.ts +39 -0
- package/src/cloudflare/worker-metadata.ts +25 -1
- package/src/cloudflare/wrangler.json.ts +32 -0
- package/src/docker/api.ts +11 -2
- package/workers/cloudflare-state-store.js +57 -57
- 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
|
*/
|
package/src/cloudflare/bound.ts
CHANGED
|
@@ -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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
|
52
|
-
?
|
|
53
|
-
: T extends
|
|
54
|
-
?
|
|
55
|
-
: T extends
|
|
56
|
-
?
|
|
57
|
-
: T extends
|
|
58
|
-
?
|
|
59
|
-
: T extends
|
|
60
|
-
?
|
|
61
|
-
: T extends
|
|
62
|
-
?
|
|
63
|
-
: T extends
|
|
64
|
-
?
|
|
65
|
-
: T extends
|
|
66
|
-
?
|
|
67
|
-
: T extends
|
|
68
|
-
?
|
|
69
|
-
: T extends
|
|
70
|
-
?
|
|
71
|
-
: T extends
|
|
72
|
-
?
|
|
73
|
-
: T extends
|
|
74
|
-
?
|
|
75
|
-
: T extends
|
|
76
|
-
?
|
|
77
|
-
: T extends
|
|
78
|
-
?
|
|
79
|
-
: T extends
|
|
80
|
-
?
|
|
81
|
-
: T extends
|
|
82
|
-
?
|
|
83
|
-
: T extends
|
|
84
|
-
?
|
|
85
|
-
: T extends
|
|
86
|
-
?
|
|
87
|
-
: T extends
|
|
88
|
-
?
|
|
89
|
-
: T extends
|
|
90
|
-
?
|
|
91
|
-
: T extends
|
|
92
|
-
?
|
|
93
|
-
: T extends
|
|
94
|
-
?
|
|
95
|
-
: T extends
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
?
|
|
101
|
-
: T extends
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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 {
|
|
2
|
+
import { DockerApi } from "../docker/api.ts";
|
|
3
|
+
import {
|
|
4
|
+
Image,
|
|
5
|
+
type DockerBuildOptions,
|
|
6
|
+
type ImageProps,
|
|
7
|
+
} from "../docker/image.ts";
|
|
8
|
+
import type { RemoteImage } from "../docker/remote-image.ts";
|
|
3
9
|
import { Resource } from "../resource.ts";
|
|
4
10
|
import { Scope } from "../scope.ts";
|
|
5
11
|
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
|
-
*
|
|
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
|
-
|
|
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,
|
|
@@ -264,7 +482,6 @@ export async function Container<T>(
|
|
|
264
482
|
platform: "linux/amd64",
|
|
265
483
|
context: process.cwd(),
|
|
266
484
|
},
|
|
267
|
-
image: props.image,
|
|
268
485
|
registry: {
|
|
269
486
|
server: "registry.cloudflare.com",
|
|
270
487
|
username: credentials.username || credentials.user!,
|
package/src/cloudflare/index.ts
CHANGED
|
@@ -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";
|
|
@@ -4,6 +4,8 @@ import { assertNever } from "../../util/assert-never.ts";
|
|
|
4
4
|
import { reservePort } from "../../util/find-open-port.ts";
|
|
5
5
|
import type { HTTPServer } from "../../util/http.ts";
|
|
6
6
|
import { logger } from "../../util/logger.ts";
|
|
7
|
+
import { isAiSearchNamespace } from "../ai-search-namespace.ts";
|
|
8
|
+
import { isAiSearch } from "../ai-search.ts";
|
|
7
9
|
import type { CloudflareApi } from "../api.ts";
|
|
8
10
|
import type {
|
|
9
11
|
Binding,
|
|
@@ -38,6 +40,8 @@ type RemoteBinding =
|
|
|
38
40
|
{
|
|
39
41
|
type:
|
|
40
42
|
| "ai"
|
|
43
|
+
| "ai_search"
|
|
44
|
+
| "ai_search_namespace"
|
|
41
45
|
| "browser"
|
|
42
46
|
| "dispatch_namespace"
|
|
43
47
|
| "mtls_certificate"
|
|
@@ -90,6 +94,29 @@ export const buildWorkerOptions = async (
|
|
|
90
94
|
(options.bindings ??= {})[key] = binding;
|
|
91
95
|
continue;
|
|
92
96
|
}
|
|
97
|
+
if (isAiSearch(binding)) {
|
|
98
|
+
// AI Search instance bindings are not supported natively by Miniflare;
|
|
99
|
+
// proxy them to the deployed instance (same mechanism used by `ai`,
|
|
100
|
+
// `vectorize`, etc.). Instance bindings are always scoped to the
|
|
101
|
+
// default namespace on the CF side, so the namespace need not be
|
|
102
|
+
// surfaced in the remote-proxy metadata.
|
|
103
|
+
remoteBindings.push({
|
|
104
|
+
type: "ai_search",
|
|
105
|
+
name: key,
|
|
106
|
+
instance_name: binding.name,
|
|
107
|
+
raw: true,
|
|
108
|
+
});
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
if (isAiSearchNamespace(binding)) {
|
|
112
|
+
remoteBindings.push({
|
|
113
|
+
type: "ai_search_namespace",
|
|
114
|
+
name: key,
|
|
115
|
+
namespace: binding.namespace,
|
|
116
|
+
raw: true,
|
|
117
|
+
});
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
93
120
|
if (binding.type === "cloudflare::Worker::Self") {
|
|
94
121
|
(options.serviceBindings ??= {})[key] = miniflare.kCurrentWorker;
|
|
95
122
|
continue;
|
|
@@ -527,6 +554,18 @@ export const buildWorkerOptions = async (
|
|
|
527
554
|
remoteProxyConnectionString: remoteProxy.connectionString,
|
|
528
555
|
};
|
|
529
556
|
break;
|
|
557
|
+
case "ai_search":
|
|
558
|
+
(options.aiSearchInstances ??= {})[binding.name] = {
|
|
559
|
+
instance_name: binding.instance_name,
|
|
560
|
+
remoteProxyConnectionString: remoteProxy.connectionString,
|
|
561
|
+
};
|
|
562
|
+
break;
|
|
563
|
+
case "ai_search_namespace":
|
|
564
|
+
(options.aiSearchNamespaces ??= {})[binding.name] = {
|
|
565
|
+
namespace: binding.namespace,
|
|
566
|
+
remoteProxyConnectionString: remoteProxy.connectionString,
|
|
567
|
+
};
|
|
568
|
+
break;
|
|
530
569
|
default: {
|
|
531
570
|
assertNever(binding);
|
|
532
571
|
}
|
|
@@ -2,6 +2,8 @@ import { assertNever } from "../util/assert-never.ts";
|
|
|
2
2
|
import { camelToSnakeObjectDeep } from "../util/camel-to-snake.ts";
|
|
3
3
|
import { logger } from "../util/logger.ts";
|
|
4
4
|
import { memoize } from "../util/memoize.ts";
|
|
5
|
+
import { isAiSearchNamespace } from "./ai-search-namespace.ts";
|
|
6
|
+
import { isAiSearch } from "./ai-search.ts";
|
|
5
7
|
import { extractCloudflareResult } from "./api-response.ts";
|
|
6
8
|
import type { CloudflareApi } from "./api.ts";
|
|
7
9
|
import type {
|
|
@@ -418,7 +420,29 @@ export async function prepareWorkerMetadata(
|
|
|
418
420
|
for (const [bindingName, binding] of Object.entries(bindings)) {
|
|
419
421
|
// Create a copy of the binding to avoid modifying the original
|
|
420
422
|
|
|
421
|
-
if (
|
|
423
|
+
if (isAiSearch(binding)) {
|
|
424
|
+
// Single-instance bindings (ai_search) are always scoped to the `default` namespace.
|
|
425
|
+
if (binding.namespace !== undefined && binding.namespace !== "default") {
|
|
426
|
+
throw new Error(
|
|
427
|
+
`Worker binding "${bindingName}" uses a single-instance AiSearch binding (type: "ai_search"), ` +
|
|
428
|
+
`but the bound AiSearch instance "${binding.name}" is in namespace "${binding.namespace}". ` +
|
|
429
|
+
`Single-instance bindings only support the "default" namespace.\n` +
|
|
430
|
+
`Fix: either (1) create the AiSearch without a custom namespace, or ` +
|
|
431
|
+
`(2) bind the enclosing AiSearchNamespace and use \`env.${bindingName}.get("${binding.name}")\`.`,
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
meta.bindings.push({
|
|
435
|
+
type: "ai_search",
|
|
436
|
+
name: bindingName,
|
|
437
|
+
instance_name: binding.name,
|
|
438
|
+
});
|
|
439
|
+
} else if (isAiSearchNamespace(binding)) {
|
|
440
|
+
meta.bindings.push({
|
|
441
|
+
type: "ai_search_namespace",
|
|
442
|
+
name: bindingName,
|
|
443
|
+
namespace: binding.namespace,
|
|
444
|
+
});
|
|
445
|
+
} else if (typeof binding === "string") {
|
|
422
446
|
meta.bindings.push({
|
|
423
447
|
type: "plain_text",
|
|
424
448
|
name: bindingName,
|