alchemy 0.1.16 → 0.1.18

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 (250) hide show
  1. package/lib/$.d.ts +12 -0
  2. package/lib/$.d.ts.map +1 -0
  3. package/lib/$.js +42 -0
  4. package/lib/$.js.map +1 -0
  5. package/lib/alchemize.d.ts +47 -0
  6. package/lib/alchemize.d.ts.map +1 -0
  7. package/lib/alchemize.js +116 -0
  8. package/lib/alchemize.js.map +1 -0
  9. package/lib/apply.d.ts +23 -0
  10. package/lib/apply.d.ts.map +1 -1
  11. package/lib/apply.js +4 -0
  12. package/lib/apply.js.map +1 -1
  13. package/lib/aws/account-id.d.ts +5 -0
  14. package/lib/aws/account-id.d.ts.map +1 -0
  15. package/lib/aws/account-id.js +10 -0
  16. package/lib/aws/account-id.js.map +1 -0
  17. package/lib/aws/bucket.d.ts +21 -0
  18. package/lib/aws/bucket.d.ts.map +1 -0
  19. package/lib/aws/bucket.js +114 -0
  20. package/lib/aws/bucket.js.map +1 -0
  21. package/lib/aws/function.d.ts +53 -0
  22. package/lib/aws/function.d.ts.map +1 -0
  23. package/lib/aws/function.js +195 -0
  24. package/lib/aws/function.js.map +1 -0
  25. package/lib/aws/index.d.ts +9 -0
  26. package/lib/aws/index.d.ts.map +1 -0
  27. package/lib/aws/index.js +9 -0
  28. package/lib/aws/index.js.map +1 -0
  29. package/lib/aws/oidc/github-oidc-provider.d.ts +11 -0
  30. package/lib/aws/oidc/github-oidc-provider.d.ts.map +1 -0
  31. package/lib/aws/oidc/github-oidc-provider.js +22 -0
  32. package/lib/aws/oidc/github-oidc-provider.js.map +1 -0
  33. package/lib/aws/oidc/index.d.ts +3 -0
  34. package/lib/aws/oidc/index.d.ts.map +1 -0
  35. package/lib/aws/oidc/index.js +3 -0
  36. package/lib/aws/oidc/index.js.map +1 -0
  37. package/lib/aws/oidc/oidc-provider.d.ts +60 -0
  38. package/lib/aws/oidc/oidc-provider.d.ts.map +1 -0
  39. package/lib/aws/oidc/oidc-provider.js +140 -0
  40. package/lib/aws/oidc/oidc-provider.js.map +1 -0
  41. package/lib/aws/policy.d.ts +46 -0
  42. package/lib/aws/policy.d.ts.map +1 -0
  43. package/lib/aws/policy.js +139 -0
  44. package/lib/aws/policy.js.map +1 -0
  45. package/lib/aws/queue.d.ts +62 -0
  46. package/lib/aws/queue.d.ts.map +1 -0
  47. package/lib/aws/queue.js +167 -0
  48. package/lib/aws/queue.js.map +1 -0
  49. package/lib/aws/role.d.ts +27 -0
  50. package/lib/aws/role.d.ts.map +1 -0
  51. package/lib/aws/role.js +197 -0
  52. package/lib/aws/role.js.map +1 -0
  53. package/lib/aws/ses.d.ts +75 -0
  54. package/lib/aws/ses.d.ts.map +1 -0
  55. package/lib/aws/ses.js +189 -0
  56. package/lib/aws/ses.js.map +1 -0
  57. package/lib/aws/table.d.ts +26 -0
  58. package/lib/aws/table.d.ts.map +1 -0
  59. package/lib/aws/table.js +154 -0
  60. package/lib/aws/table.js.map +1 -0
  61. package/lib/cloudflare/api.d.ts +88 -0
  62. package/lib/cloudflare/api.d.ts.map +1 -0
  63. package/lib/cloudflare/api.js +184 -0
  64. package/lib/cloudflare/api.js.map +1 -0
  65. package/lib/cloudflare/asset-manifest.d.ts +15 -0
  66. package/lib/cloudflare/asset-manifest.d.ts.map +1 -0
  67. package/lib/cloudflare/asset-manifest.js +2 -0
  68. package/lib/cloudflare/asset-manifest.js.map +1 -0
  69. package/lib/cloudflare/auth.d.ts +52 -0
  70. package/lib/cloudflare/auth.d.ts.map +1 -0
  71. package/lib/cloudflare/auth.js +114 -0
  72. package/lib/cloudflare/auth.js.map +1 -0
  73. package/lib/cloudflare/bindings.d.ts +261 -0
  74. package/lib/cloudflare/bindings.d.ts.map +1 -0
  75. package/lib/cloudflare/bindings.js +2 -0
  76. package/lib/cloudflare/bindings.js.map +1 -0
  77. package/lib/cloudflare/bound.d.ts +8 -0
  78. package/lib/cloudflare/bound.d.ts.map +1 -0
  79. package/lib/cloudflare/bound.js +2 -0
  80. package/lib/cloudflare/bound.js.map +1 -0
  81. package/lib/cloudflare/bucket.d.ts +54 -0
  82. package/lib/cloudflare/bucket.d.ts.map +1 -0
  83. package/lib/cloudflare/bucket.js +120 -0
  84. package/lib/cloudflare/bucket.js.map +1 -0
  85. package/lib/cloudflare/durable-object-namespace.d.ts +20 -0
  86. package/lib/cloudflare/durable-object-namespace.d.ts.map +1 -0
  87. package/lib/cloudflare/durable-object-namespace.js +21 -0
  88. package/lib/cloudflare/durable-object-namespace.js.map +1 -0
  89. package/lib/cloudflare/generate-asset-manifest.d.ts +3 -0
  90. package/lib/cloudflare/generate-asset-manifest.d.ts.map +1 -0
  91. package/lib/cloudflare/generate-asset-manifest.js +69 -0
  92. package/lib/cloudflare/generate-asset-manifest.js.map +1 -0
  93. package/lib/cloudflare/index.d.ts +8 -0
  94. package/lib/cloudflare/index.d.ts.map +1 -0
  95. package/lib/cloudflare/index.js +8 -0
  96. package/lib/cloudflare/index.js.map +1 -0
  97. package/lib/cloudflare/kv-namespace.d.ts +64 -0
  98. package/lib/cloudflare/kv-namespace.d.ts.map +1 -0
  99. package/lib/cloudflare/kv-namespace.js +114 -0
  100. package/lib/cloudflare/kv-namespace.js.map +1 -0
  101. package/lib/cloudflare/state.d.ts +83 -0
  102. package/lib/cloudflare/state.d.ts.map +1 -0
  103. package/lib/cloudflare/state.js +109 -0
  104. package/lib/cloudflare/state.js.map +1 -0
  105. package/lib/cloudflare/static-site-router.d.ts +19 -0
  106. package/lib/cloudflare/static-site-router.d.ts.map +1 -0
  107. package/lib/cloudflare/static-site-router.js +110 -0
  108. package/lib/cloudflare/static-site-router.js.map +1 -0
  109. package/lib/cloudflare/static-site.d.ts +166 -0
  110. package/lib/cloudflare/static-site.d.ts.map +1 -0
  111. package/lib/cloudflare/static-site.js +161 -0
  112. package/lib/cloudflare/static-site.js.map +1 -0
  113. package/lib/cloudflare/types.d.ts +52 -0
  114. package/lib/cloudflare/types.d.ts.map +1 -0
  115. package/lib/cloudflare/types.js +21 -0
  116. package/lib/cloudflare/types.js.map +1 -0
  117. package/lib/cloudflare/upload-asset-manifest.d.ts +12 -0
  118. package/lib/cloudflare/upload-asset-manifest.d.ts.map +1 -0
  119. package/lib/cloudflare/upload-asset-manifest.js +55 -0
  120. package/lib/cloudflare/upload-asset-manifest.js.map +1 -0
  121. package/lib/cloudflare/worker-metadata.d.ts +130 -0
  122. package/lib/cloudflare/worker-metadata.d.ts.map +1 -0
  123. package/lib/cloudflare/worker-metadata.js +2 -0
  124. package/lib/cloudflare/worker-metadata.js.map +1 -0
  125. package/lib/cloudflare/worker-migration.d.ts +110 -0
  126. package/lib/cloudflare/worker-migration.d.ts.map +1 -0
  127. package/lib/cloudflare/worker-migration.js +11 -0
  128. package/lib/cloudflare/worker-migration.js.map +1 -0
  129. package/lib/cloudflare/worker.d.ts +114 -0
  130. package/lib/cloudflare/worker.d.ts.map +1 -0
  131. package/lib/cloudflare/worker.js +419 -0
  132. package/lib/cloudflare/worker.js.map +1 -0
  133. package/lib/destroy.d.ts +19 -0
  134. package/lib/encrypt.d.ts +17 -0
  135. package/lib/encrypt.d.ts.map +1 -0
  136. package/lib/encrypt.js +45 -0
  137. package/lib/encrypt.js.map +1 -0
  138. package/lib/error.d.ts +2 -0
  139. package/lib/error.d.ts.map +1 -0
  140. package/lib/error.js +13 -0
  141. package/lib/error.js.map +1 -0
  142. package/lib/esbuild.d.ts +152 -0
  143. package/lib/esbuild.d.ts.map +1 -0
  144. package/lib/esbuild.js +67 -0
  145. package/lib/esbuild.js.map +1 -0
  146. package/lib/fs.d.ts +12 -0
  147. package/lib/fs.d.ts.map +1 -0
  148. package/lib/fs.js +41 -0
  149. package/lib/fs.js.map +1 -0
  150. package/lib/github/client.d.ts +34 -0
  151. package/lib/github/client.d.ts.map +1 -0
  152. package/lib/github/client.js +91 -0
  153. package/lib/github/client.js.map +1 -0
  154. package/lib/github/index.d.ts +2 -0
  155. package/lib/github/index.d.ts.map +1 -0
  156. package/lib/github/index.js +2 -0
  157. package/lib/github/index.js.map +1 -0
  158. package/lib/github/secret.d.ts +47 -0
  159. package/lib/github/secret.d.ts.map +1 -0
  160. package/lib/github/secret.js +99 -0
  161. package/lib/github/secret.js.map +1 -0
  162. package/lib/global.d.ts +16 -0
  163. package/lib/global.d.ts.map +1 -0
  164. package/lib/global.js +42 -0
  165. package/lib/global.js.map +1 -0
  166. package/lib/index.d.ts +11 -0
  167. package/lib/index.d.ts.map +1 -0
  168. package/lib/index.js +10 -0
  169. package/lib/index.js.map +1 -0
  170. package/lib/input.d.ts +7 -0
  171. package/lib/input.d.ts.map +1 -0
  172. package/lib/input.js +2 -0
  173. package/lib/input.js.map +1 -0
  174. package/lib/main.d.ts +3 -0
  175. package/lib/main.d.ts.map +1 -0
  176. package/lib/main.js +32 -0
  177. package/lib/main.js.map +1 -0
  178. package/lib/output.d.ts +25 -0
  179. package/lib/output.d.ts.map +1 -0
  180. package/lib/output.js +23 -0
  181. package/lib/output.js.map +1 -0
  182. package/lib/print.d.ts +7 -0
  183. package/lib/print.d.ts.map +1 -0
  184. package/lib/print.js +10 -0
  185. package/lib/print.js.map +1 -0
  186. package/lib/resource.d.ts +71 -0
  187. package/lib/resource.d.ts.map +1 -0
  188. package/lib/resource.js +276 -0
  189. package/lib/resource.js.map +1 -0
  190. package/lib/scope.d.ts +18 -0
  191. package/lib/scope.d.ts.map +1 -0
  192. package/lib/scope.js +30 -0
  193. package/lib/scope.js.map +1 -0
  194. package/lib/secret.d.ts +12 -0
  195. package/lib/secret.d.ts.map +1 -0
  196. package/lib/secret.js +30 -0
  197. package/lib/secret.js.map +1 -0
  198. package/lib/serde.d.ts +3 -0
  199. package/lib/serde.d.ts.map +1 -0
  200. package/lib/serde.js +37 -0
  201. package/lib/serde.js.map +1 -0
  202. package/lib/slug.d.ts +2 -0
  203. package/lib/slug.d.ts.map +1 -0
  204. package/lib/slug.js +4 -0
  205. package/lib/slug.js.map +1 -0
  206. package/lib/state.d.ts +40 -0
  207. package/lib/state.d.ts.map +1 -0
  208. package/lib/state.js +74 -0
  209. package/lib/state.js.map +1 -0
  210. package/lib/stripe/index.d.ts +4 -0
  211. package/lib/stripe/index.d.ts.map +1 -0
  212. package/lib/stripe/index.js +4 -0
  213. package/lib/stripe/index.js.map +1 -0
  214. package/lib/stripe/price.d.ts +109 -0
  215. package/lib/stripe/price.d.ts.map +1 -0
  216. package/lib/stripe/price.js +112 -0
  217. package/lib/stripe/price.js.map +1 -0
  218. package/lib/stripe/product.d.ts +86 -0
  219. package/lib/stripe/product.d.ts.map +1 -0
  220. package/lib/stripe/product.js +90 -0
  221. package/lib/stripe/product.js.map +1 -0
  222. package/lib/stripe/webhook.d.ts +73 -0
  223. package/lib/stripe/webhook.d.ts.map +1 -0
  224. package/lib/stripe/webhook.js +94 -0
  225. package/lib/stripe/webhook.js.map +1 -0
  226. package/lib/utils/content-type.d.ts +8 -0
  227. package/lib/utils/content-type.d.ts.map +1 -0
  228. package/lib/utils/content-type.js +41 -0
  229. package/lib/utils/content-type.js.map +1 -0
  230. package/lib/utils/retry.d.ts +13 -0
  231. package/lib/utils/retry.d.ts.map +1 -0
  232. package/lib/utils/retry.js +32 -0
  233. package/lib/utils/retry.js.map +1 -0
  234. package/package.json +3 -6
  235. package/src/alchemize.ts +9 -0
  236. package/src/apply.ts +3 -0
  237. package/src/cloudflare/bindings.ts +9 -1
  238. package/src/cloudflare/bound.ts +18 -0
  239. package/src/cloudflare/bucket.ts +240 -0
  240. package/src/cloudflare/index.ts +1 -0
  241. package/src/cloudflare/static-site.ts +14 -2
  242. package/src/cloudflare/worker.ts +84 -59
  243. package/src/encrypt.ts +79 -0
  244. package/src/github/secret.ts +34 -33
  245. package/src/index.ts +1 -0
  246. package/src/input.ts +13 -10
  247. package/src/output.ts +13 -9
  248. package/src/secret.ts +41 -0
  249. package/src/serde.ts +46 -0
  250. package/src/state.ts +3 -2
