alchemy 0.1.17 → 0.2.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.
Files changed (301) hide show
  1. package/lib/alchemy.d.ts +67 -0
  2. package/lib/alchemy.js +40 -0
  3. package/lib/apply.d.ts +3 -20
  4. package/lib/apply.js +86 -78
  5. package/lib/aws/account-id.d.ts +0 -1
  6. package/lib/aws/account-id.js +0 -1
  7. package/lib/aws/bucket.d.ts +4 -7
  8. package/lib/aws/bucket.js +8 -12
  9. package/lib/aws/function.d.ts +4 -7
  10. package/lib/aws/function.js +9 -30
  11. package/lib/aws/index.d.ts +1 -1
  12. package/lib/aws/index.js +1 -1
  13. package/lib/aws/oidc/github-oidc-provider.d.ts +2 -6
  14. package/lib/aws/oidc/github-oidc-provider.js +10 -15
  15. package/lib/aws/oidc/index.d.ts +0 -1
  16. package/lib/aws/oidc/index.js +0 -1
  17. package/lib/aws/oidc/oidc-provider.d.ts +4 -6
  18. package/lib/aws/oidc/oidc-provider.js +9 -11
  19. package/lib/aws/policy-attachment.d.ts +9 -0
  20. package/lib/aws/policy-attachment.js +20 -0
  21. package/lib/aws/policy.d.ts +4 -16
  22. package/lib/aws/policy.js +9 -40
  23. package/lib/aws/queue.d.ts +4 -34
  24. package/lib/aws/queue.js +10 -20
  25. package/lib/aws/role.d.ts +4 -7
  26. package/lib/aws/role.js +17 -26
  27. package/lib/aws/ses.d.ts +4 -10
  28. package/lib/aws/ses.js +118 -131
  29. package/lib/aws/table.d.ts +4 -7
  30. package/lib/aws/table.js +8 -11
  31. package/lib/cloudflare/api.d.ts +0 -1
  32. package/lib/cloudflare/api.js +0 -1
  33. package/lib/cloudflare/asset-manifest.d.ts +0 -1
  34. package/lib/cloudflare/asset-manifest.js +0 -1
  35. package/lib/cloudflare/auth.d.ts +0 -1
  36. package/lib/cloudflare/auth.js +0 -1
  37. package/lib/cloudflare/bindings.d.ts +4 -3
  38. package/lib/cloudflare/bindings.js +3 -2
  39. package/lib/cloudflare/bound.d.ts +7 -0
  40. package/lib/cloudflare/bound.js +1 -0
  41. package/lib/cloudflare/bucket.d.ts +7 -15
  42. package/lib/cloudflare/bucket.js +12 -19
  43. package/lib/cloudflare/durable-object-namespace.d.ts +0 -3
  44. package/lib/cloudflare/durable-object-namespace.js +0 -4
  45. package/lib/cloudflare/generate-asset-manifest.d.ts +0 -1
  46. package/lib/cloudflare/generate-asset-manifest.js +1 -2
  47. package/lib/cloudflare/index.d.ts +0 -1
  48. package/lib/cloudflare/index.js +0 -1
  49. package/lib/cloudflare/kv-namespace.d.ts +6 -9
  50. package/lib/cloudflare/kv-namespace.js +16 -20
  51. package/lib/cloudflare/state.d.ts +0 -1
  52. package/lib/cloudflare/state.js +0 -1
  53. package/lib/cloudflare/static-site-router.d.ts +0 -1
  54. package/lib/cloudflare/static-site-router.js +0 -4
  55. package/lib/cloudflare/static-site.d.ts +5 -11
  56. package/lib/cloudflare/static-site.js +16 -28
  57. package/lib/cloudflare/types.d.ts +0 -1
  58. package/lib/cloudflare/types.js +0 -1
  59. package/lib/cloudflare/upload-asset-manifest.d.ts +0 -1
  60. package/lib/cloudflare/upload-asset-manifest.js +0 -1
  61. package/lib/cloudflare/worker-metadata.d.ts +0 -1
  62. package/lib/cloudflare/worker-metadata.js +0 -1
  63. package/lib/cloudflare/worker-migration.d.ts +0 -1
  64. package/lib/cloudflare/worker-migration.js +0 -1
  65. package/lib/cloudflare/worker.d.ts +13 -13
  66. package/lib/cloudflare/worker.js +43 -43
  67. package/lib/context.d.ts +58 -0
  68. package/lib/context.js +36 -0
  69. package/lib/destroy.d.ts +10 -13
  70. package/lib/destroy.js +104 -43
  71. package/lib/{esbuild.d.ts → esbuild/bundle.d.ts} +4 -18
  72. package/lib/{esbuild.js → esbuild/bundle.js} +10 -24
  73. package/lib/esbuild/index.d.ts +1 -0
  74. package/lib/esbuild/index.js +1 -0
  75. package/lib/fs/file.d.ts +13 -0
  76. package/lib/fs/file.js +20 -0
  77. package/lib/fs/folder.d.ts +10 -0
  78. package/lib/fs/folder.js +16 -0
  79. package/lib/fs/index.d.ts +2 -0
  80. package/lib/fs/index.js +2 -0
  81. package/lib/github/client.d.ts +0 -1
  82. package/lib/github/client.js +0 -1
  83. package/lib/github/index.d.ts +0 -1
  84. package/lib/github/index.js +0 -1
  85. package/lib/github/secret.d.ts +6 -7
  86. package/lib/github/secret.js +30 -36
  87. package/lib/index.d.ts +7 -10
  88. package/lib/index.js +6 -9
  89. package/lib/resource.d.ts +34 -62
  90. package/lib/resource.js +29 -270
  91. package/lib/scope.d.ts +35 -14
  92. package/lib/scope.js +78 -23
  93. package/lib/secret.d.ts +7 -0
  94. package/lib/secret.js +17 -0
  95. package/lib/state.d.ts +14 -10
  96. package/lib/state.js +15 -13
  97. package/lib/stripe/index.d.ts +0 -1
  98. package/lib/stripe/index.js +0 -1
  99. package/lib/stripe/price.d.ts +4 -5
  100. package/lib/stripe/price.js +10 -20
  101. package/lib/stripe/product.d.ts +4 -5
  102. package/lib/stripe/product.js +10 -20
  103. package/lib/stripe/webhook.d.ts +4 -6
  104. package/lib/stripe/webhook.js +12 -25
  105. package/lib/test/bun.d.ts +19 -0
  106. package/lib/test/bun.js +55 -0
  107. package/lib/{utils → util}/content-type.d.ts +0 -1
  108. package/lib/{utils → util}/content-type.js +0 -1
  109. package/lib/util/encrypt.d.ts +16 -0
  110. package/lib/util/encrypt.js +44 -0
  111. package/lib/{error.d.ts → util/ignore.d.ts} +0 -1
  112. package/lib/{error.js → util/ignore.js} +0 -1
  113. package/lib/{utils → util}/retry.d.ts +0 -1
  114. package/lib/{utils → util}/retry.js +0 -1
  115. package/lib/util/serde.d.ts +3 -0
  116. package/lib/util/serde.js +46 -0
  117. package/lib/util/slugify.d.ts +1 -0
  118. package/lib/util/slugify.js +3 -0
  119. package/package.json +5 -7
  120. package/src/alchemy.ts +120 -0
  121. package/src/apply.ts +109 -111
  122. package/src/aws/bucket.ts +11 -14
  123. package/src/aws/function.ts +13 -33
  124. package/src/aws/index.ts +1 -0
  125. package/src/aws/oidc/github-oidc-provider.ts +14 -19
  126. package/src/aws/oidc/oidc-provider.ts +19 -12
  127. package/src/aws/policy-attachment.ts +51 -0
  128. package/src/aws/policy.ts +15 -60
  129. package/src/aws/queue.ts +17 -21
  130. package/src/aws/role.ts +25 -28
  131. package/src/aws/ses.ts +150 -164
  132. package/src/aws/table.ts +15 -15
  133. package/src/cloudflare/bindings.ts +12 -2
  134. package/src/cloudflare/bound.ts +18 -0
  135. package/src/cloudflare/bucket.ts +40 -48
  136. package/src/cloudflare/durable-object-namespace.ts +0 -10
  137. package/src/cloudflare/generate-asset-manifest.ts +1 -1
  138. package/src/cloudflare/kv-namespace.ts +35 -33
  139. package/src/cloudflare/static-site-router.ts +0 -3
  140. package/src/cloudflare/static-site.ts +43 -59
  141. package/src/cloudflare/worker.ts +76 -64
  142. package/src/context.ts +122 -0
  143. package/src/destroy.ts +124 -73
  144. package/src/{esbuild.ts → esbuild/bundle.ts} +16 -28
  145. package/src/esbuild/index.ts +1 -0
  146. package/src/fs/file.ts +39 -0
  147. package/src/fs/folder.ts +32 -0
  148. package/src/fs/index.ts +2 -0
  149. package/src/github/secret.ts +53 -49
  150. package/src/index.ts +8 -15
  151. package/src/resource.ts +101 -462
  152. package/src/scope.ts +101 -36
  153. package/src/secret.ts +18 -0
  154. package/src/state.ts +30 -20
  155. package/src/stripe/price.ts +17 -22
  156. package/src/stripe/product.ts +17 -22
  157. package/src/stripe/webhook.ts +21 -27
  158. package/src/test/bun.ts +105 -0
  159. package/src/util/encrypt.ts +79 -0
  160. package/src/util/serde.ts +53 -0
  161. package/src/util/slugify.ts +3 -0
  162. package/lib/$.d.ts +0 -12
  163. package/lib/$.d.ts.map +0 -1
  164. package/lib/$.js +0 -42
  165. package/lib/$.js.map +0 -1
  166. package/lib/alchemize.d.ts +0 -43
  167. package/lib/alchemize.d.ts.map +0 -1
  168. package/lib/alchemize.js +0 -112
  169. package/lib/alchemize.js.map +0 -1
  170. package/lib/apply.d.ts.map +0 -1
  171. package/lib/apply.js.map +0 -1
  172. package/lib/aws/account-id.d.ts.map +0 -1
  173. package/lib/aws/account-id.js.map +0 -1
  174. package/lib/aws/bucket.d.ts.map +0 -1
  175. package/lib/aws/bucket.js.map +0 -1
  176. package/lib/aws/function.d.ts.map +0 -1
  177. package/lib/aws/function.js.map +0 -1
  178. package/lib/aws/index.d.ts.map +0 -1
  179. package/lib/aws/index.js.map +0 -1
  180. package/lib/aws/oidc/github-oidc-provider.d.ts.map +0 -1
  181. package/lib/aws/oidc/github-oidc-provider.js.map +0 -1
  182. package/lib/aws/oidc/index.d.ts.map +0 -1
  183. package/lib/aws/oidc/index.js.map +0 -1
  184. package/lib/aws/oidc/oidc-provider.d.ts.map +0 -1
  185. package/lib/aws/oidc/oidc-provider.js.map +0 -1
  186. package/lib/aws/policy.d.ts.map +0 -1
  187. package/lib/aws/policy.js.map +0 -1
  188. package/lib/aws/queue.d.ts.map +0 -1
  189. package/lib/aws/queue.js.map +0 -1
  190. package/lib/aws/role.d.ts.map +0 -1
  191. package/lib/aws/role.js.map +0 -1
  192. package/lib/aws/ses.d.ts.map +0 -1
  193. package/lib/aws/ses.js.map +0 -1
  194. package/lib/aws/table.d.ts.map +0 -1
  195. package/lib/aws/table.js.map +0 -1
  196. package/lib/cloudflare/api.d.ts.map +0 -1
  197. package/lib/cloudflare/api.js.map +0 -1
  198. package/lib/cloudflare/asset-manifest.d.ts.map +0 -1
  199. package/lib/cloudflare/asset-manifest.js.map +0 -1
  200. package/lib/cloudflare/auth.d.ts.map +0 -1
  201. package/lib/cloudflare/auth.js.map +0 -1
  202. package/lib/cloudflare/bindings.d.ts.map +0 -1
  203. package/lib/cloudflare/bindings.js.map +0 -1
  204. package/lib/cloudflare/bucket.d.ts.map +0 -1
  205. package/lib/cloudflare/bucket.js.map +0 -1
  206. package/lib/cloudflare/durable-object-namespace.d.ts.map +0 -1
  207. package/lib/cloudflare/durable-object-namespace.js.map +0 -1
  208. package/lib/cloudflare/generate-asset-manifest.d.ts.map +0 -1
  209. package/lib/cloudflare/generate-asset-manifest.js.map +0 -1
  210. package/lib/cloudflare/index.d.ts.map +0 -1
  211. package/lib/cloudflare/index.js.map +0 -1
  212. package/lib/cloudflare/kv-namespace.d.ts.map +0 -1
  213. package/lib/cloudflare/kv-namespace.js.map +0 -1
  214. package/lib/cloudflare/state.d.ts.map +0 -1
  215. package/lib/cloudflare/state.js.map +0 -1
  216. package/lib/cloudflare/static-site-router.d.ts.map +0 -1
  217. package/lib/cloudflare/static-site-router.js.map +0 -1
  218. package/lib/cloudflare/static-site.d.ts.map +0 -1
  219. package/lib/cloudflare/static-site.js.map +0 -1
  220. package/lib/cloudflare/types.d.ts.map +0 -1
  221. package/lib/cloudflare/types.js.map +0 -1
  222. package/lib/cloudflare/upload-asset-manifest.d.ts.map +0 -1
  223. package/lib/cloudflare/upload-asset-manifest.js.map +0 -1
  224. package/lib/cloudflare/worker-metadata.d.ts.map +0 -1
  225. package/lib/cloudflare/worker-metadata.js.map +0 -1
  226. package/lib/cloudflare/worker-migration.d.ts.map +0 -1
  227. package/lib/cloudflare/worker-migration.js.map +0 -1
  228. package/lib/cloudflare/worker.d.ts.map +0 -1
  229. package/lib/cloudflare/worker.js.map +0 -1
  230. package/lib/destroy.d.ts.map +0 -1
  231. package/lib/destroy.js.map +0 -1
  232. package/lib/error.d.ts.map +0 -1
  233. package/lib/error.js.map +0 -1
  234. package/lib/esbuild.d.ts.map +0 -1
  235. package/lib/esbuild.js.map +0 -1
  236. package/lib/fs.d.ts +0 -12
  237. package/lib/fs.d.ts.map +0 -1
  238. package/lib/fs.js +0 -41
  239. package/lib/fs.js.map +0 -1
  240. package/lib/github/client.d.ts.map +0 -1
  241. package/lib/github/client.js.map +0 -1
  242. package/lib/github/index.d.ts.map +0 -1
  243. package/lib/github/index.js.map +0 -1
  244. package/lib/github/secret.d.ts.map +0 -1
  245. package/lib/github/secret.js.map +0 -1
  246. package/lib/global.d.ts +0 -16
  247. package/lib/global.d.ts.map +0 -1
  248. package/lib/global.js +0 -42
  249. package/lib/global.js.map +0 -1
  250. package/lib/index.d.ts.map +0 -1
  251. package/lib/index.js.map +0 -1
  252. package/lib/input.d.ts +0 -6
  253. package/lib/input.d.ts.map +0 -1
  254. package/lib/input.js +0 -2
  255. package/lib/input.js.map +0 -1
  256. package/lib/main.d.ts +0 -3
  257. package/lib/main.d.ts.map +0 -1
  258. package/lib/main.js +0 -32
  259. package/lib/main.js.map +0 -1
  260. package/lib/output.d.ts +0 -24
  261. package/lib/output.d.ts.map +0 -1
  262. package/lib/output.js +0 -23
  263. package/lib/output.js.map +0 -1
  264. package/lib/print.d.ts +0 -7
  265. package/lib/print.d.ts.map +0 -1
  266. package/lib/print.js +0 -10
  267. package/lib/print.js.map +0 -1
  268. package/lib/resource.d.ts.map +0 -1
  269. package/lib/resource.js.map +0 -1
  270. package/lib/scope.d.ts.map +0 -1
  271. package/lib/scope.js.map +0 -1
  272. package/lib/slug.d.ts +0 -2
  273. package/lib/slug.d.ts.map +0 -1
  274. package/lib/slug.js +0 -4
  275. package/lib/slug.js.map +0 -1
  276. package/lib/state.d.ts.map +0 -1
  277. package/lib/state.js.map +0 -1
  278. package/lib/stripe/index.d.ts.map +0 -1
  279. package/lib/stripe/index.js.map +0 -1
  280. package/lib/stripe/price.d.ts.map +0 -1
  281. package/lib/stripe/price.js.map +0 -1
  282. package/lib/stripe/product.d.ts.map +0 -1
  283. package/lib/stripe/product.js.map +0 -1
  284. package/lib/stripe/webhook.d.ts.map +0 -1
  285. package/lib/stripe/webhook.js.map +0 -1
  286. package/lib/utils/content-type.d.ts.map +0 -1
  287. package/lib/utils/content-type.js.map +0 -1
  288. package/lib/utils/retry.d.ts.map +0 -1
  289. package/lib/utils/retry.js.map +0 -1
  290. package/src/$.ts +0 -69
  291. package/src/alchemize.ts +0 -186
  292. package/src/fs.ts +0 -47
  293. package/src/global.ts +0 -63
  294. package/src/input.ts +0 -16
  295. package/src/main.ts +0 -34
  296. package/src/output.ts +0 -71
  297. package/src/print.ts +0 -18
  298. package/src/slug.ts +0 -3
  299. /package/src/{utils → util}/content-type.ts +0 -0
  300. /package/src/{error.ts → util/ignore.ts} +0 -0
  301. /package/src/{utils → util}/retry.ts +0 -0
