alchemy 0.15.4 → 0.15.6
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/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/kv-namespace.d.ts +39 -0
- package/lib/cloudflare/kv-namespace.d.ts.map +1 -1
- package/lib/cloudflare/kv-namespace.js +95 -26
- package/lib/cloudflare/kv-namespace.js.map +1 -1
- package/lib/cloudflare/route.d.ts +102 -0
- package/lib/cloudflare/route.d.ts.map +1 -0
- package/lib/cloudflare/route.js +130 -0
- package/lib/cloudflare/route.js.map +1 -0
- package/lib/cloudflare/worker.d.ts +6 -0
- package/lib/cloudflare/worker.d.ts.map +1 -1
- package/lib/cloudflare/worker.js +6 -0
- package/lib/cloudflare/worker.js.map +1 -1
- package/lib/cloudflare/wrangler.json.d.ts +20 -0
- package/lib/cloudflare/wrangler.json.d.ts.map +1 -1
- package/lib/cloudflare/wrangler.json.js +20 -3
- package/lib/cloudflare/wrangler.json.js.map +1 -1
- package/lib/internal/docs/providers.d.ts.map +1 -1
- package/lib/internal/docs/providers.js +8 -2
- package/lib/internal/docs/providers.js.map +1 -1
- package/lib/scope.d.ts +0 -1
- package/lib/scope.d.ts.map +1 -1
- package/lib/scope.js +3 -2
- package/lib/scope.js.map +1 -1
- package/package.json +1 -1
- package/src/cloudflare/index.ts +1 -0
- package/src/cloudflare/kv-namespace.ts +162 -36
- package/src/cloudflare/route.ts +268 -0
- package/src/cloudflare/worker.ts +6 -0
- package/src/cloudflare/wrangler.json.ts +42 -2
- package/src/internal/docs/providers.ts +11 -2
- package/src/scope.ts +6 -1
|
@@ -3,8 +3,8 @@ import { Resource } from "../resource.js";
|
|
|
3
3
|
import { withExponentialBackoff } from "../util/retry.js";
|
|
4
4
|
import { handleApiError } from "./api-error.js";
|
|
5
5
|
import {
|
|
6
|
-
type CloudflareApi,
|
|
7
6
|
createCloudflareApi,
|
|
7
|
+
type CloudflareApi,
|
|
8
8
|
type CloudflareApiOptions,
|
|
9
9
|
} from "./api.js";
|
|
10
10
|
|
|
@@ -22,6 +22,22 @@ export interface KVNamespaceProps extends CloudflareApiOptions {
|
|
|
22
22
|
* Only used for initial setup or updates
|
|
23
23
|
*/
|
|
24
24
|
values?: KVPair[];
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Whether to adopt an existing namespace with the same title if it exists
|
|
28
|
+
* If true and a namespace with the same title exists, it will be adopted rather than creating a new one
|
|
29
|
+
*
|
|
30
|
+
* @default false
|
|
31
|
+
*/
|
|
32
|
+
adopt?: boolean;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Whether to delete the namespace.
|
|
36
|
+
* If set to false, the namespace will remain but the resource will be removed from state
|
|
37
|
+
*
|
|
38
|
+
* @default true
|
|
39
|
+
*/
|
|
40
|
+
delete?: boolean;
|
|
25
41
|
}
|
|
26
42
|
|
|
27
43
|
/**
|
|
@@ -112,6 +128,24 @@ export interface KVNamespace
|
|
|
112
128
|
* }
|
|
113
129
|
* }]
|
|
114
130
|
* });
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
* // Adopt an existing namespace if it already exists instead of failing
|
|
134
|
+
* const existingNamespace = await KVNamespace("existing-ns", {
|
|
135
|
+
* title: "existing-namespace",
|
|
136
|
+
* adopt: true,
|
|
137
|
+
* values: [{
|
|
138
|
+
* key: "config",
|
|
139
|
+
* value: { setting: "updated-value" }
|
|
140
|
+
* }]
|
|
141
|
+
* });
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
* // When removing from Alchemy state, keep the namespace in Cloudflare
|
|
145
|
+
* const preservedNamespace = await KVNamespace("preserve-ns", {
|
|
146
|
+
* title: "preserved-namespace",
|
|
147
|
+
* delete: false
|
|
148
|
+
* });
|
|
115
149
|
*/
|
|
116
150
|
export const KVNamespace = Resource(
|
|
117
151
|
"cloudflare::KVNamespace",
|
|
@@ -126,7 +160,7 @@ export const KVNamespace = Resource(
|
|
|
126
160
|
if (this.phase === "delete") {
|
|
127
161
|
// For delete operations, we need to check if the namespace ID exists in the output
|
|
128
162
|
const namespaceId = this.output?.namespaceId;
|
|
129
|
-
if (namespaceId) {
|
|
163
|
+
if (namespaceId && props.delete !== false) {
|
|
130
164
|
await deleteKVNamespace(api, namespaceId);
|
|
131
165
|
}
|
|
132
166
|
|
|
@@ -145,10 +179,39 @@ export const KVNamespace = Resource(
|
|
|
145
179
|
if (this.phase === "update" && namespaceId) {
|
|
146
180
|
// Can't update a KV namespace title directly, just work with existing ID
|
|
147
181
|
} else {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
182
|
+
try {
|
|
183
|
+
// Try to create the KV namespace
|
|
184
|
+
const { id } = await createKVNamespace(api, props);
|
|
185
|
+
createdAt = Date.now();
|
|
186
|
+
namespaceId = id;
|
|
187
|
+
} catch (error) {
|
|
188
|
+
// Check if this is a "namespace already exists" error and adopt is enabled
|
|
189
|
+
if (
|
|
190
|
+
props.adopt &&
|
|
191
|
+
error instanceof Error &&
|
|
192
|
+
error.message.includes("already exists")
|
|
193
|
+
) {
|
|
194
|
+
console.log(`Namespace '${props.title}' already exists, adopting it`);
|
|
195
|
+
// Find the existing namespace by title
|
|
196
|
+
const existingNamespace = await findKVNamespaceByTitle(
|
|
197
|
+
api,
|
|
198
|
+
props.title,
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
if (!existingNamespace) {
|
|
202
|
+
throw new Error(
|
|
203
|
+
`Failed to find existing namespace '${props.title}' for adoption`,
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Use the existing namespace ID
|
|
208
|
+
namespaceId = existingNamespace.id;
|
|
209
|
+
createdAt = existingNamespace.createdAt || Date.now();
|
|
210
|
+
} else {
|
|
211
|
+
// Re-throw the error if adopt is false or it's not a "namespace already exists" error
|
|
212
|
+
throw error;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
152
215
|
}
|
|
153
216
|
|
|
154
217
|
await insertKVRecords(api, namespaceId, props);
|
|
@@ -232,37 +295,100 @@ export async function insertKVRecords(
|
|
|
232
295
|
return item;
|
|
233
296
|
});
|
|
234
297
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
298
|
+
await withExponentialBackoff(
|
|
299
|
+
async () => {
|
|
300
|
+
const bulkResponse = await api.put(
|
|
301
|
+
`/accounts/${api.accountId}/storage/kv/namespaces/${namespaceId}/bulk`,
|
|
302
|
+
bulkPayload,
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
if (!bulkResponse.ok) {
|
|
306
|
+
const errorData: any = await bulkResponse.json().catch(() => ({
|
|
307
|
+
errors: [{ message: bulkResponse.statusText }],
|
|
308
|
+
}));
|
|
309
|
+
const errorMessage =
|
|
310
|
+
errorData.errors?.[0]?.message || bulkResponse.statusText;
|
|
311
|
+
|
|
312
|
+
// Throw error to trigger retry
|
|
313
|
+
throw new Error(`Error writing KV batch: ${errorMessage}`);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return bulkResponse;
|
|
317
|
+
},
|
|
318
|
+
(error) => {
|
|
319
|
+
// Retry on "namespace not found" errors as they're likely propagation delays
|
|
320
|
+
return error.message?.includes("not found");
|
|
321
|
+
},
|
|
322
|
+
5, // 5 retry attempts
|
|
323
|
+
1000, // Start with 1 second delay
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
242
328
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
329
|
+
/**
|
|
330
|
+
* Interface representing a KV namespace as returned by Cloudflare API
|
|
331
|
+
*/
|
|
332
|
+
interface CloudflareKVNamespace {
|
|
333
|
+
id: string;
|
|
334
|
+
title: string;
|
|
335
|
+
supports_url_encoding?: boolean;
|
|
336
|
+
created_on?: string;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Find a KV namespace by title with pagination support
|
|
341
|
+
*/
|
|
342
|
+
export async function findKVNamespaceByTitle(
|
|
343
|
+
api: CloudflareApi,
|
|
344
|
+
title: string,
|
|
345
|
+
): Promise<{ id: string; createdAt?: number } | null> {
|
|
346
|
+
let page = 1;
|
|
347
|
+
const perPage = 100; // Maximum allowed by API
|
|
348
|
+
let hasMorePages = true;
|
|
349
|
+
|
|
350
|
+
while (hasMorePages) {
|
|
351
|
+
const response = await api.get(
|
|
352
|
+
`/accounts/${api.accountId}/storage/kv/namespaces?page=${page}&per_page=${perPage}`,
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
if (!response.ok) {
|
|
356
|
+
await handleApiError(response, "list", "kv_namespace", "all");
|
|
266
357
|
}
|
|
358
|
+
|
|
359
|
+
const data = (await response.json()) as {
|
|
360
|
+
result: CloudflareKVNamespace[];
|
|
361
|
+
result_info: {
|
|
362
|
+
count: number;
|
|
363
|
+
page: number;
|
|
364
|
+
per_page: number;
|
|
365
|
+
total_count: number;
|
|
366
|
+
};
|
|
367
|
+
success: boolean;
|
|
368
|
+
errors: any[];
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
const namespaces = data.result;
|
|
372
|
+
const resultInfo = data.result_info;
|
|
373
|
+
|
|
374
|
+
// Look for a namespace with matching title
|
|
375
|
+
const match = namespaces.find((ns) => ns.title === title);
|
|
376
|
+
if (match) {
|
|
377
|
+
return {
|
|
378
|
+
id: match.id,
|
|
379
|
+
// Convert ISO string to timestamp if available, otherwise use current time
|
|
380
|
+
createdAt: match.created_on
|
|
381
|
+
? new Date(match.created_on).getTime()
|
|
382
|
+
: undefined,
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Check if we've seen all pages
|
|
387
|
+
hasMorePages =
|
|
388
|
+
resultInfo.page * resultInfo.per_page < resultInfo.total_count;
|
|
389
|
+
page++;
|
|
267
390
|
}
|
|
391
|
+
|
|
392
|
+
// No matching namespace found
|
|
393
|
+
return null;
|
|
268
394
|
}
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import type { Context } from "../context.js";
|
|
2
|
+
import { Resource } from "../resource.js";
|
|
3
|
+
import { CloudflareApiError, handleApiError } from "./api-error.js";
|
|
4
|
+
import {
|
|
5
|
+
createCloudflareApi,
|
|
6
|
+
type CloudflareApi,
|
|
7
|
+
type CloudflareApiOptions,
|
|
8
|
+
} from "./api.js";
|
|
9
|
+
import type { Worker } from "./worker.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Properties for creating or updating a Route
|
|
13
|
+
*/
|
|
14
|
+
export interface RouteProps extends CloudflareApiOptions {
|
|
15
|
+
/**
|
|
16
|
+
* URL pattern for the route
|
|
17
|
+
* @example "example.com/*"
|
|
18
|
+
*/
|
|
19
|
+
pattern: string;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Worker script for the route
|
|
23
|
+
* This can be a Worker resource or script name as a string
|
|
24
|
+
*/
|
|
25
|
+
script: Worker | string;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Zone ID for the route
|
|
29
|
+
*/
|
|
30
|
+
zoneId: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Output returned after Route creation/update
|
|
35
|
+
*/
|
|
36
|
+
export interface Route extends Resource<"cloudflare::Route">, RouteProps {
|
|
37
|
+
/**
|
|
38
|
+
* The unique ID of the route
|
|
39
|
+
*/
|
|
40
|
+
id: string;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The URL pattern for the route
|
|
44
|
+
*/
|
|
45
|
+
pattern: string;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The Worker script name for the route
|
|
49
|
+
*/
|
|
50
|
+
script: string;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The Zone ID for the route
|
|
54
|
+
*/
|
|
55
|
+
zoneId: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Creates and manages Cloudflare Worker Routes.
|
|
60
|
+
*
|
|
61
|
+
* Routes map URL patterns to Worker scripts, allowing you to control which
|
|
62
|
+
* requests are handled by your Workers.
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* // Create a route that maps all requests on a domain to a Worker
|
|
66
|
+
* const basicRoute = await Route("main-route", {
|
|
67
|
+
* pattern: "example.com/*",
|
|
68
|
+
* script: "my-worker",
|
|
69
|
+
* zoneId: "your-zone-id"
|
|
70
|
+
* });
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* // Create a route using a Worker resource
|
|
74
|
+
* const worker = await Worker("api-worker", {
|
|
75
|
+
* script: `
|
|
76
|
+
* export default {
|
|
77
|
+
* fetch(request, env) {
|
|
78
|
+
* return new Response("Hello from API!");
|
|
79
|
+
* }
|
|
80
|
+
* }
|
|
81
|
+
* `
|
|
82
|
+
* });
|
|
83
|
+
*
|
|
84
|
+
* const apiRoute = await Route("api-route", {
|
|
85
|
+
* pattern: "api.example.com/*",
|
|
86
|
+
* script: worker,
|
|
87
|
+
* zoneId: "your-zone-id"
|
|
88
|
+
* });
|
|
89
|
+
*
|
|
90
|
+
* @see https://developers.cloudflare.com/workers/configuration/routes/
|
|
91
|
+
*/
|
|
92
|
+
export const Route = Resource(
|
|
93
|
+
"cloudflare::Route",
|
|
94
|
+
async function (
|
|
95
|
+
this: Context<Route>,
|
|
96
|
+
id: string,
|
|
97
|
+
props: RouteProps,
|
|
98
|
+
): Promise<Route> {
|
|
99
|
+
const api = await createCloudflareApi(props);
|
|
100
|
+
|
|
101
|
+
// Get script name from script prop (either a string or a Worker resource)
|
|
102
|
+
const scriptName =
|
|
103
|
+
typeof props.script === "string" ? props.script : props.script.name;
|
|
104
|
+
|
|
105
|
+
// Get zone ID from props
|
|
106
|
+
const { zoneId } = props;
|
|
107
|
+
|
|
108
|
+
if (this.phase === "delete") {
|
|
109
|
+
console.log("Deleting Route:", props.pattern);
|
|
110
|
+
|
|
111
|
+
// Only delete if we have an ID
|
|
112
|
+
if (this.output?.id) {
|
|
113
|
+
await deleteRoute(api, zoneId, this.output.id);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Return void (a deleted route has no content)
|
|
117
|
+
return this.destroy();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
let routeData: CloudflareRouteResponse;
|
|
121
|
+
|
|
122
|
+
if (this.phase === "update" && this.output?.id) {
|
|
123
|
+
console.log("Updating Route:", props.pattern);
|
|
124
|
+
|
|
125
|
+
// Update existing route
|
|
126
|
+
routeData = await updateRoute(
|
|
127
|
+
api,
|
|
128
|
+
zoneId,
|
|
129
|
+
this.output.id,
|
|
130
|
+
props.pattern,
|
|
131
|
+
scriptName,
|
|
132
|
+
);
|
|
133
|
+
} else {
|
|
134
|
+
console.log("Creating Route:", props.pattern);
|
|
135
|
+
|
|
136
|
+
// Create new route
|
|
137
|
+
routeData = await createRoute(api, zoneId, props.pattern, scriptName);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Return the route resource
|
|
141
|
+
return this({
|
|
142
|
+
id: routeData.result.id,
|
|
143
|
+
pattern: routeData.result.pattern,
|
|
144
|
+
script: routeData.result.script,
|
|
145
|
+
zoneId,
|
|
146
|
+
});
|
|
147
|
+
},
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
interface CloudflareRouteResponse {
|
|
151
|
+
result: {
|
|
152
|
+
id: string;
|
|
153
|
+
pattern: string;
|
|
154
|
+
script: string;
|
|
155
|
+
};
|
|
156
|
+
success: boolean;
|
|
157
|
+
errors: Array<{ code: number; message: string }>;
|
|
158
|
+
messages: string[];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Create a new route
|
|
163
|
+
*/
|
|
164
|
+
async function createRoute(
|
|
165
|
+
api: CloudflareApi,
|
|
166
|
+
zoneId: string,
|
|
167
|
+
pattern: string,
|
|
168
|
+
script: string,
|
|
169
|
+
): Promise<CloudflareRouteResponse> {
|
|
170
|
+
const createResponse = await api.post(`/zones/${zoneId}/workers/routes`, {
|
|
171
|
+
pattern,
|
|
172
|
+
script,
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
if (!createResponse.ok) {
|
|
176
|
+
return await handleApiError(createResponse, "creating", "Route", pattern);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return (await createResponse.json()) as CloudflareRouteResponse;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Update a route
|
|
184
|
+
*/
|
|
185
|
+
async function updateRoute(
|
|
186
|
+
api: CloudflareApi,
|
|
187
|
+
zoneId: string,
|
|
188
|
+
routeId: string,
|
|
189
|
+
pattern: string,
|
|
190
|
+
script: string,
|
|
191
|
+
): Promise<CloudflareRouteResponse> {
|
|
192
|
+
const updateResponse = await api.put(
|
|
193
|
+
`/zones/${zoneId}/workers/routes/${routeId}`,
|
|
194
|
+
{
|
|
195
|
+
pattern,
|
|
196
|
+
script,
|
|
197
|
+
},
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
if (!updateResponse.ok) {
|
|
201
|
+
return await handleApiError(updateResponse, "updating", "Route", pattern);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return (await updateResponse.json()) as CloudflareRouteResponse;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Delete a route
|
|
209
|
+
*/
|
|
210
|
+
async function deleteRoute(
|
|
211
|
+
api: CloudflareApi,
|
|
212
|
+
zoneId: string,
|
|
213
|
+
routeId: string,
|
|
214
|
+
): Promise<void> {
|
|
215
|
+
const deleteResponse = await api.delete(
|
|
216
|
+
`/zones/${zoneId}/workers/routes/${routeId}`,
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
if (!deleteResponse.ok && deleteResponse.status !== 404) {
|
|
220
|
+
const errorData: any = await deleteResponse.json().catch(() => ({
|
|
221
|
+
errors: [{ message: deleteResponse.statusText }],
|
|
222
|
+
}));
|
|
223
|
+
|
|
224
|
+
throw new CloudflareApiError(
|
|
225
|
+
`Error deleting Route '${routeId}': ${errorData.errors?.[0]?.message || deleteResponse.statusText}`,
|
|
226
|
+
deleteResponse,
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Get a route by ID
|
|
233
|
+
*/
|
|
234
|
+
export async function getRoute(
|
|
235
|
+
api: CloudflareApi,
|
|
236
|
+
zoneId: string,
|
|
237
|
+
routeId: string,
|
|
238
|
+
): Promise<CloudflareRouteResponse> {
|
|
239
|
+
const response = await api.get(`/zones/${zoneId}/workers/routes/${routeId}`);
|
|
240
|
+
|
|
241
|
+
if (!response.ok) {
|
|
242
|
+
throw new CloudflareApiError(
|
|
243
|
+
`Failed to get route ${routeId}: ${response.statusText}`,
|
|
244
|
+
response,
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return (await response.json()) as CloudflareRouteResponse;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* List all routes for a zone
|
|
253
|
+
*/
|
|
254
|
+
export async function listRoutes(
|
|
255
|
+
api: CloudflareApi,
|
|
256
|
+
zoneId: string,
|
|
257
|
+
): Promise<CloudflareRouteResponse> {
|
|
258
|
+
const response = await api.get(`/zones/${zoneId}/workers/routes`);
|
|
259
|
+
|
|
260
|
+
if (!response.ok) {
|
|
261
|
+
throw new CloudflareApiError(
|
|
262
|
+
`Failed to list routes: ${response.statusText}`,
|
|
263
|
+
response,
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return (await response.json()) as CloudflareRouteResponse;
|
|
268
|
+
}
|
package/src/cloudflare/worker.ts
CHANGED
|
@@ -275,6 +275,12 @@ export interface Worker<B extends Bindings = Bindings>
|
|
|
275
275
|
* url: true
|
|
276
276
|
* });
|
|
277
277
|
*
|
|
278
|
+
* await Route("route", {
|
|
279
|
+
* zoneId: zone.zoneId,
|
|
280
|
+
* worker: api,
|
|
281
|
+
* pattern: "api.example.com/*",
|
|
282
|
+
* });
|
|
283
|
+
*
|
|
278
284
|
* @example
|
|
279
285
|
* // Create a real-time chat worker using Durable Objects
|
|
280
286
|
* // for state management:
|
|
@@ -95,7 +95,7 @@ export const WranglerJson = Resource(
|
|
|
95
95
|
|
|
96
96
|
// Process bindings if they exist
|
|
97
97
|
if (worker.bindings) {
|
|
98
|
-
processBindings(spec, worker.bindings, worker.eventSources);
|
|
98
|
+
processBindings(spec, worker.bindings, worker.eventSources, worker.name);
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
// Add environment variables as vars
|
|
@@ -170,6 +170,10 @@ export interface WranglerJsonSpec {
|
|
|
170
170
|
kv_namespaces?: {
|
|
171
171
|
binding: string;
|
|
172
172
|
id: string;
|
|
173
|
+
/**
|
|
174
|
+
* The ID of the KV namespace used during `wrangler dev`
|
|
175
|
+
*/
|
|
176
|
+
preview_id?: string;
|
|
173
177
|
}[];
|
|
174
178
|
|
|
175
179
|
/**
|
|
@@ -190,6 +194,10 @@ export interface WranglerJsonSpec {
|
|
|
190
194
|
r2_buckets?: {
|
|
191
195
|
binding: string;
|
|
192
196
|
bucket_name: string;
|
|
197
|
+
/**
|
|
198
|
+
* The preview name of this R2 bucket at the edge.
|
|
199
|
+
*/
|
|
200
|
+
preview_bucket_name?: string;
|
|
193
201
|
}[];
|
|
194
202
|
|
|
195
203
|
/**
|
|
@@ -246,6 +254,10 @@ export interface WranglerJsonSpec {
|
|
|
246
254
|
database_id: string;
|
|
247
255
|
database_name: string;
|
|
248
256
|
migrations_dir?: string;
|
|
257
|
+
/**
|
|
258
|
+
* The ID of the D1 database used during `wrangler dev`
|
|
259
|
+
*/
|
|
260
|
+
preview_database_id?: string;
|
|
249
261
|
}[];
|
|
250
262
|
|
|
251
263
|
/**
|
|
@@ -256,6 +268,15 @@ export interface WranglerJsonSpec {
|
|
|
256
268
|
binding: string;
|
|
257
269
|
};
|
|
258
270
|
|
|
271
|
+
/**
|
|
272
|
+
* Migrations
|
|
273
|
+
*/
|
|
274
|
+
migrations?: {
|
|
275
|
+
tag: string;
|
|
276
|
+
new_sqlite_classes?: string[];
|
|
277
|
+
new_classes?: string[];
|
|
278
|
+
}[];
|
|
279
|
+
|
|
259
280
|
/**
|
|
260
281
|
* Workflow bindings
|
|
261
282
|
*/
|
|
@@ -279,6 +300,7 @@ function processBindings(
|
|
|
279
300
|
spec: WranglerJsonSpec,
|
|
280
301
|
bindings: Bindings,
|
|
281
302
|
eventSources: EventSource[] | undefined,
|
|
303
|
+
workerName: string,
|
|
282
304
|
): void {
|
|
283
305
|
// Arrays to collect different binding types
|
|
284
306
|
const kvNamespaces: { binding: string; id: string }[] = [];
|
|
@@ -314,6 +336,9 @@ function processBindings(
|
|
|
314
336
|
consumers: [],
|
|
315
337
|
};
|
|
316
338
|
|
|
339
|
+
const new_sqlite_classes: string[] = [];
|
|
340
|
+
const new_classes: string[] = [];
|
|
341
|
+
|
|
317
342
|
const vectorizeIndexes: { binding: string; index_name: string }[] = [];
|
|
318
343
|
|
|
319
344
|
for (const eventSource of eventSources ?? []) {
|
|
@@ -344,7 +369,7 @@ function processBindings(
|
|
|
344
369
|
// Self(service) binding
|
|
345
370
|
services.push({
|
|
346
371
|
binding: bindingName,
|
|
347
|
-
service:
|
|
372
|
+
service: workerName,
|
|
348
373
|
});
|
|
349
374
|
} else if (binding.type === "service") {
|
|
350
375
|
// Service binding
|
|
@@ -370,6 +395,11 @@ function processBindings(
|
|
|
370
395
|
script_name: doBinding.scriptName,
|
|
371
396
|
environment: doBinding.environment,
|
|
372
397
|
});
|
|
398
|
+
if (doBinding.sqlite) {
|
|
399
|
+
new_sqlite_classes.push(doBinding.className);
|
|
400
|
+
} else {
|
|
401
|
+
new_classes.push(doBinding.className);
|
|
402
|
+
}
|
|
373
403
|
} else if (binding.type === "r2_bucket") {
|
|
374
404
|
r2Buckets.push({
|
|
375
405
|
binding: bindingName,
|
|
@@ -453,4 +483,14 @@ function processBindings(
|
|
|
453
483
|
if (vectorizeIndexes.length > 0) {
|
|
454
484
|
spec.vectorize_indexes = vectorizeIndexes;
|
|
455
485
|
}
|
|
486
|
+
|
|
487
|
+
if (new_sqlite_classes.length > 0 || new_classes.length > 0) {
|
|
488
|
+
spec.migrations = [
|
|
489
|
+
{
|
|
490
|
+
tag: "v1",
|
|
491
|
+
new_sqlite_classes,
|
|
492
|
+
new_classes,
|
|
493
|
+
},
|
|
494
|
+
];
|
|
495
|
+
}
|
|
456
496
|
}
|
|
@@ -6,6 +6,14 @@ import { Document } from "../../ai/document.js";
|
|
|
6
6
|
import { alchemy } from "../../alchemy.js";
|
|
7
7
|
import { Folder } from "../../fs/folder.js";
|
|
8
8
|
|
|
9
|
+
function getArg(arg: string) {
|
|
10
|
+
const idx = process.argv.findIndex((a) => a === arg);
|
|
11
|
+
return idx > -1 ? process.argv[idx + 1] : undefined;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const onlyProviderName = getArg("--provider");
|
|
15
|
+
const onlyResourceName = getArg("--resource");
|
|
16
|
+
|
|
9
17
|
export interface DocsProps {
|
|
10
18
|
/**
|
|
11
19
|
* The output directory for the docs.
|
|
@@ -117,7 +125,7 @@ async function generateProviderDocs({
|
|
|
117
125
|
reasoningEffort: "high",
|
|
118
126
|
},
|
|
119
127
|
},
|
|
120
|
-
freeze:
|
|
128
|
+
freeze: onlyProviderName === undefined || onlyProviderName !== providerName,
|
|
121
129
|
temperature: 0.1,
|
|
122
130
|
schema: type({
|
|
123
131
|
groups: type({
|
|
@@ -177,7 +185,8 @@ async function generateProviderDocs({
|
|
|
177
185
|
providerDocsDir,
|
|
178
186
|
`${g.filename.replace(".ts", "").replace(".md", "")}.md`,
|
|
179
187
|
),
|
|
180
|
-
freeze:
|
|
188
|
+
freeze:
|
|
189
|
+
onlyResourceName !== undefined && onlyResourceName !== g.identifier,
|
|
181
190
|
model: {
|
|
182
191
|
id: "claude-3-5-sonnet-latest",
|
|
183
192
|
provider: "anthropic",
|
package/src/scope.ts
CHANGED
|
@@ -47,10 +47,15 @@ export class Scope {
|
|
|
47
47
|
|
|
48
48
|
private isErrored = false;
|
|
49
49
|
|
|
50
|
-
constructor(
|
|
50
|
+
constructor(options: ScopeOptions) {
|
|
51
51
|
this.appName = options.appName;
|
|
52
52
|
this.stage = options?.stage ?? DEFAULT_STAGE;
|
|
53
53
|
this.scopeName = options.scopeName ?? null;
|
|
54
|
+
if (this.scopeName?.includes(":")) {
|
|
55
|
+
throw new Error(
|
|
56
|
+
`Scope name ${this.scopeName} cannot contain double colons`,
|
|
57
|
+
);
|
|
58
|
+
}
|
|
54
59
|
this.parent = options.parent ?? Scope.get();
|
|
55
60
|
this.quiet = options.quiet ?? this.parent?.quiet ?? false;
|
|
56
61
|
if (this.parent && !this.scopeName) {
|