@@ -0,0 +1,240 @@
1
+ import type { Resolved } from "../output";
2
+ import { type Context, Resource } from "../resource";
3
+ import { createCloudflareApi } from "./api";
4
+
5
+ /**
6
+ * Properties for creating or updating an R2 Bucket
7
+ */
8
+ export interface BucketProps {
9
+ /**
10
+ * Name of the bucket
11
+ * Names can only contain lowercase letters (a-z), numbers (0-9), and hyphens (-)
12
+ * Cannot begin or end with a hyphen
13
+ */
14
+ name?: string;
15
+
16
+ /**
17
+ * Optional location hint for the bucket
18
+ * Indicates the primary geographical location data will be accessed from
19
+ */
20
+ locationHint?: string;
21
+
22
+ /**
23
+ * Optional jurisdiction for the bucket
24
+ * Determines the regulatory jurisdiction the bucket data falls under
25
+ */
26
+ jurisdiction?: "default" | "eu" | "fedramp";
27
+
28
+ /**
29
+ * Whether to allow public access through the r2.dev subdomain
30
+ * Only for development purposes - use custom domains for production
31
+ */
32
+ allowPublicAccess?: boolean;
33
+ }
34
+
35
+ /**
36
+ * Output returned after R2 Bucket creation/update
37
+ */
38
+ export interface BucketOutput extends BucketProps {
39
+ /**
40
+ * The ID of the bucket (same as name)
41
+ */
42
+ id: string;
43
+
44
+ /**
45
+ * Location of the bucket
46
+ */
47
+ location: string;
48
+
49
+ /**
50
+ * Time at which the bucket was created (Unix timestamp in seconds)
51
+ */
52
+ creationDate: number;
53
+
54
+ /**
55
+ * Resource type identifier
56
+ */
57
+ type: "r2_bucket";
58
+ }
59
+
60
+ /**
61
+ * Cloudflare R2 bucket API response
62
+ */
63
+ interface CloudflareBucketResponse {
64
+ result: {
65
+ name: string;
66
+ location?: string;
67
+ creation_date: string;
68
+ };
69
+ success: boolean;
70
+ errors: Array<{ code: number; message: string }>;
71
+ messages: string[];
72
+ }
73
+
74
+ export function isBucket(resource: any): resource is Resolved<R2Bucket> {
75
+ return (
76
+ resource && typeof resource === "object" && resource.id && resource.location
77
+ );
78
+ }
79
+
80
+ export class R2Bucket extends Resource(
81
+ "cloudflare::R2Bucket",
82
+ async (ctx: Context<BucketOutput>, props: BucketProps) => {
83
+ // Create Cloudflare API client with automatic account discovery
84
+ const api = await createCloudflareApi();
85
+
86
+ // Resource ID defaults to bucket name if provided
87
+ const bucketName = props.name || ctx.resourceID;
88
+
89
+ if (ctx.event === "delete") {
90
+ if (ctx.output?.id) {
91
+ // Delete R2 bucket
92
+ const headers: Record<string, string> = {};
93
+ if (props.jurisdiction && props.jurisdiction !== "default") {
94
+ headers["cf-r2-jurisdiction"] = props.jurisdiction;
95
+ }
96
+
97
+ const deleteResponse = await api.delete(
98
+ `/accounts/${api.accountId}/r2/buckets/${ctx.output.id}`,
99
+ { headers },
100
+ );
101
+
102
+ if (!deleteResponse.ok && deleteResponse.status !== 404) {
103
+ const errorData: any = await deleteResponse.json().catch(() => ({
104
+ errors: [{ message: deleteResponse.statusText }],
105
+ }));
106
+ throw new Error(
107
+ `Error deleting R2 bucket '${bucketName}': ${errorData.errors?.[0]?.message || deleteResponse.statusText}`,
108
+ );
109
+ }
110
+ }
111
+
112
+ // Return void (a deleted bucket has no content)
113
+ return;
114
+ } else {
115
+ try {
116
+ let bucketOutput: BucketOutput;
117
+
118
+ if (ctx.event === "update" && ctx.output?.id) {
119
+ // Get bucket details to verify it exists
120
+ const headers: Record<string, string> = {};
121
+ if (props.jurisdiction && props.jurisdiction !== "default") {
122
+ headers["cf-r2-jurisdiction"] = props.jurisdiction;
123
+ }
124
+
125
+ const getResponse = await api.get(
126
+ `/accounts/${api.accountId}/r2/buckets/${bucketName}`,
127
+ { headers },
128
+ );
129
+
130
+ if (!getResponse.ok) {
131
+ const errorData: any = await getResponse.json().catch(() => ({
132
+ errors: [{ message: getResponse.statusText }],
133
+ }));
134
+ throw new Error(
135
+ `Error getting R2 bucket '${bucketName}': ${errorData.errors?.[0]?.message || getResponse.statusText}`,
136
+ );
137
+ }
138
+
139
+ const result = (await getResponse.json()) as CloudflareBucketResponse;
140
+ bucketOutput = {
141
+ id: result.result.name,
142
+ name: result.result.name,
143
+ location: result.result.location || "default",
144
+ creationDate: Math.floor(
145
+ new Date(result.result.creation_date).getTime() / 1000,
146
+ ),
147
+ jurisdiction: props.jurisdiction || "default",
148
+ type: "r2_bucket",
149
+ };
150
+
151
+ // Update public access setting if it has changed
152
+ if (props.allowPublicAccess !== undefined) {
153
+ await updatePublicAccess(api, bucketName, props.allowPublicAccess);
154
+ }
155
+ } else {
156
+ // Create new R2 bucket
157
+ const createPayload: any = {
158
+ name: bucketName,
159
+ };
160
+
161
+ if (props.locationHint) {
162
+ createPayload.location_hint = props.locationHint;
163
+ }
164
+
165
+ const headers: Record<string, string> = {};
166
+ if (props.jurisdiction && props.jurisdiction !== "default") {
167
+ headers["cf-r2-jurisdiction"] = props.jurisdiction;
168
+ }
169
+
170
+ const createResponse = await api.post(
171
+ `/accounts/${api.accountId}/r2/buckets`,
172
+ createPayload,
173
+ { headers },
174
+ );
175
+
176
+ if (!createResponse.ok) {
177
+ const errorData: any = await createResponse.json().catch(() => ({
178
+ errors: [{ message: createResponse.statusText }],
179
+ }));
180
+ throw new Error(
181
+ `Error creating R2 bucket '${bucketName}': ${errorData.errors?.[0]?.message || createResponse.statusText}`,
182
+ );
183
+ }
184
+
185
+ const result =
186
+ (await createResponse.json()) as CloudflareBucketResponse;
187
+ bucketOutput = {
188
+ id: result.result.name,
189
+ name: result.result.name,
190
+ location: result.result.location || "default",
191
+ creationDate: Math.floor(
192
+ new Date(result.result.creation_date).getTime() / 1000,
193
+ ),
194
+ jurisdiction: props.jurisdiction || "default",
195
+ type: "r2_bucket",
196
+ };
197
+
198
+ // Set public access if requested
199
+ if (props.allowPublicAccess) {
200
+ await updatePublicAccess(api, bucketName, true);
201
+ }
202
+ }
203
+
204
+ return bucketOutput;
205
+ } catch (error) {
206
+ console.error("Error creating/updating R2 bucket:", error);
207
+ throw error;
208
+ }
209
+ }
210
+ },
211
+ ) {}
212
+
213
+ /**
214
+ * Helper function to update the public access setting for a bucket
215
+ */
216
+ async function updatePublicAccess(
217
+ api: any,
218
+ bucketName: string,
219
+ allowPublicAccess: boolean,
220
+ ): Promise<void> {
221
+ try {
222
+ const response = await api.put(
223
+ `/accounts/${api.accountId}/r2/buckets/${bucketName}/domains/managed`,
224
+ {
225
+ enabled: allowPublicAccess,
226
+ },
227
+ );
228
+
229
+ if (!response.ok) {
230
+ const errorData: any = await response.json().catch(() => ({
231
+ errors: [{ message: response.statusText }],
232
+ }));
233
+ throw new Error(
234
+ `Error updating R2 bucket public access: ${errorData.errors?.[0]?.message || response.statusText}`,
235
+ );
236
+ }
237
+ } catch (error) {
238
+ console.warn("Could not set public access:", error);
239
+ }
240
+ }
@@ -1,4 +1,5 @@
1
1
  export * from "./bindings";