@@ -1,5 +1,3 @@
1
- import type { Resolved } from "../output";
2
-
3
1
  export interface DurableObjectNamespaceInput {
4
2
  className: string;
5
3
  scriptName?: string | undefined;
@@ -8,14 +6,6 @@ export interface DurableObjectNamespaceInput {
8
6
  namespaceId?: string | undefined;
9
7
  }
10
8
 
11
- export function isDurableObjectNamespace(
12
- binding: any,
13
- ): binding is Resolved<DurableObjectNamespace> {
14
- return (
15
- typeof binding === "object" && binding.type === "durable_object_namespace"
16
- );
17
- }
18
-
19
9
  export class DurableObjectNamespace implements DurableObjectNamespaceInput {
20
10
  public readonly type = "durable_object_namespace" as const;
21
11
  // alias for bindingName to be consistent with other bindings
@@ -2,7 +2,7 @@ import crypto from "crypto";
2
2
  import { promises as fs } from "fs";
3
3
  import { glob } from "glob";
4
4
  import path from "path";
5
- import { getContentType } from "../utils/content-type";
5
+ import { getContentType } from "../util/content-type";
6
6
  import type {
7
7
  AssetManifest,
8
8
  AssetManifestEntry,
@@ -1,8 +1,14 @@
1
- import type { Resolved } from "../output";
2
- import { type Context, Resource } from "../resource";
3
- import { withExponentialBackoff } from "../utils/retry";
1
+ import type { Context } from "../context";
2
+ import { Resource } from "../resource";
3
+ import { withExponentialBackoff } from "../util/retry";
4
4
  import { createCloudflareApi } from "./api";
5
5
 
6
+ export function isKVNamespace(resource: any): resource is KVNamespace {
7
+ return (
8
+ resource && typeof resource === "object" && resource.type === "kv_namespace"
9
+ );
10
+ }
11
+
6
12
  /**
7
13
  * Properties for creating or updating a KV Namespace
8
14
  */
@@ -52,12 +58,14 @@ export interface KVPair {
52
58
  /**
53
59
  * Output returned after KV Namespace creation/update
54
60
  */
55
- export interface KVNamespaceOutput extends KVNamespaceProps {
61
+ export interface KVNamespace
62
+ extends Resource<"cloudflare::KVNamespace">,
63
+ KVNamespaceProps {
56
64
  type: "kv_namespace";
57
65
  /**
58
66
  * The ID of the namespace
59
67
  */
60
- id: string;
68
+ namespaceId: string;
61
69
 
62
70
  /**
63
71
  * Time at which the namespace was created
@@ -70,23 +78,19 @@ export interface KVNamespaceOutput extends KVNamespaceProps {
70
78
  modifiedAt: number;
71
79
  }
72
80
 
73
- export function isKVNamespace(
74
- resource: any,
75
- ): resource is Resolved<KVNamespace> {
76
- return (
77
- resource && typeof resource === "object" && resource.type === "kv_namespace"
78
- );
79
- }
80
-
81
- export class KVNamespace extends Resource(
81
+ export const KVNamespace = Resource(
82
82
  "cloudflare::KVNamespace",
83
- async (ctx: Context<KVNamespaceOutput>, props: KVNamespaceProps) => {
83
+ async function (
84
+ this: Context<KVNamespace>,
85
+ id: string,
86
+ props: KVNamespaceProps,
87
+ ) {
84
88
  // Create Cloudflare API client with automatic account discovery
85
89
  const api = await createCloudflareApi();
86
90
 
87
- if (ctx.event === "delete") {
91
+ if (this.phase === "delete") {
88
92
  // For delete operations, we need to check if the namespace ID exists in the output
89
- const namespaceId = ctx.output?.id;
93
+ const namespaceId = this.output?.namespaceId;
90
94
  if (namespaceId) {
91
95
  // Delete KV namespace
92
96
  const deleteResponse = await api.delete(
@@ -104,19 +108,22 @@ export class KVNamespace extends Resource(
104
108
  }
105
109
 
106
110
  // Return minimal output for deleted state
107
- return;
111
+ return this.destroy();
108
112
  } else {
109
113
  // For create or update operations
110
- // If ctx.event is "update", we expect ctx.output to exist
111
- let namespaceId = ctx.event === "update" ? ctx.output?.id || "" : "";
114
+ // If this.phase is "update", we expect this.output to exist
115
+ let namespaceId =
116
+ this.phase === "update" ? this.output?.namespaceId || "" : "";
112
117
  let createdAt =
113
- ctx.event === "update"
114
- ? ctx.output?.createdAt || Date.now()
118
+ this.phase === "update"
119
+ ? this.output?.createdAt || Date.now()
115
120
  : Date.now();
116
121
 
117
- if (ctx.event === "update" && namespaceId) {
122
+ if (this.phase === "update" && namespaceId) {
118
123
  // Can't update a KV namespace title directly, just work with existing ID
119
124
  } else {
125
+ // TODO: if it already exists, then check the tags to see if we own it and continue
126
+
120
127
  // Create new KV namespace
121
128
  const createResponse = await api.post(
122
129
  `/accounts/${api.accountId}/storage/kv/namespaces`,
@@ -207,19 +214,14 @@ export class KVNamespace extends Resource(
207
214
  }
208
215
  }
209
216
 
210
- const now = Date.now();
211
-
212
- // Construct the output
213
- const output: KVNamespaceOutput = {
217
+ return this({
214
218
  type: "kv_namespace",
215
- id: namespaceId,
219
+ namespaceId: namespaceId,
216
220
  title: props.title,
217
221
  values: props.values,
218
222
  createdAt: createdAt,
219
- modifiedAt: now,
220
- };
221
-
222
- return output;
223
+ modifiedAt: Date.now(),
224
+ });
223
225
  }
224
226
  },
225
- ) {}
227
+ );
@@ -34,14 +34,12 @@ export default {
34
34
  isInit = true;
35
35
  for (const [key, value] of Object.entries(env)) {
36
36
  if (key.startsWith("ROUTE_")) {
37
- console.log(key, value);
38
37
  const service = value as Service;
39
38
  const patternKey = `__${key}`;
40
39
  if (!(patternKey in env)) {
41
40
  throw new Error(`Missing pattern key: ${patternKey}`);
42
41
  }
43
42
  const pattern = (env as any)[`__${key}`];
44
- console.log("pattern", pattern);
45
43
  // TODO(sam): we should support narrowing down METHOD
46
44
  app.all(pattern, (ctx) => service.fetch(ctx.req.raw));
47
45
  }
@@ -53,7 +51,6 @@ export default {
53
51
 
54
52
  // fall back to assuming the request is a static file
55
53
  app.notFound(async (ctx) => {
56
- console.log("not found");
57
54
  const request = ctx.req;
58
55
  const env = ctx.env;
59
56
  const url = new URL(request.url);
@@ -2,10 +2,9 @@ import { exec } from "child_process";
2
2
  import { promises as fs } from "fs";
3
3
  import path from "path";
4
4
  import { promisify } from "util";
5
- import { apply } from "../apply";
6
- import type { BundleProps } from "../esbuild";
7
- import type { Input } from "../input";
8
- import { type Context, Resource } from "../resource";
5
+ import type { Context } from "../context";
6
+ import type { BundleProps } from "../esbuild/bundle";
7
+ import { Resource } from "../resource";
9
8
  import { createCloudflareApi } from "./api";
10
9
  import type { Bindings } from "./bindings";
11
10
  import { generateAssetManifest } from "./generate-asset-manifest";
@@ -131,12 +130,7 @@ export interface StaticSiteProps {
131
130
  /**
132
131
  * Output returned after StaticSite creation/update
133
132
  */
134
- export interface StaticSiteOutput {
135
- /**
136
- * The ID of the site
137
- */
138
- id: string;
139
-
133
+ export interface StaticSite extends Resource<"cloudflare::StaticSite"> {
140
134
  /**
141
135
  * The ID of the worker
142
136
  */
@@ -208,16 +202,20 @@ export interface StaticSiteOutput {
208
202
  routes?: Record<string, string>;
209
203
  }
210
204
 
211
- export class StaticSite extends Resource(
205
+ export const StaticSite = Resource(
212
206
  "cloudflare::StaticSite",
213
207
  {
214
208
  alwaysUpdate: true,
215
209
  },
216
- async (ctx: Context<StaticSiteOutput>, props: StaticSiteProps) => {
217
- if (ctx.event === "delete") {
210
+ async function (
211
+ this: Context<StaticSite>,
212
+ id: string,
213
+ props: StaticSiteProps,
214
+ ) {
215
+ if (this.phase === "delete") {
218
216
  // For delete operations, we'll rely on the Worker delete to clean up
219
217
  // Return empty output for deleted state
220
- return;
218
+ return this.destroy();
221
219
  }
222
220
 
223
221
  // Validate that a name is provided
@@ -246,7 +244,7 @@ export class StaticSite extends Resource(
246
244
  // Run build command if provided
247
245
  if (props.build?.command) {
248
246
  try {
249
- if (!ctx.quiet) {
247
+ if (!this.quiet) {
250
248
  console.log(props.build.command);
251
249
  }
252
250
  const execAsync = promisify(exec);
@@ -254,7 +252,7 @@ export class StaticSite extends Resource(
254
252
  cwd: process.cwd(),
255
253
  });
256
254
 
257
- if (stdout && !ctx.quiet) console.log(stdout);
255
+ if (stdout && !this.quiet) console.log(stdout);
258
256
  } catch (error: any) {
259
257
  // Log detailed error information
260
258
  console.error(`Build command failed with exit code ${error.code}`);
@@ -276,22 +274,18 @@ export class StaticSite extends Resource(
276
274
  const api = await createCloudflareApi();
277
275
 
278
276
  // Step 1: Create or get the KV namespace for assets
279
- const kv = new KVNamespace("assets", {
280
- title: `${siteName}-assets`,
281
- });
282
-
283
- const [{ id: kvNamespaceId }, assetManifest] = await Promise.all([
284
- apply(kv, {
285
- quiet: ctx.quiet,
277
+ const [kv, assetManifest] = await Promise.all([
278
+ KVNamespace("assets", {
279
+ title: `${siteName}-assets`,
286
280
  }),
287
281
  generateAssetManifest(props.dir),
288
282
  ]);
289
283
 
290
284
  // Step 3: Upload assets to KV
291
- await uploadAssetManifest(api, kvNamespaceId, assetManifest);
285
+ await uploadAssetManifest(api, kv.namespaceId, assetManifest);
292
286
 
293
287
  // Prepare the bindings for the worker
294
- const bindings: Input<Bindings> = {
288
+ const bindings: Bindings = {
295
289
  ASSETS: kv,
296
290
  INDEX_PAGE: indexPage,
297
291
  // Add error page binding if specified
@@ -335,39 +329,31 @@ export class StaticSite extends Resource(
335
329
  // TypeScript file doesn't exist, use JavaScript (default)
336
330
  }
337
331
 
338
- const worker = new Worker(
339
- "worker",
340
- {
341
- name: siteName,
342
- url: true,
343
- format: props.format || "esm",
344
- // If a custom worker script is provided, use it, otherwise use the entrypoint from router
345
- ...(props.workerScript
346
- ? { script: props.workerScript }
347
- : {
348
- entrypoint: path.resolve(__dirname, entrypointFile),
349
- bundle: bundleOptions,
350
- }),
351
- bindings,
352
- env: {
353
- ...Object.fromEntries(
354
- Object.entries(routes).map(([key, value]) => [
355
- `__ROUTE_${value}`,
356
- key,
357
- ]),
358
- ),
359
- },
332
+ const worker = await Worker("worker", {
333
+ name: siteName,
334
+ url: true,
335
+ format: props.format || "esm",
336
+ // If a custom worker script is provided, use it, otherwise use the entrypoint from router
337
+ ...(props.workerScript
338
+ ? { script: props.workerScript }
339
+ : {
340
+ entrypoint: path.resolve(__dirname, entrypointFile),
341
+ bundle: bundleOptions as any,
342
+ }),
343
+ bindings,
344
+ env: {
345
+ ...Object.fromEntries(
346
+ Object.entries(routes).map(([key, value]) => [
347
+ `__ROUTE_${value}`,
348
+ key,
349
+ ]),
350
+ ),
360
351
  },
361
- // place a dependency on the namespace
362
- kv.id,
363
- );
352
+ });
364
353
 
365
- // Get current timestamp
366
354
  const now = Date.now();
367
355
 
368
- // Construct the output
369
- const output: Input<StaticSiteOutput> = {
370
- id: siteName,
356
+ return this({
371
357
  workerId: worker.id,
372
358
  name: siteName,
373
359
  directory: props.dir,
@@ -375,14 +361,12 @@ export class StaticSite extends Resource(
375
361
  errorPage: props.errorPage,
376
362
  indexPage,
377
363
  assets: assetManifest.map((item) => item.key),
378
- createdAt: ctx.output?.createdAt || now,
364
+ createdAt: this.output?.createdAt || now,
379
365
  updatedAt: now,
380
366
  domain: props.domain,
381
367
  production: props.production !== false,
382
368
  url: worker.url,
383
369
  routes,
384
- };
385
-
386
- return output;
370
+ });
387
371
  },
388
- ) {}
372
+ );
@@ -1,21 +1,25 @@
1
1
  import * as fs from "fs/promises";
2
- import { apply } from "../apply";
3
- import { Bundle, type BundleProps } from "../esbuild";
4
- import type { Resolved } from "../output";
5
- import { type Context, Resource } from "../resource";
6
- import { withExponentialBackoff } from "../utils/retry";
2
+ import type { Context } from "../context";
3
+ import { Bundle, type BundleProps } from "../esbuild/bundle";
4
+ import { Resource } from "../resource";
5
+ import { isSecret } from "../secret";
6
+ import { withExponentialBackoff } from "../util/retry";
7
+ import { slugify } from "../util/slugify";
7
8
  import { type CloudflareApi, createCloudflareApi } from "./api";
8
- import type { Bindings, WorkerBindingSpec } from "./bindings";
9
- import { isBucket } from "./bucket";
9
+ import {
10
+ type Bindings,
11
+ type WorkerBindingSpec,
12
+ isDurableObjectNamespace,
13
+ } from "./bindings";
14
+ import type { Bound } from "./bound";
10
15
  import type { DurableObjectNamespace } from "./durable-object-namespace";
11
- import { isDurableObjectNamespace } from "./durable-object-namespace";
12
16
  import { isKVNamespace } from "./kv-namespace";
13
17
  import type { WorkerScriptMetadata } from "./worker-metadata";
14
18
  import type { SingleStepMigration } from "./worker-migration";
15
19
  /**
16
20
  * Properties for creating or updating a Worker
17
21
  */
18
- export interface WorkerProps {
22
+ export interface WorkerProps<B extends Bindings = Bindings> {
19
23
  /**
20
24
  * The worker script content (JavaScript or WASM)
21
25
  * One of script, entryPoint, or bundle must be provided
@@ -58,7 +62,7 @@ export interface WorkerProps {
58
62
  /**
59
63
  * Bindings to attach to the worker
60
64
  */
61
- bindings?: Bindings;
65
+ bindings?: B;
62
66
 
63
67
  /**
64
68
  * Environment variables to attach to the worker
@@ -97,7 +101,9 @@ export interface WorkerProps {
97
101
  /**
98
102
  * Output returned after Worker creation/update
99
103
  */
100
- export interface WorkerOutput extends Omit<WorkerProps, "url"> {
104
+ export interface Worker<B extends Bindings = Bindings>
105
+ extends Resource<"cloudflare::Worker">,
106
+ Omit<WorkerProps<B>, "url"> {
101
107
  type: "service";
102
108
 
103
109
  /**
@@ -124,19 +130,24 @@ export interface WorkerOutput extends Omit<WorkerProps, "url"> {
124
130
  /**
125
131
  * The bindings that were created
126
132
  */
127
- bindings: Bindings;
128
- }
133
+ bindings: B | undefined;
129
134
 
130
- export function isWorker(resource: any): resource is Resolved<Worker> {
131
- return resource && typeof resource === "object" && resource.type === "worker";
135
+ // phantom property (for typeof myWorker.Env)
136
+ Env: {
137
+ [bindingName in keyof B]: Bound<B[bindingName]>;
138
+ };
132
139
  }
133
140
 
134
- export class Worker extends Resource(
141
+ export const Worker = Resource(
135
142
  "cloudflare::Worker",
136
143
  {
137
144
  alwaysUpdate: true,
138
145
  },
139
- async (ctx: Context<WorkerOutput>, props: WorkerProps) => {
146
+ async function <const B extends Bindings>(
147
+ this: Context<Worker<NoInfer<B>>>,
148
+ id: string,
149
+ props: WorkerProps<B>,
150
+ ): Promise<Worker<B>> {
140
151
  // Create Cloudflare API client with automatic account discovery
141
152
  const api = await createCloudflareApi();
142
153
 
@@ -148,32 +159,36 @@ export class Worker extends Resource(
148
159
  throw new Error("One of script or entryPoint must be provided");
149
160
  }
150
161
 
151
- if (ctx.event === "delete") {
152
- return deleteWorker(ctx, api, workerName);
153
- } else if (ctx.event === "create") {
154
- await assertWorkerDoesNotExist(ctx, api, workerName);
162
+ if (this.phase === "delete") {
163
+ await deleteWorker(this, api, workerName);
164
+ return this.destroy();
165
+ } else if (this.phase === "create") {
166
+ await assertWorkerDoesNotExist(this, api, workerName);
155
167
  }
156
168
 
157
- const oldBindings = await ctx.get<Bindings>("bindings");
169
+ const oldBindings = await this.get<Bindings>("bindings");
158
170
 
159
- const scriptMetadata = await prepareWorkerMetadata(ctx, oldBindings, props);
171
+ const scriptMetadata = await prepareWorkerMetadata(
172
+ this,
173
+ oldBindings,
174
+ props,
175
+ );
160
176
 
161
177
  // Get the script content - either from props.script, or by bundling
162
- const scriptContent =
163
- props.script ?? (await bundleWorkerScript(ctx, props));
178
+ const scriptContent = props.script ?? (await bundleWorkerScript(props));
164
179
 
165
180
  // Upload the worker script
166
- await putWorker(ctx, api, workerName, scriptContent, scriptMetadata);
181
+ await putWorker(api, workerName, scriptContent, scriptMetadata);
167
182
 
168
183
  // TODO: it is less than ideal that this can fail, resulting in state problem
169
- await ctx.set("bindings", props.bindings);
184
+ await this.set("bindings", props.bindings);
170
185
 
171
186
  // Handle routes if requested
172
187
  await setupRoutes(api, workerName, props.routes || []);
173
188
 
174
189
  // Handle worker URL if requested
175
190
  const workerUrl = await configureURL(
176
- ctx,
191
+ this,
177
192
  api,
178
193
  workerName,
179
194
  props.url ?? false,
@@ -183,27 +198,27 @@ export class Worker extends Resource(
183
198
  const now = Date.now();
184
199
 
185
200
  // Construct the output
186
- const output: WorkerOutput = {
201
+ return this({
187
202
  type: "service",
188
- id: workerName,
203
+ id,
189
204
  name: workerName,
190
205
  script: scriptContent,
191
206
  format: props.format || "esm", // Include format in the output
192
207
  routes: props.routes || [],
193
- bindings: props.bindings ?? {},
208
+ bindings: props.bindings ?? ({} as B),
194
209
  env: props.env,
195
210
  observability: scriptMetadata.observability,
196
211
  createdAt: now,
197
212
  updatedAt: now,
198
213
  url: workerUrl,
199
- };
200
-
201
- return output;
214
+ // phantom property
215
+ Env: undefined!,
216
+ });
202
217
  },
203
- ) {}
218
+ );
204
219
 
205
- async function deleteWorker(
206
- ctx: Context<WorkerOutput>,
220
+ async function deleteWorker<B extends Bindings>(
221
+ ctx: Context<Worker<B>>,
207
222
  api: CloudflareApi,
208
223
  workerName: string,
209
224
  ) {
@@ -272,7 +287,6 @@ async function deleteWorker(
272
287
  }
273
288
 
274
289
  async function putWorker(
275
- ctx: Context<WorkerOutput>,
276
290
  api: CloudflareApi,
277
291
  workerName: string,
278
292
  scriptContent: string,
@@ -297,8 +311,6 @@ async function putWorker(
297
311
  scriptName,
298
312
  );
299
313
 
300
- // console.log("Script Metadata:", JSON.stringify(scriptMetadata, null, 2));
301
-
302
314
  // Add metadata as JSON
303
315
  formData.append(
304
316
  "metadata",
@@ -363,8 +375,8 @@ interface WorkerMetadata {
363
375
  tags?: string[];
364
376
  }
365
377
 
366
- async function prepareWorkerMetadata(
367
- ctx: Context<WorkerOutput>,
378
+ async function prepareWorkerMetadata<B extends Bindings>(
379
+ ctx: Context<Worker<B>>,
368
380
  oldBindings: Bindings | undefined,
369
381
  props: WorkerProps,
370
382
  ): Promise<WorkerMetadata> {
@@ -374,7 +386,8 @@ async function prepareWorkerMetadata(
374
386
  observability: {
375
387
  enabled: props.observability?.enabled !== false,
376
388
  },
377
- tags: [`alchemy:id:${ctx.resourceFQN}`],
389
+ // TODO(sam): base64 encode instead? 0 collision risk vs readability.
390
+ tags: [`alchemy:id:${slugify(ctx.fqn)}`],
378
391
  migrations: {
379
392
  new_classes: props.migrations?.new_classes ?? [],
380
393
  deleted_classes: props.migrations?.deleted_classes ?? [],
@@ -384,7 +397,7 @@ async function prepareWorkerMetadata(
384
397
  },
385
398
  };
386
399
 
387
- const bindings = (props.bindings ?? {}) as Resolved<Bindings>;
400
+ const bindings = (props.bindings ?? {}) as Bindings;
388
401
 
389
402
  // Convert bindings to the format expected by the API
390
403
  for (const [bindingName, binding] of Object.entries(bindings)) {
@@ -394,7 +407,7 @@ async function prepareWorkerMetadata(
394
407
  meta.bindings.push({
395
408
  type: "kv_namespace",
396
409
  name: bindingName,
397
- namespace_id: binding.id,
410
+ namespace_id: binding.namespaceId,
398
411
  });
399
412
  } else if (typeof binding === "string") {
400
413
  meta.bindings.push({
@@ -408,7 +421,7 @@ async function prepareWorkerMetadata(
408
421
  name: bindingName,
409
422
  service: binding.id,
410
423
  });
411
- } else if (isDurableObjectNamespace(binding)) {
424
+ } else if (binding.type === "durable_object_namespace") {
412
425
  const stableId = binding.id;
413
426
  const className = binding.className;
414
427
 
@@ -439,11 +452,17 @@ async function prepareWorkerMetadata(
439
452
  to: className,
440
453
  });
441
454
  }
442
- } else if (isBucket(binding)) {
455
+ } else if (binding.type === "r2_bucket") {
443
456
  meta.bindings.push({
444
457
  type: "r2_bucket",
445
458
  name: bindingName,
446
- bucket_name: binding.name || binding.id,
459
+ bucket_name: binding.name,
460
+ });
461
+ } else if (isSecret(binding)) {
462
+ meta.bindings.push({
463
+ type: "secret_text",
464
+ name: bindingName,
465
+ text: binding.unencrypted,
447
466
  });
448
467
  } else {
449
468
  // @ts-expect-error - we should never reach here
@@ -476,8 +495,8 @@ async function prepareWorkerMetadata(
476
495
  return meta;
477
496
  }
478
497
 
479
- async function assertWorkerDoesNotExist(
480
- ctx: Context<WorkerOutput>,
498
+ async function assertWorkerDoesNotExist<B extends Bindings>(
499
+ ctx: Context<Worker<B>>,
481
500
  api: CloudflareApi,
482
501
  workerName: string,
483
502
  ) {
@@ -498,7 +517,7 @@ async function assertWorkerDoesNotExist(
498
517
 
499
518
  if (
500
519
  metadata.default_environment?.script.tags.includes(
501
- `alchemy:id:${ctx.resourceFQN}`,
520
+ `alchemy:id:${slugify(ctx.fqn)}`,
502
521
  )
503
522
  ) {
504
523
  return true;
@@ -514,10 +533,7 @@ async function assertWorkerDoesNotExist(
514
533
  }
515
534
  }
516
535
 
517
- async function bundleWorkerScript(
518
- ctx: Context<WorkerOutput>,
519
- props: WorkerProps,
520
- ) {
536
+ async function bundleWorkerScript<B extends Bindings>(props: WorkerProps) {
521
537
  // Get the script content - either from props.script, or by bundling
522
538
 
523
539
  // Create and use a Bundle resource with worker-optimized configuration
@@ -538,18 +554,14 @@ async function bundleWorkerScript(
538
554
  };
539
555
 
540
556
  // Create the bundle
541
- const bundle = new Bundle("bundle", {
557
+ const bundle = await Bundle("bundle", {
542
558
  entryPoint: props.entrypoint!,
543
559
  ...bundleOptions,
544
560
  external: [...(bundleOptions.external ?? []), "cloudflare:workers"],
545
561
  });
546
562
 
547
- // Get the bundled content
548
- const bundleOutput = await apply(bundle, {
549
- quiet: ctx.quiet,
550
- });
551
563
  try {
552
- return await fs.readFile(bundleOutput.path, "utf-8");
564
+ return await fs.readFile(bundle.path, "utf-8");
553
565
  } catch (error) {
554
566
  console.error("Error reading bundle:", error);
555
567
  throw new Error("Error reading bundle");
@@ -617,8 +629,8 @@ async function setupRoutes(
617
629
  }
618
630
  }
619
631
 
620
- async function configureURL(
621
- ctx: Context<WorkerOutput>,
632
+ async function configureURL<B extends Bindings>(
633
+ ctx: Context<Worker<B>>,
622
634
  api: CloudflareApi,
623
635
  workerName: string,
624
636
  url: boolean,
@@ -657,7 +669,7 @@ async function configureURL(
657
669
  // Add a delay when the subdomain is first created.
658
670
  // This is to prevent an issue where a negative cache-hit
659
671
  // causes the subdomain to be unavailable for 30 seconds.
660
- if (ctx.event === "create" || !ctx.output?.url) {
672
+ if (ctx.phase === "create" || !ctx.output?.url) {
661
673
  await new Promise((resolve) => setTimeout(resolve, 3000));
662
674
  }
663
675
  }