alchemy 0.4.0 → 0.5.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/README.md +3 -463
- package/lib/ai/data.d.ts +5 -0
- package/lib/ai/data.js +3 -0
- package/lib/alchemy.d.ts +3 -3
- package/lib/alchemy.js +82 -85
- package/lib/cloudflare/assets.d.ts +74 -0
- package/lib/cloudflare/assets.js +75 -0
- package/lib/cloudflare/bindings.d.ts +6 -1
- package/lib/cloudflare/bindings.js +6 -0
- package/lib/cloudflare/bound.d.ts +2 -1
- package/lib/cloudflare/custom-domain.d.ts +1 -15
- package/lib/cloudflare/custom-domain.js +1 -15
- package/lib/cloudflare/index.d.ts +1 -1
- package/lib/cloudflare/index.js +1 -1
- package/lib/cloudflare/r2-rest-state-store.js +52 -22
- package/lib/cloudflare/worker.d.ts +18 -0
- package/lib/cloudflare/worker.js +153 -4
- package/lib/internal/docs/providers.js +2 -1
- package/lib/os/exec.d.ts +103 -0
- package/lib/os/exec.js +103 -0
- package/lib/os/index.d.ts +1 -0
- package/lib/os/index.js +1 -0
- package/lib/secret.d.ts +2 -2
- package/lib/secret.js +2 -2
- package/lib/web/vitepress/vitepress.js +1 -1
- package/package.json +2 -1
- package/src/ai/data.ts +10 -0
- package/src/alchemy.ts +97 -100
- package/src/cloudflare/assets.ts +158 -0
- package/src/cloudflare/bindings.ts +11 -2
- package/src/cloudflare/bound.ts +4 -1
- package/src/cloudflare/custom-domain.ts +1 -15
- package/src/cloudflare/index.ts +1 -1
- package/src/cloudflare/r2-rest-state-store.ts +78 -34
- package/src/cloudflare/worker.ts +261 -6
- package/src/internal/docs/providers.ts +2 -1
- package/src/os/exec.ts +190 -0
- package/src/os/index.ts +1 -0
- package/src/secret.ts +2 -2
- package/src/web/vitepress/vitepress.ts +1 -1
- package/lib/cloudflare/generate-asset-manifest.d.ts +0 -2
- package/lib/cloudflare/generate-asset-manifest.js +0 -68
- package/lib/cloudflare/static-site-router.d.ts +0 -18
- package/lib/cloudflare/static-site-router.js +0 -115
- package/lib/cloudflare/static-site.d.ts +0 -211
- package/lib/cloudflare/static-site.js +0 -213
- package/lib/cloudflare/upload-asset-manifest.d.ts +0 -11
- package/lib/cloudflare/upload-asset-manifest.js +0 -56
- package/src/cloudflare/generate-asset-manifest.ts +0 -93
- package/src/cloudflare/static-site-router.ts +0 -157
- package/src/cloudflare/static-site.ts +0 -416
- package/src/cloudflare/upload-asset-manifest.ts +0 -85
- package/src/web/vitepress/index.md +0 -75
package/lib/alchemy.js
CHANGED
|
@@ -10,7 +10,7 @@ export const alchemy = _alchemy;
|
|
|
10
10
|
* Implementation of the alchemy function that handles both application scoping
|
|
11
11
|
* and template string interpolation.
|
|
12
12
|
*/
|
|
13
|
-
function _alchemy(...args) {
|
|
13
|
+
async function _alchemy(...args) {
|
|
14
14
|
if (typeof args[0] === "string") {
|
|
15
15
|
const [appName, options] = args;
|
|
16
16
|
const root = scope(undefined, {
|
|
@@ -19,9 +19,8 @@ function _alchemy(...args) {
|
|
|
19
19
|
stage: options?.stage,
|
|
20
20
|
});
|
|
21
21
|
if (options?.phase === "destroy") {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
});
|
|
22
|
+
await destroy(root);
|
|
23
|
+
return process.exit(0);
|
|
25
24
|
}
|
|
26
25
|
return root;
|
|
27
26
|
}
|
|
@@ -32,92 +31,90 @@ function _alchemy(...args) {
|
|
|
32
31
|
? secondLine.match(/^(\s*)/)?.[1]?.length || 0
|
|
33
32
|
: 0;
|
|
34
33
|
const indent = " ".repeat(leadingSpaces);
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
34
|
+
const { isFileCollection, isFileRef } = await import("./fs");
|
|
35
|
+
const appendices = {};
|
|
36
|
+
const stringValues = await Promise.all(values.map(async function resolve(value) {
|
|
37
|
+
if (typeof value === "string") {
|
|
38
|
+
return indent + value;
|
|
39
|
+
}
|
|
40
|
+
else if (value === null) {
|
|
41
|
+
return "null";
|
|
42
|
+
}
|
|
43
|
+
else if (value === undefined) {
|
|
44
|
+
return "undefined";
|
|
45
|
+
}
|
|
46
|
+
else if (typeof value === "number" ||
|
|
47
|
+
typeof value === "boolean" ||
|
|
48
|
+
typeof value === "bigint") {
|
|
49
|
+
return value.toString();
|
|
50
|
+
}
|
|
51
|
+
else if (value instanceof Promise) {
|
|
52
|
+
return resolve(await value);
|
|
53
|
+
}
|
|
54
|
+
else if (isFileRef(value)) {
|
|
55
|
+
if (!(value.path in appendices)) {
|
|
56
|
+
appendices[value.path] = await fs.readFile(value.path, "utf-8");
|
|
55
57
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
return `[${path.basename(value.path)}](${value.path})`;
|
|
59
|
+
}
|
|
60
|
+
else if (isFileCollection(value)) {
|
|
61
|
+
return Object.entries(value.files)
|
|
62
|
+
.map(([filePath, content]) => {
|
|
63
|
+
appendices[filePath] = content;
|
|
64
|
+
return `[${path.basename(filePath)}](${filePath})`;
|
|
65
|
+
})
|
|
66
|
+
.join("\n\n");
|
|
67
|
+
}
|
|
68
|
+
else if (Array.isArray(value)) {
|
|
69
|
+
return (await Promise.all(value.map(async (value, i) => `${i}. ${await resolve(value)}`))).join("\n");
|
|
70
|
+
}
|
|
71
|
+
else if (typeof value === "object" &&
|
|
72
|
+
typeof value.path === "string") {
|
|
73
|
+
if (typeof value.content === "string") {
|
|
74
|
+
appendices[value.path] = value.content;
|
|
60
75
|
return `[${path.basename(value.path)}](${value.path})`;
|
|
61
76
|
}
|
|
62
|
-
else if (isFileCollection(value)) {
|
|
63
|
-
return Object.entries(value.files)
|
|
64
|
-
.map(([filePath, content]) => {
|
|
65
|
-
appendices[filePath] = content;
|
|
66
|
-
return `[${path.basename(filePath)}](${filePath})`;
|
|
67
|
-
})
|
|
68
|
-
.join("\n\n");
|
|
69
|
-
}
|
|
70
|
-
else if (Array.isArray(value)) {
|
|
71
|
-
return (await Promise.all(value.map(async (value, i) => `${i}. ${await resolve(value)}`))).join("\n");
|
|
72
|
-
}
|
|
73
|
-
else if (typeof value === "object" &&
|
|
74
|
-
typeof value.path === "string") {
|
|
75
|
-
if (typeof value.content === "string") {
|
|
76
|
-
appendices[value.path] = value.content;
|
|
77
|
-
return `[${path.basename(value.path)}](${value.path})`;
|
|
78
|
-
}
|
|
79
|
-
else {
|
|
80
|
-
appendices[value.path] = await fs.readFile(value.path, "utf-8");
|
|
81
|
-
return `[${path.basename(value.path)}](${value.path})`;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
else if (typeof value === "object") {
|
|
85
|
-
return (await Promise.all(Object.entries(value).map(async ([key, value]) => {
|
|
86
|
-
return `* ${key}: ${await resolve(value)}`;
|
|
87
|
-
}))).join("\n");
|
|
88
|
-
}
|
|
89
77
|
else {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
throw new Error(`Unsupported value type: ${value}`);
|
|
78
|
+
appendices[value.path] = await fs.readFile(value.path, "utf-8");
|
|
79
|
+
return `[${path.basename(value.path)}](${value.path})`;
|
|
93
80
|
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
.
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
.
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
81
|
+
}
|
|
82
|
+
else if (typeof value === "object") {
|
|
83
|
+
return (await Promise.all(Object.entries(value).map(async ([key, value]) => {
|
|
84
|
+
return `* ${key}: ${await resolve(value)}`;
|
|
85
|
+
}))).join("\n");
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
// TODO: support other types
|
|
89
|
+
console.log(value);
|
|
90
|
+
throw new Error(`Unsupported value type: ${value}`);
|
|
91
|
+
}
|
|
92
|
+
}));
|
|
93
|
+
// Construct the string template by joining template parts with interpolated values
|
|
94
|
+
const lines = template
|
|
95
|
+
.map((part) => part
|
|
96
|
+
.split("\n")
|
|
97
|
+
.map((line) => line.startsWith(indent) ? line.slice(indent.length) : line)
|
|
98
|
+
.join("\n"))
|
|
99
|
+
.flatMap((part, i) => i < stringValues.length ? [part, stringValues[i] ?? ""] : [part])
|
|
100
|
+
.join("")
|
|
101
|
+
.split("\n");
|
|
102
|
+
// Collect and sort appendices by file path
|
|
103
|
+
return [
|
|
104
|
+
// format the user prompt and trim the first line if it's empty
|
|
105
|
+
lines.length > 1 && lines[0].replaceAll(" ", "").length === 0
|
|
106
|
+
? lines.slice(1).join("\n")
|
|
107
|
+
: lines.join("\n"),
|
|
108
|
+
// sort appendices by path and include at the end of the prompt
|
|
109
|
+
Object.entries(appendices)
|
|
110
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
111
|
+
.map(([filePath, content]) => {
|
|
112
|
+
const extension = path.extname(filePath).slice(1);
|
|
113
|
+
const codeTag = extension ? extension : "";
|
|
114
|
+
return `// ${filePath}\n\`\`\`${codeTag}\n${content}\n\`\`\``;
|
|
115
|
+
})
|
|
116
|
+
.join("\n\n"),
|
|
117
|
+
].join("\n");
|
|
121
118
|
}
|
|
122
119
|
}
|
|
123
120
|
_alchemy.destroy = destroy;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { Context } from "../context";
|
|
2
|
+
import { Resource } from "../resource";
|
|
3
|
+
/**
|
|
4
|
+
* Properties for creating or updating Assets
|
|
5
|
+
*/
|
|
6
|
+
export interface AssetsProps {
|
|
7
|
+
/**
|
|
8
|
+
* Path to a directory containing static assets to be uploaded
|
|
9
|
+
* These files will be served by Cloudflare's Workers runtime
|
|
10
|
+
*/
|
|
11
|
+
path: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Output returned after Assets creation/update
|
|
15
|
+
*/
|
|
16
|
+
export interface Assets extends Resource<"cloudflare::Asset">, AssetsProps {
|
|
17
|
+
/**
|
|
18
|
+
* The type of binding
|
|
19
|
+
*/
|
|
20
|
+
type: "assets";
|
|
21
|
+
/**
|
|
22
|
+
* The ID of the assets bundle
|
|
23
|
+
*/
|
|
24
|
+
id: string;
|
|
25
|
+
/**
|
|
26
|
+
* Asset files that were found
|
|
27
|
+
*/
|
|
28
|
+
files: AssetFile[];
|
|
29
|
+
/**
|
|
30
|
+
* Time at which the assets were created
|
|
31
|
+
*/
|
|
32
|
+
createdAt: number;
|
|
33
|
+
/**
|
|
34
|
+
* Time at which the assets were last updated
|
|
35
|
+
*/
|
|
36
|
+
updatedAt: number;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Represents a single asset file
|
|
40
|
+
*/
|
|
41
|
+
export interface AssetFile {
|
|
42
|
+
/**
|
|
43
|
+
* Path relative to the assets directory
|
|
44
|
+
*/
|
|
45
|
+
path: string;
|
|
46
|
+
/**
|
|
47
|
+
* Full filesystem path to the file
|
|
48
|
+
*/
|
|
49
|
+
filePath: string;
|
|
50
|
+
/**
|
|
51
|
+
* Content type of the file
|
|
52
|
+
*/
|
|
53
|
+
contentType: string;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Cloudflare Assets represent a collection of static files that can be uploaded and served
|
|
57
|
+
* by Cloudflare Workers.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* // Create a basic assets bundle from a local directory
|
|
61
|
+
* const staticAssets = await Assets("static", {
|
|
62
|
+
* path: "./src/assets"
|
|
63
|
+
* });
|
|
64
|
+
*
|
|
65
|
+
* // Use these assets with a worker
|
|
66
|
+
* const worker = await Worker("frontend", {
|
|
67
|
+
* name: "frontend-worker",
|
|
68
|
+
* entrypoint: "./src/worker.ts",
|
|
69
|
+
* bindings: {
|
|
70
|
+
* ASSETS: staticAssets
|
|
71
|
+
* }
|
|
72
|
+
* });
|
|
73
|
+
*/
|
|
74
|
+
export declare const Assets: (((this: any, id: string, props?: {}) => never) & (new (_: never) => never)) | ((this: Context<Assets>, id: string, props: AssetsProps) => Promise<Assets>);
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import * as fs from "fs/promises";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import { Resource } from "../resource";
|
|
4
|
+
import { getContentType } from "../util/content-type";
|
|
5
|
+
/**
|
|
6
|
+
* Cloudflare Assets represent a collection of static files that can be uploaded and served
|
|
7
|
+
* by Cloudflare Workers.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* // Create a basic assets bundle from a local directory
|
|
11
|
+
* const staticAssets = await Assets("static", {
|
|
12
|
+
* path: "./src/assets"
|
|
13
|
+
* });
|
|
14
|
+
*
|
|
15
|
+
* // Use these assets with a worker
|
|
16
|
+
* const worker = await Worker("frontend", {
|
|
17
|
+
* name: "frontend-worker",
|
|
18
|
+
* entrypoint: "./src/worker.ts",
|
|
19
|
+
* bindings: {
|
|
20
|
+
* ASSETS: staticAssets
|
|
21
|
+
* }
|
|
22
|
+
* });
|
|
23
|
+
*/
|
|
24
|
+
export const Assets = Resource("cloudflare::Asset", {
|
|
25
|
+
alwaysUpdate: true,
|
|
26
|
+
}, async function (id, props) {
|
|
27
|
+
if (this.phase === "delete") {
|
|
28
|
+
return this.destroy();
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
// Check if the assets directory exists
|
|
32
|
+
const stats = await fs.stat(props.path);
|
|
33
|
+
if (!stats.isDirectory()) {
|
|
34
|
+
throw new Error(`Assets path ${props.path} is not a directory`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
throw new Error(`Assets directory ${props.path} does not exist or is not accessible`);
|
|
39
|
+
}
|
|
40
|
+
// Recursively get all files in the assets directory
|
|
41
|
+
const filesList = await getFilesRecursively(props.path);
|
|
42
|
+
// Create asset file objects
|
|
43
|
+
const files = filesList.map((filePath) => {
|
|
44
|
+
const relativePath = path.relative(props.path, filePath);
|
|
45
|
+
const normalizedPath = relativePath.split(path.sep).join("/"); // Ensure forward slashes for URLs
|
|
46
|
+
return {
|
|
47
|
+
path: normalizedPath,
|
|
48
|
+
filePath,
|
|
49
|
+
contentType: getContentType(filePath),
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
// Get current timestamp
|
|
53
|
+
const now = Date.now();
|
|
54
|
+
// Construct the output
|
|
55
|
+
return this({
|
|
56
|
+
id,
|
|
57
|
+
type: "assets",
|
|
58
|
+
path: props.path,
|
|
59
|
+
files,
|
|
60
|
+
createdAt: this.output?.createdAt || now,
|
|
61
|
+
updatedAt: now,
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
// Helper functions for file operations
|
|
65
|
+
async function getFilesRecursively(dir) {
|
|
66
|
+
const files = await fs.readdir(dir, { withFileTypes: true });
|
|
67
|
+
const allFiles = await Promise.all(files.map(async (file) => {
|
|
68
|
+
const path = `${dir}/${file.name}`;
|
|
69
|
+
if (file.isDirectory()) {
|
|
70
|
+
return getFilesRecursively(path);
|
|
71
|
+
}
|
|
72
|
+
return path;
|
|
73
|
+
}));
|
|
74
|
+
return allFiles.flat();
|
|
75
|
+
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* https://developers.cloudflare.com/api/resources/workers/subresources/scripts/methods/update/
|
|
5
5
|
*/
|
|
6
6
|
import type { Secret } from "../secret";
|
|
7
|
+
import type { Assets } from "./assets";
|
|
7
8
|
import type { R2Bucket } from "./bucket";
|
|
8
9
|
import type { DurableObjectNamespace } from "./durable-object-namespace";
|
|
9
10
|
import type { KVNamespace } from "./kv-namespace";
|
|
@@ -14,8 +15,12 @@ export type Bindings = {
|
|
|
14
15
|
/**
|
|
15
16
|
* L2 Binding Resources.
|
|
16
17
|
*/
|
|
17
|
-
export type Binding = DurableObjectNamespace | KVNamespace | Worker | R2Bucket | Secret | string;
|
|
18
|
+
export type Binding = DurableObjectNamespace | KVNamespace | Worker | R2Bucket | Secret | string | Assets;
|
|
18
19
|
export declare function isDurableObjectNamespace(binding: Binding): binding is DurableObjectNamespace;
|
|
20
|
+
/**
|
|
21
|
+
* Check if a binding is an Assets resource
|
|
22
|
+
*/
|
|
23
|
+
export declare function isAssets(binding: Binding): binding is Assets;
|
|
19
24
|
/**
|
|
20
25
|
* Union type for all Worker binding types (API spec)
|
|
21
26
|
*/
|
|
@@ -1,3 +1,9 @@
|
|
|
1
1
|
export function isDurableObjectNamespace(binding) {
|
|
2
2
|
return (typeof binding === "object" && binding.type === "durable_object_namespace");
|
|
3
3
|
}
|
|
4
|
+
/**
|
|
5
|
+
* Check if a binding is an Assets resource
|
|
6
|
+
*/
|
|
7
|
+
export function isAssets(binding) {
|
|
8
|
+
return typeof binding === "object" && binding.type === "assets";
|
|
9
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { Secret } from "../secret";
|
|
2
|
+
import type { Assets } from "./assets";
|
|
2
3
|
import type { Binding } from "./bindings";
|
|
3
4
|
import type { R2Bucket as _R2Bucket } from "./bucket";
|
|
4
5
|
import type { DurableObjectNamespace as _DurableObjectNamespace } from "./durable-object-namespace";
|
|
5
6
|
import type { KVNamespace as _KVNamespace } from "./kv-namespace";
|
|
6
7
|
import type { Worker as _Worker } from "./worker";
|
|
7
|
-
export type Bound<T extends Binding> = T extends _DurableObjectNamespace ? DurableObjectNamespace : T extends _KVNamespace ? KVNamespace : T extends _Worker ? Worker : T extends _R2Bucket ? R2Bucket : T extends Secret ? string :
|
|
8
|
+
export type Bound<T extends Binding> = T extends _DurableObjectNamespace ? DurableObjectNamespace : T extends _KVNamespace ? KVNamespace : T extends _Worker ? Worker : T extends _R2Bucket ? R2Bucket : T extends Secret ? string : T extends Assets ? Service : Service;
|
|
@@ -41,8 +41,7 @@ export interface CustomDomain extends Resource<"cloudflare::CustomDomain">, Cust
|
|
|
41
41
|
}
|
|
42
42
|
/**
|
|
43
43
|
* Configure custom domain for a Cloudflare Worker using the Cloudflare Custom Domains API
|
|
44
|
-
* This attaches a worker
|
|
45
|
-
* to a specific hostname within a zone.
|
|
44
|
+
* This attaches a worker to a specific hostname within a zone.
|
|
46
45
|
*
|
|
47
46
|
* @example
|
|
48
47
|
* // Bind a domain to a standard Cloudflare Worker
|
|
@@ -57,19 +56,6 @@ export interface CustomDomain extends Resource<"cloudflare::CustomDomain">, Cust
|
|
|
57
56
|
* workerName: apiWorker.name // Use the name from the Worker resource
|
|
58
57
|
* });
|
|
59
58
|
*
|
|
60
|
-
* @example
|
|
61
|
-
* // Bind a domain to a Cloudflare Static Site
|
|
62
|
-
* const mySite = await StaticSite("landing-page", {
|
|
63
|
-
* name: "my-landing-page-site", // This becomes the underlying worker name
|
|
64
|
-
* dir: "./dist/landing-page"
|
|
65
|
-
* });
|
|
66
|
-
*
|
|
67
|
-
* const siteDomain = await CustomDomain("site-domain-binding", {
|
|
68
|
-
* name: "www.example.com",
|
|
69
|
-
* zoneId: "YOUR_ZONE_ID", // Replace with actual Zone ID
|
|
70
|
-
* workerName: mySite.name // Use the name from the StaticSite resource
|
|
71
|
-
* });
|
|
72
|
-
*
|
|
73
59
|
* @see https://developers.cloudflare.com/api/resources/workers/subresources/domains/
|
|
74
60
|
*/
|
|
75
61
|
export declare const CustomDomain: (((this: any, id: string, props?: {}) => never) & (new (_: never) => never)) | ((this: Context<CustomDomain>, logicalId: string, props: CustomDomainProps) => Promise<CustomDomain>);
|
|
@@ -2,8 +2,7 @@ import { Resource } from "../resource";
|
|
|
2
2
|
import { CloudflareApi, createCloudflareApi, handleApiError } from "./api";
|
|
3
3
|
/**
|
|
4
4
|
* Configure custom domain for a Cloudflare Worker using the Cloudflare Custom Domains API
|
|
5
|
-
* This attaches a worker
|
|
6
|
-
* to a specific hostname within a zone.
|
|
5
|
+
* This attaches a worker to a specific hostname within a zone.
|
|
7
6
|
*
|
|
8
7
|
* @example
|
|
9
8
|
* // Bind a domain to a standard Cloudflare Worker
|
|
@@ -18,19 +17,6 @@ import { CloudflareApi, createCloudflareApi, handleApiError } from "./api";
|
|
|
18
17
|
* workerName: apiWorker.name // Use the name from the Worker resource
|
|
19
18
|
* });
|
|
20
19
|
*
|
|
21
|
-
* @example
|
|
22
|
-
* // Bind a domain to a Cloudflare Static Site
|
|
23
|
-
* const mySite = await StaticSite("landing-page", {
|
|
24
|
-
* name: "my-landing-page-site", // This becomes the underlying worker name
|
|
25
|
-
* dir: "./dist/landing-page"
|
|
26
|
-
* });
|
|
27
|
-
*
|
|
28
|
-
* const siteDomain = await CustomDomain("site-domain-binding", {
|
|
29
|
-
* name: "www.example.com",
|
|
30
|
-
* zoneId: "YOUR_ZONE_ID", // Replace with actual Zone ID
|
|
31
|
-
* workerName: mySite.name // Use the name from the StaticSite resource
|
|
32
|
-
* });
|
|
33
|
-
*
|
|
34
20
|
* @see https://developers.cloudflare.com/api/resources/workers/subresources/domains/
|
|
35
21
|
*/
|
|
36
22
|
export const CustomDomain = Resource("cloudflare::CustomDomain", async function (logicalId, // Changed param name from id to logicalId for clarity
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export * from "./account-api-token";
|
|
2
2
|
export * from "./api";
|
|
3
|
+
export * from "./assets";
|
|
3
4
|
export * from "./bindings";
|
|
4
5
|
export * from "./bucket";
|
|
5
6
|
export * from "./custom-domain";
|
|
@@ -8,7 +9,6 @@ export * from "./durable-object-namespace";
|
|
|
8
9
|
export * from "./kv-namespace";
|
|
9
10
|
export * from "./permission-groups";
|
|
10
11
|
export * from "./r2-rest-state-store";
|
|
11
|
-
export * from "./static-site";
|
|
12
12
|
export * from "./worker";
|
|
13
13
|
export * from "./wrangler.json";
|
|
14
14
|
export * from "./zone";
|
package/lib/cloudflare/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export * from "./account-api-token";
|
|
2
2
|
export * from "./api";
|
|
3
|
+
export * from "./assets";
|
|
3
4
|
export * from "./bindings";
|
|
4
5
|
export * from "./bucket";
|
|
5
6
|
export * from "./custom-domain";
|
|
@@ -8,7 +9,6 @@ export * from "./durable-object-namespace";
|
|
|
8
9
|
export * from "./kv-namespace";
|
|
9
10
|
export * from "./permission-groups";
|
|
10
11
|
export * from "./r2-rest-state-store";
|
|
11
|
-
export * from "./static-site";
|
|
12
12
|
export * from "./worker";
|
|
13
13
|
export * from "./wrangler.json";
|
|
14
14
|
export * from "./zone";
|
|
@@ -74,13 +74,22 @@ export class R2RestStateStore {
|
|
|
74
74
|
params.append("cursor", cursor);
|
|
75
75
|
}
|
|
76
76
|
const listPath = `/accounts/${this.api.accountId}/r2/buckets/${this.bucketName}/objects?${params.toString()}`;
|
|
77
|
-
const response = await
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
77
|
+
const response = await withExponentialBackoff(async () => {
|
|
78
|
+
const response = await this.api.get(listPath);
|
|
79
|
+
if (!response.ok) {
|
|
80
|
+
const errorData = await response.json().catch(() => ({
|
|
81
|
+
errors: [{ message: response.statusText }],
|
|
82
|
+
}));
|
|
83
|
+
throw new Error(`Error listing R2 objects: ${errorData.errors?.[0]?.message || response.statusText}`);
|
|
84
|
+
}
|
|
85
|
+
return response;
|
|
86
|
+
},
|
|
87
|
+
// Retry on transient errors
|
|
88
|
+
(error) => error.message?.includes("502") ||
|
|
89
|
+
error.message?.includes("503") ||
|
|
90
|
+
error.message?.includes("timeout"), 5, // 5 retry attempts
|
|
91
|
+
1000 // Start with 1 second delay
|
|
92
|
+
);
|
|
84
93
|
const data = (await response.json());
|
|
85
94
|
// The result structure may be under "result" key in Cloudflare's API
|
|
86
95
|
const result = data.result || data;
|
|
@@ -114,15 +123,27 @@ export class R2RestStateStore {
|
|
|
114
123
|
async get(key) {
|
|
115
124
|
await this.ensureInitialized();
|
|
116
125
|
try {
|
|
117
|
-
const response = await
|
|
118
|
-
|
|
119
|
-
if (response.
|
|
120
|
-
|
|
126
|
+
const response = await withExponentialBackoff(async () => {
|
|
127
|
+
const response = await this.api.get(`/accounts/${this.api.accountId}/r2/buckets/${this.bucketName}/objects/${this.getObjectKey(key)}`);
|
|
128
|
+
if (!response.ok) {
|
|
129
|
+
if (response.status === 404) {
|
|
130
|
+
return response;
|
|
131
|
+
}
|
|
132
|
+
const errorData = await response.json().catch(() => ({
|
|
133
|
+
errors: [{ message: response.statusText }],
|
|
134
|
+
}));
|
|
135
|
+
throw new Error(`Error getting R2 object: ${errorData.errors?.[0]?.message || response.statusText}`);
|
|
121
136
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
137
|
+
return response;
|
|
138
|
+
},
|
|
139
|
+
// Retry on transient errors
|
|
140
|
+
(error) => error.message?.includes("502") ||
|
|
141
|
+
error.message?.includes("503") ||
|
|
142
|
+
error.message?.includes("timeout"), 5, // 5 retry attempts
|
|
143
|
+
1000 // Start with 1 second delay
|
|
144
|
+
);
|
|
145
|
+
if (response.status === 404) {
|
|
146
|
+
return undefined;
|
|
126
147
|
}
|
|
127
148
|
// Parse and deserialize the state data
|
|
128
149
|
const rawData = await response.json();
|
|
@@ -208,13 +229,22 @@ export class R2RestStateStore {
|
|
|
208
229
|
*/
|
|
209
230
|
async delete(key) {
|
|
210
231
|
await this.ensureInitialized();
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
232
|
+
await withExponentialBackoff(async () => {
|
|
233
|
+
const response = await this.api.delete(`/accounts/${this.api.accountId}/r2/buckets/${this.bucketName}/objects/${this.getObjectKey(key)}`);
|
|
234
|
+
if (!response.ok && response.status !== 404) {
|
|
235
|
+
const errorData = await response.json().catch(() => ({
|
|
236
|
+
errors: [{ message: response.statusText }],
|
|
237
|
+
}));
|
|
238
|
+
throw new Error(`Error deleting from R2: ${errorData.errors?.[0]?.message || response.statusText}`);
|
|
239
|
+
}
|
|
240
|
+
return response;
|
|
241
|
+
},
|
|
242
|
+
// Retry on transient errors
|
|
243
|
+
(error) => error.message?.includes("502") ||
|
|
244
|
+
error.message?.includes("503") ||
|
|
245
|
+
error.message?.includes("timeout"), 5, // 5 retry attempts
|
|
246
|
+
1000 // Start with 1 second delay
|
|
247
|
+
);
|
|
218
248
|
}
|
|
219
249
|
/**
|
|
220
250
|
* Convert key for storage by replacing slashes with colons
|
|
@@ -74,6 +74,10 @@ export interface WorkerProps<B extends Bindings = Bindings> {
|
|
|
74
74
|
* Migrations to apply to the worker
|
|
75
75
|
*/
|
|
76
76
|
migrations?: SingleStepMigration;
|
|
77
|
+
/**
|
|
78
|
+
* Whether to adopt the Worker if it already exists when creating
|
|
79
|
+
*/
|
|
80
|
+
adopt?: boolean;
|
|
77
81
|
}
|
|
78
82
|
/**
|
|
79
83
|
* Output returned after Worker creation/update
|
|
@@ -166,6 +170,20 @@ export interface Worker<B extends Bindings = Bindings> extends Resource<"cloudfl
|
|
|
166
170
|
* }
|
|
167
171
|
* });
|
|
168
172
|
*
|
|
173
|
+
* @example
|
|
174
|
+
* // Create a worker with static assets:
|
|
175
|
+
* const staticAssets = await Assets("static", {
|
|
176
|
+
* path: "./src/assets"
|
|
177
|
+
* });
|
|
178
|
+
*
|
|
179
|
+
* const frontendWorker = await Worker("frontend", {
|
|
180
|
+
* name: "frontend-worker",
|
|
181
|
+
* entrypoint: "./src/worker.ts",
|
|
182
|
+
* bindings: {
|
|
183
|
+
* ASSETS: staticAssets
|
|
184
|
+
* }
|
|
185
|
+
* });
|
|
186
|
+
*
|
|
169
187
|
* @see https://developers.cloudflare.com/workers/
|
|
170
188
|
*/
|
|
171
189
|
export declare const Worker: (((this: any, id: string, props?: {}) => never) & (new (_: never) => never)) | (<const B extends Bindings>(this: Context<Worker<NoInfer<B>>>, id: string, props: WorkerProps<B>) => Promise<Worker<B>>);
|