2
+ export * from "./bucket";
2
3
  export * from "./durable-object-namespace";
3
4
  export * from "./kv-namespace";
4
5
  export * from "./state";
@@ -323,6 +323,18 @@ export class StaticSite extends Resource(
323
323
  },
324
324
  },
325
325
  };
326
+
327
+ // Determine the entrypoint file - check for .ts first, fallback to .js
328
+ let entrypointFile = "static-site-router.js";
329
+ try {
330
+ const tsFile = path.resolve(__dirname, "static-site-router.ts");
331
+ await fs.access(tsFile);
332
+ // If we reach here, the TypeScript file exists
333
+ entrypointFile = "static-site-router.ts";
334
+ } catch (error) {
335
+ // TypeScript file doesn't exist, use JavaScript (default)
336
+ }
337
+
326
338
  const worker = new Worker(
327
339
  "worker",
328
340
  {
@@ -333,7 +345,7 @@ export class StaticSite extends Resource(
333
345
  ...(props.workerScript
334
346
  ? { script: props.workerScript }
335
347
  : {
336
- entrypoint: path.resolve(__dirname, "static-site-router.ts"),
348
+ entrypoint: path.resolve(__dirname, entrypointFile),
337
349
  bundle: bundleOptions,
338
350
  }),
339
351
  bindings,
@@ -367,7 +379,7 @@ export class StaticSite extends Resource(
367
379
  updatedAt: now,
368
380
  domain: props.domain,
369
381
  production: props.production !== false,
370
- url: worker.url,
382
+ url: worker.url as Input<string | undefined>,
371
383
  routes,
372
384
  };
373
385
 
@@ -1,11 +1,15 @@
1
1
  import * as fs from "fs/promises";
2
2
  import { apply } from "../apply";
3
3
  import { Bundle, type BundleProps } from "../esbuild";
4
+ import type { Input } from "../input";
4
5
  import type { Resolved } from "../output";
5
6
  import { type Context, Resource } from "../resource";
7
+ import { Secret } from "../secret";
6
8
  import { withExponentialBackoff } from "../utils/retry";
7
9
  import { type CloudflareApi, createCloudflareApi } from "./api";
8
10
  import type { Bindings, WorkerBindingSpec } from "./bindings";
11
+ import type { Bound } from "./bound";
12
+ import { isBucket } from "./bucket";
9
13
  import type { DurableObjectNamespace } from "./durable-object-namespace";
10
14
  import { isDurableObjectNamespace } from "./durable-object-namespace";
11
15
  import { isKVNamespace } from "./kv-namespace";
@@ -14,7 +18,7 @@ import type { SingleStepMigration } from "./worker-migration";
14
18
  /**
15
19
  * Properties for creating or updating a Worker
16
20
  */
17
- export interface WorkerProps {
21
+ export interface WorkerProps<B extends Bindings = Bindings> {
18
22
  /**
19
23
  * The worker script content (JavaScript or WASM)
20
24
  * One of script, entryPoint, or bundle must be provided
@@ -57,7 +61,7 @@ export interface WorkerProps {
57
61
  /**
58
62
  * Bindings to attach to the worker
59
63
  */
60
- bindings?: Bindings;
64
+ bindings?: B;
61
65
 
62
66
  /**
63
67
  * Environment variables to attach to the worker
@@ -96,7 +100,8 @@ export interface WorkerProps {
96
100
  /**
97
101
  * Output returned after Worker creation/update
98
102
  */
99
- export interface WorkerOutput extends Omit<WorkerProps, "url"> {
103
+ export interface WorkerOutput<B extends Bindings = Bindings>
104
+ extends Omit<WorkerProps<B>, "url"> {
100
105
  type: "service";
101
106
 
102
107
  /**
@@ -123,83 +128,91 @@ export interface WorkerOutput extends Omit<WorkerProps, "url"> {
123
128
  /**
124
129
  * The bindings that were created
125
130
  */
126
- bindings: Bindings;
131
+ bindings: B;
127
132
  }
128
133
 
129
134
  export function isWorker(resource: any): resource is Resolved<Worker> {
130
135
  return resource && typeof resource === "object" && resource.type === "worker";
131
136
  }
132
137
 
133
- export class Worker extends Resource(
138
+ export class Worker<B extends Bindings = Bindings> extends Resource(
134
139
  "cloudflare::Worker",
135
140
  {
136
141
  alwaysUpdate: true,
137
142
  },
138
- async (ctx: Context<WorkerOutput>, props: WorkerProps) => {
139
- // Create Cloudflare API client with automatic account discovery
140
- const api = await createCloudflareApi();
143
+ applyWorker,
144
+ ) {
145
+ public declare readonly Env: {
146
+ [bindingName in keyof B]: Bound<B[bindingName]>;
147
+ };
148
+ constructor(id: string, input: Input<WorkerProps<B>>, ...inputs: any[]) {
149
+ super(id, input as any, ...inputs);
150
+ }
151
+ }
141
152
 
142
- // Use the provided name
143
- const workerName = props.name;
153
+ async function applyWorker(ctx: Context<WorkerOutput>, props: WorkerProps) {
154
+ // Create Cloudflare API client with automatic account discovery
155
+ const api = await createCloudflareApi();
144
156
 
145
- // Validate input - we need either script, entryPoint, or bundle
146
- if (!props.script && !props.entrypoint) {
147
- throw new Error("One of script or entryPoint must be provided");
148
- }
157
+ // Use the provided name
158
+ const workerName = props.name;
149
159
 
150
- if (ctx.event === "delete") {
151
- return deleteWorker(ctx, api, workerName);
152
- } else if (ctx.event === "create") {
153
- await assertWorkerDoesNotExist(ctx, api, workerName);
154
- }
160
+ // Validate input - we need either script, entryPoint, or bundle
161
+ if (!props.script && !props.entrypoint) {
162
+ throw new Error("One of script or entryPoint must be provided");
163
+ }
155
164
 
156
- const oldBindings = await ctx.get<Bindings>("bindings");
165
+ if (ctx.event === "delete") {
166
+ return deleteWorker(ctx, api, workerName);
167
+ } else if (ctx.event === "create") {
168
+ await assertWorkerDoesNotExist(ctx, api, workerName);
169
+ }
157
170
 
158
- const scriptMetadata = await prepareWorkerMetadata(ctx, oldBindings, props);
171
+ const oldBindings = await ctx.get<Bindings>("bindings");
159
172
 
160
- // Get the script content - either from props.script, or by bundling
161
- const scriptContent =
162
- props.script ?? (await bundleWorkerScript(ctx, props));
173
+ const scriptMetadata = await prepareWorkerMetadata(ctx, oldBindings, props);
163
174
 
164
- // Upload the worker script
165
- await putWorker(ctx, api, workerName, scriptContent, scriptMetadata);
175
+ // Get the script content - either from props.script, or by bundling
176
+ const scriptContent = props.script ?? (await bundleWorkerScript(ctx, props));
166
177
 
167
- // TODO: it is less than ideal that this can fail, resulting in state problem
168
- await ctx.set("bindings", props.bindings);
178
+ // Upload the worker script
179
+ await putWorker(ctx, api, workerName, scriptContent, scriptMetadata);
169
180
 
170
- // Handle routes if requested
171
- await setupRoutes(api, workerName, props.routes || []);
181
+ // TODO: it is less than ideal that this can fail, resulting in state problem
182
+ await ctx.set("bindings", props.bindings);
172
183
 
173
- // Handle worker URL if requested
174
- const workerUrl = await configureURL(
175
- ctx,
176
- api,
177
- workerName,
178
- props.url ?? false,
179
- );
184
+ // Handle routes if requested
185
+ await setupRoutes(api, workerName, props.routes || []);
180
186
 
181
- // Get current timestamp
182
- const now = Date.now();
183
-
184
- // Construct the output
185
- const output: WorkerOutput = {
186
- type: "service",
187
- id: workerName,
188
- name: workerName,
189
- script: scriptContent,
190
- format: props.format || "esm", // Include format in the output
191
- routes: props.routes || [],
192
- bindings: props.bindings ?? {},
193
- env: props.env,
194
- observability: scriptMetadata.observability,
195
- createdAt: now,
196
- updatedAt: now,
197
- url: workerUrl,
198
- };
199
-
200
- return output;
201
- },
202
- ) {}
187
+ // Handle worker URL if requested
188
+ const workerUrl = await configureURL(
189
+ ctx,
190
+ api,
191
+ workerName,
192
+ props.url ?? false,
193
+ );
194
+
195
+ // Get current timestamp
196
+ const now = Date.now();
197
+
198
+ // Construct the output
199
+ const output: WorkerOutput = {
200
+ type: "service",
201
+ id: workerName,
202
+ name: workerName,
203
+ script: scriptContent,
204
+ format: props.format || "esm", // Include format in the output
205
+ routes: props.routes || [],
206
+ bindings: props.bindings ?? {},
207
+ env: props.env,
208
+ observability: scriptMetadata.observability,
209
+ createdAt: now,
210
+ updatedAt: now,
211
+ url: workerUrl,
212
+ };
213
+
214
+ return output;
215
+ }
203
216
 
204
217
  async function deleteWorker(
205
218
  ctx: Context<WorkerOutput>,
@@ -438,6 +451,18 @@ async function prepareWorkerMetadata(
438
451
  to: className,
439
452
  });
440
453
  }
454
+ } else if (isBucket(binding)) {
455
+ meta.bindings.push({
456
+ type: "r2_bucket",
457
+ name: bindingName,
458
+ bucket_name: binding.name || binding.id,
459
+ });
460
+ } else if (binding instanceof Secret) {
461
+ meta.bindings.push({
462
+ type: "secret_text",
463
+ name: bindingName,
464
+ text: binding.unencrypted,
465
+ });
441
466
  } else {
442
467
  // @ts-expect-error - we should never reach here
443
468
  throw new Error(`Unsupported binding type: ${binding.type}`);
package/src/encrypt.ts ADDED
@@ -0,0 +1,79 @@
1
+ import sodium from "libsodium-wrappers";
2
+
3
+ /**
4
+ * Encrypt a value with a symmetric key using libsodium
5
+ *
6
+ * @param value - The value to encrypt
7
+ * @param key - The encryption key
8
+ * @returns The base64-encoded encrypted value with nonce
9
+ */
10
+ export async function encryptWithKey(
11
+ value: string,
12
+ key: string,
13
+ ): Promise<string> {
14
+ // Initialize libsodium
15
+ await sodium.ready;
16
+
17
+ // Derive a key from the passphrase
18
+ const cryptoKey = sodium.crypto_generichash(
19
+ sodium.crypto_secretbox_KEYBYTES,
20
+ sodium.from_string(key),
21
+ );
22
+
23
+ // Generate a random nonce
24
+ const nonce = sodium.randombytes_buf(sodium.crypto_secretbox_NONCEBYTES);
25
+
26
+ // Encrypt the message
27
+ const encryptedBin = sodium.crypto_secretbox_easy(
28
+ sodium.from_string(value),
29
+ nonce,
30
+ cryptoKey,
31
+ );
32
+
33
+ // Combine nonce and ciphertext, then encode to base64
34
+ const combined = new Uint8Array(nonce.length + encryptedBin.length);
35
+ combined.set(nonce);
36
+ combined.set(encryptedBin, nonce.length);
37
+
38
+ return sodium.to_base64(combined, sodium.base64_variants.ORIGINAL);
39
+ }
40
+
41
+ /**
42
+ * Decrypt a value encrypted with a symmetric key
43
+ *
44
+ * @param encryptedValue - The base64-encoded encrypted value with nonce
45
+ * @param key - The decryption key
46
+ * @returns The decrypted string
47
+ */
48
+ export async function decryptWithKey(
49
+ encryptedValue: string,
50
+ key: string,
51
+ ): Promise<string> {
52
+ // Initialize libsodium
53
+ await sodium.ready;
54
+
55
+ // Derive a key from the passphrase
56
+ const cryptoKey = sodium.crypto_generichash(
57
+ sodium.crypto_secretbox_KEYBYTES,
58
+ sodium.from_string(key),
59
+ );
60
+
61
+ // Decode the base64 combined value
62
+ const combined = sodium.from_base64(
63
+ encryptedValue,
64
+ sodium.base64_variants.ORIGINAL,
65
+ );
66
+
67
+ // Extract nonce and ciphertext
68
+ const nonce = combined.slice(0, sodium.crypto_secretbox_NONCEBYTES);
69
+ const ciphertext = combined.slice(sodium.crypto_secretbox_NONCEBYTES);
70
+
71
+ // Decrypt the message
72
+ const decryptedBin = sodium.crypto_secretbox_open_easy(
73
+ ciphertext,
74
+ nonce,
75
+ cryptoKey,
76
+ );
77
+
78
+ return sodium.to_string(decryptedBin);
79
+ }
@@ -1,7 +1,7 @@
1
1
  import sodium from "libsodium-wrappers";
2
2
  import { type Context, Resource } from "../resource";
3
+ import type { Secret } from "../secret";
3
4
  import { createGitHubClient, verifyGitHubAuth } from "./client";
4
-
5
5
  /**
6
6
  * Properties for creating or updating a GitHub Secret
7
7
  */
@@ -24,7 +24,7 @@ export interface GitHubSecretProps {
24
24
  /**
25
25
  * Secret value (will be stored securely on GitHub)
26
26
  */
27
- value: string;
27
+ value: Secret;
28
28
 
29
29
  /**
30
30
  * Optional GitHub API token (overrides environment variable)
@@ -47,31 +47,6 @@ export interface GitHubSecretOutput extends Omit<GitHubSecretProps, "value"> {
47
47
  updatedAt: string;
48
48
  }
49
49
 
50
- /**
51
- * Encrypt a value for GitHub using libsodium
52
- * Based on GitHub's documentation for encrypting secrets
53
- *
54
- * @param value - The secret value to encrypt
55
- * @param publicKey - The base64-encoded public key from GitHub
56
- * @returns The base64-encoded encrypted value
57
- */
58
- async function encryptValue(value: string, publicKey: string): Promise<string> {
59
- // Initialize libsodium
60
- await sodium.ready;
61
-
62
- // Convert the public key from base64 to binary
63
- const binKey = sodium.from_base64(publicKey, sodium.base64_variants.ORIGINAL);
64
-
65
- // Convert the message to a Uint8Array
66
- const binMessage = sodium.from_string(value);
67
-
68
- // Encrypt the message with the public key using libsodium's sealed box
69
- const encryptedBin = sodium.crypto_box_seal(binMessage, binKey);
70
-
71
- // Convert the encrypted message to base64
72
- return sodium.to_base64(encryptedBin, sodium.base64_variants.ORIGINAL);
73
- }
74
-
75
50
  /**
76
51
  * Resource for managing GitHub repository secrets
77
52
  */
@@ -121,7 +96,10 @@ export class GitHubSecret extends Resource(
121
96
  );
122
97
 
123
98
  // Encrypt the secret value using libsodium
124
- const encryptedValue = await encryptValue(props.value, publicKey.key);
99
+ const encryptedValue = await encryptString(
100
+ props.value.unencrypted,
101
+ publicKey.key,
102
+ );
125
103
 
126
104
  // Create or update the secret with the encrypted value and key_id
127
105
  const response = await octokit.rest.actions.createOrUpdateRepoSecret({
@@ -132,11 +110,6 @@ export class GitHubSecret extends Resource(
132
110
  key_id: publicKey.key_id,
133
111
  });
134
112
 
135
- // Log success
136
- if (!ctx.quiet) {
137
- console.log(`Successfully created/updated secret: ${props.name}`);
138
- }
139
-
140
113
  // GitHub doesn't return the secret details on create/update, so we need to construct it
141
114
  const output: GitHubSecretOutput = {
142
115
  id: `${props.owner}/${props.repository}/${props.name}`,
@@ -170,3 +143,31 @@ export class GitHubSecret extends Resource(
170
143
  }
171
144
  },
172
145
  ) {}
146
+
147
+ /**
148
+ * Encrypt a value for GitHub using libsodium
149
+ * Based on GitHub's documentation for encrypting secrets
150
+ *
151
+ * @param value - The secret value to encrypt
152
+ * @param publicKey - The base64-encoded public key from GitHub
153
+ * @returns The base64-encoded encrypted value
154
+ */
155
+ async function encryptString(
156
+ value: string,
157
+ publicKey: string,
158
+ ): Promise<string> {
159
+ // Initialize libsodium
160
+ await sodium.ready;
161
+
162
+ // Convert the public key from base64 to binary
163
+ const binKey = sodium.from_base64(publicKey, sodium.base64_variants.ORIGINAL);
164
+
165
+ // Convert the message to a Uint8Array
166
+ const binMessage = sodium.from_string(value);
167
+
168
+ // Encrypt the message with the public key using libsodium's sealed box
169
+ const encryptedBin = sodium.crypto_box_seal(binMessage, binKey);
170
+
171
+ // Convert the encrypted message to base64
172
+ return sodium.to_base64(encryptedBin, sodium.base64_variants.ORIGINAL);
173
+ }
package/src/index.ts CHANGED
@@ -12,4 +12,5 @@ export {
12
12
  type DeleteContext,
13
13
  type UpdateContext,
14
14
  } from "./resource";
15
+ export * from "./secret";
15
16
  export type { FileSystemStateStore as StateStore } from "./state";