alchemy 2.0.0-beta.47 → 2.0.0-beta.49

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 (263) hide show
  1. package/bin/exec.js +28 -5
  2. package/bin/exec.js.map +1 -1
  3. package/lib/AWS/Lambda/BucketEventSource.d.ts +1 -1
  4. package/lib/AWS/Lambda/BucketEventSource.d.ts.map +1 -1
  5. package/lib/AWS/Lambda/EventBridgeEventSource.d.ts +1 -1
  6. package/lib/AWS/Lambda/EventBridgeEventSource.d.ts.map +1 -1
  7. package/lib/AWS/Lambda/QueueEventSource.d.ts +1 -1
  8. package/lib/AWS/Lambda/QueueEventSource.d.ts.map +1 -1
  9. package/lib/AWS/Lambda/StreamEventSource.d.ts +1 -1
  10. package/lib/AWS/Lambda/StreamEventSource.d.ts.map +1 -1
  11. package/lib/AWS/Lambda/TableEventSource.d.ts +1 -1
  12. package/lib/AWS/Lambda/TableEventSource.d.ts.map +1 -1
  13. package/lib/AWS/Lambda/TopicEventSource.d.ts +1 -1
  14. package/lib/AWS/Lambda/TopicEventSource.d.ts.map +1 -1
  15. package/lib/AdoptPolicy.js +1 -1
  16. package/lib/AdoptPolicy.js.map +1 -1
  17. package/lib/Binding.d.ts +5 -1
  18. package/lib/Binding.d.ts.map +1 -1
  19. package/lib/Binding.js.map +1 -1
  20. package/lib/Cli/commands/cloudflare.d.ts +4 -1
  21. package/lib/Cli/commands/cloudflare.d.ts.map +1 -1
  22. package/lib/Cli/commands/cloudflare.js +274 -1
  23. package/lib/Cli/commands/cloudflare.js.map +1 -1
  24. package/lib/Cloudflare/AiGateway/AiGateway.d.ts +1 -1
  25. package/lib/Cloudflare/AiGateway/AiGateway.d.ts.map +1 -1
  26. package/lib/Cloudflare/AiGateway/AiGateway.js +2 -1
  27. package/lib/Cloudflare/AiGateway/AiGateway.js.map +1 -1
  28. package/lib/Cloudflare/ApiToken/AccountApiToken.d.ts +40 -2
  29. package/lib/Cloudflare/ApiToken/AccountApiToken.d.ts.map +1 -1
  30. package/lib/Cloudflare/ApiToken/AccountApiToken.js +49 -6
  31. package/lib/Cloudflare/ApiToken/AccountApiToken.js.map +1 -1
  32. package/lib/Cloudflare/ApiToken/Common.d.ts +36 -4
  33. package/lib/Cloudflare/ApiToken/Common.d.ts.map +1 -1
  34. package/lib/Cloudflare/ApiToken/Common.js +8 -0
  35. package/lib/Cloudflare/ApiToken/Common.js.map +1 -1
  36. package/lib/Cloudflare/ApiToken/UserApiToken.d.ts +38 -2
  37. package/lib/Cloudflare/ApiToken/UserApiToken.d.ts.map +1 -1
  38. package/lib/Cloudflare/ApiToken/UserApiToken.js +47 -6
  39. package/lib/Cloudflare/ApiToken/UserApiToken.js.map +1 -1
  40. package/lib/Cloudflare/Browser/Browser.d.ts +121 -0
  41. package/lib/Cloudflare/Browser/Browser.d.ts.map +1 -0
  42. package/lib/Cloudflare/{BrowserRendering/BrowserRendering.js → Browser/Browser.js} +19 -26
  43. package/lib/Cloudflare/Browser/Browser.js.map +1 -0
  44. package/lib/Cloudflare/Browser/BrowserBinding.d.ts +55 -0
  45. package/lib/Cloudflare/Browser/BrowserBinding.d.ts.map +1 -0
  46. package/lib/Cloudflare/{BrowserRendering/BrowserRenderingBinding.js → Browser/BrowserBinding.js} +11 -11
  47. package/lib/Cloudflare/Browser/BrowserBinding.js.map +1 -0
  48. package/lib/Cloudflare/Browser/index.d.ts +3 -0
  49. package/lib/Cloudflare/Browser/index.d.ts.map +1 -0
  50. package/lib/Cloudflare/Browser/index.js +3 -0
  51. package/lib/Cloudflare/Browser/index.js.map +1 -0
  52. package/lib/Cloudflare/Container/ContainerBinding.d.ts +2 -1
  53. package/lib/Cloudflare/Container/ContainerBinding.d.ts.map +1 -1
  54. package/lib/Cloudflare/D1/D1Database.d.ts.map +1 -1
  55. package/lib/Cloudflare/D1/D1Database.js +2 -1
  56. package/lib/Cloudflare/D1/D1Database.js.map +1 -1
  57. package/lib/Cloudflare/Dns/DnsBinding.d.ts +52 -0
  58. package/lib/Cloudflare/Dns/DnsBinding.d.ts.map +1 -0
  59. package/lib/Cloudflare/Dns/DnsBinding.js +59 -0
  60. package/lib/Cloudflare/Dns/DnsBinding.js.map +1 -0
  61. package/lib/Cloudflare/Dns/DnsRead.d.ts +80 -0
  62. package/lib/Cloudflare/Dns/DnsRead.d.ts.map +1 -0
  63. package/lib/Cloudflare/Dns/DnsRead.js +70 -0
  64. package/lib/Cloudflare/Dns/DnsRead.js.map +1 -0
  65. package/lib/Cloudflare/Dns/DnsReadWrite.d.ts +79 -0
  66. package/lib/Cloudflare/Dns/DnsReadWrite.d.ts.map +1 -0
  67. package/lib/Cloudflare/Dns/DnsReadWrite.js +72 -0
  68. package/lib/Cloudflare/Dns/DnsReadWrite.js.map +1 -0
  69. package/lib/Cloudflare/Dns/DnsWrite.d.ts +113 -0
  70. package/lib/Cloudflare/Dns/DnsWrite.d.ts.map +1 -0
  71. package/lib/Cloudflare/Dns/DnsWrite.js +100 -0
  72. package/lib/Cloudflare/Dns/DnsWrite.js.map +1 -0
  73. package/lib/Cloudflare/Dns/index.d.ts +5 -0
  74. package/lib/Cloudflare/Dns/index.d.ts.map +1 -0
  75. package/lib/Cloudflare/Dns/index.js +5 -0
  76. package/lib/Cloudflare/Dns/index.js.map +1 -0
  77. package/lib/Cloudflare/Email/EmailRouting.d.ts +1 -1
  78. package/lib/Cloudflare/Email/EmailRouting.d.ts.map +1 -1
  79. package/lib/Cloudflare/Email/EmailRouting.js +1 -1
  80. package/lib/Cloudflare/Email/EmailRouting.js.map +1 -1
  81. package/lib/Cloudflare/Email/EmailRule.d.ts +1 -1
  82. package/lib/Cloudflare/Email/EmailRule.d.ts.map +1 -1
  83. package/lib/Cloudflare/Email/EmailRule.js +1 -1
  84. package/lib/Cloudflare/Email/EmailRule.js.map +1 -1
  85. package/lib/Cloudflare/Hyperdrive/Hyperdrive.d.ts +1 -1
  86. package/lib/Cloudflare/Images/Images.d.ts +32 -23
  87. package/lib/Cloudflare/Images/Images.d.ts.map +1 -1
  88. package/lib/Cloudflare/Images/Images.js +10 -13
  89. package/lib/Cloudflare/Images/Images.js.map +1 -1
  90. package/lib/Cloudflare/KV/KVNamespace.d.ts +1 -1
  91. package/lib/Cloudflare/Providers.d.ts.map +1 -1
  92. package/lib/Cloudflare/Providers.js +14 -3
  93. package/lib/Cloudflare/Providers.js.map +1 -1
  94. package/lib/Cloudflare/Queue/Queue.d.ts +1 -1
  95. package/lib/Cloudflare/R2/R2Bucket.d.ts +2 -2
  96. package/lib/Cloudflare/R2/R2Bucket.d.ts.map +1 -1
  97. package/lib/Cloudflare/R2/R2Bucket.js +13 -7
  98. package/lib/Cloudflare/R2/R2Bucket.js.map +1 -1
  99. package/lib/Cloudflare/RateLimit/RateLimit.d.ts +132 -0
  100. package/lib/Cloudflare/RateLimit/RateLimit.d.ts.map +1 -0
  101. package/lib/Cloudflare/RateLimit/RateLimit.js +80 -0
  102. package/lib/Cloudflare/RateLimit/RateLimit.js.map +1 -0
  103. package/lib/Cloudflare/RateLimit/RateLimitBinding.d.ts +30 -0
  104. package/lib/Cloudflare/RateLimit/RateLimitBinding.d.ts.map +1 -0
  105. package/lib/Cloudflare/RateLimit/RateLimitBinding.js +50 -0
  106. package/lib/Cloudflare/RateLimit/RateLimitBinding.js.map +1 -0
  107. package/lib/Cloudflare/RateLimit/index.d.ts +3 -0
  108. package/lib/Cloudflare/RateLimit/index.d.ts.map +1 -0
  109. package/lib/Cloudflare/RateLimit/index.js +3 -0
  110. package/lib/Cloudflare/RateLimit/index.js.map +1 -0
  111. package/lib/Cloudflare/SecretsStore/Secret.d.ts +3 -2
  112. package/lib/Cloudflare/SecretsStore/Secret.d.ts.map +1 -1
  113. package/lib/Cloudflare/SecretsStore/Secret.js +7 -4
  114. package/lib/Cloudflare/SecretsStore/Secret.js.map +1 -1
  115. package/lib/Cloudflare/StateStore/Api.d.ts +1 -1
  116. package/lib/Cloudflare/Tunnel/Tunnel.d.ts +27 -0
  117. package/lib/Cloudflare/Tunnel/Tunnel.d.ts.map +1 -1
  118. package/lib/Cloudflare/Tunnel/Tunnel.js +27 -0
  119. package/lib/Cloudflare/Tunnel/Tunnel.js.map +1 -1
  120. package/lib/Cloudflare/Tunnel/TunnelBinding.d.ts +48 -0
  121. package/lib/Cloudflare/Tunnel/TunnelBinding.d.ts.map +1 -0
  122. package/lib/Cloudflare/Tunnel/TunnelBinding.js +56 -0
  123. package/lib/Cloudflare/Tunnel/TunnelBinding.js.map +1 -0
  124. package/lib/Cloudflare/Tunnel/TunnelRead.d.ts +83 -0
  125. package/lib/Cloudflare/Tunnel/TunnelRead.d.ts.map +1 -0
  126. package/lib/Cloudflare/Tunnel/TunnelRead.js +79 -0
  127. package/lib/Cloudflare/Tunnel/TunnelRead.js.map +1 -0
  128. package/lib/Cloudflare/Tunnel/TunnelReadWrite.d.ts +70 -0
  129. package/lib/Cloudflare/Tunnel/TunnelReadWrite.d.ts.map +1 -0
  130. package/lib/Cloudflare/Tunnel/TunnelReadWrite.js +63 -0
  131. package/lib/Cloudflare/Tunnel/TunnelReadWrite.js.map +1 -0
  132. package/lib/Cloudflare/Tunnel/TunnelWrite.d.ts +92 -0
  133. package/lib/Cloudflare/Tunnel/TunnelWrite.d.ts.map +1 -0
  134. package/lib/Cloudflare/Tunnel/TunnelWrite.js +88 -0
  135. package/lib/Cloudflare/Tunnel/TunnelWrite.js.map +1 -0
  136. package/lib/Cloudflare/Tunnel/index.d.ts +4 -0
  137. package/lib/Cloudflare/Tunnel/index.d.ts.map +1 -1
  138. package/lib/Cloudflare/Tunnel/index.js +4 -0
  139. package/lib/Cloudflare/Tunnel/index.js.map +1 -1
  140. package/lib/Cloudflare/Vectorize/VectorizeIndex.d.ts +1 -1
  141. package/lib/Cloudflare/Vectorize/VectorizeIndex.js +1 -0
  142. package/lib/Cloudflare/Vectorize/VectorizeIndex.js.map +1 -1
  143. package/lib/Cloudflare/VpcService/VpcService.d.ts +1 -1
  144. package/lib/Cloudflare/VpcService/VpcService.d.ts.map +1 -1
  145. package/lib/Cloudflare/VpcService/VpcService.js.map +1 -1
  146. package/lib/Cloudflare/Workers/DynamicWorkerLoader.d.ts +119 -11
  147. package/lib/Cloudflare/Workers/DynamicWorkerLoader.d.ts.map +1 -1
  148. package/lib/Cloudflare/Workers/DynamicWorkerLoader.js +88 -9
  149. package/lib/Cloudflare/Workers/DynamicWorkerLoader.js.map +1 -1
  150. package/lib/Cloudflare/Workers/InferEnv.d.ts +1 -1
  151. package/lib/Cloudflare/Workers/InferEnv.d.ts.map +1 -1
  152. package/lib/Cloudflare/Workers/LocalWorkerProvider.js +2 -0
  153. package/lib/Cloudflare/Workers/LocalWorkerProvider.js.map +1 -1
  154. package/lib/Cloudflare/Workers/WorkerAsyncBindings.d.ts.map +1 -1
  155. package/lib/Cloudflare/Workers/WorkerAsyncBindings.js +18 -2
  156. package/lib/Cloudflare/Workers/WorkerAsyncBindings.js.map +1 -1
  157. package/lib/Cloudflare/Workers/WorkerBinding.d.ts +7 -5
  158. package/lib/Cloudflare/Workers/WorkerBinding.d.ts.map +1 -1
  159. package/lib/Cloudflare/Workers/WorkerBinding.js +1 -1
  160. package/lib/Cloudflare/Workers/WorkerBinding.js.map +1 -1
  161. package/lib/Cloudflare/Zaraz/ZarazConfig.d.ts +1 -1
  162. package/lib/Cloudflare/Zaraz/ZarazConfig.d.ts.map +1 -1
  163. package/lib/Cloudflare/Zaraz/ZarazConfig.js +1 -1
  164. package/lib/Cloudflare/Zaraz/ZarazConfig.js.map +1 -1
  165. package/lib/Cloudflare/Zone/Zone.d.ts +170 -0
  166. package/lib/Cloudflare/Zone/Zone.d.ts.map +1 -0
  167. package/lib/Cloudflare/Zone/Zone.js +180 -0
  168. package/lib/Cloudflare/Zone/Zone.js.map +1 -0
  169. package/lib/Cloudflare/Zone/index.d.ts +3 -0
  170. package/lib/Cloudflare/Zone/index.d.ts.map +1 -0
  171. package/lib/Cloudflare/Zone/index.js +3 -0
  172. package/lib/Cloudflare/Zone/index.js.map +1 -0
  173. package/lib/Cloudflare/Zone/lookup.d.ts +33 -0
  174. package/lib/Cloudflare/Zone/lookup.d.ts.map +1 -0
  175. package/lib/Cloudflare/{Zone.js → Zone/lookup.js} +2 -2
  176. package/lib/Cloudflare/Zone/lookup.js.map +1 -0
  177. package/lib/Cloudflare/index.d.ts +4 -1
  178. package/lib/Cloudflare/index.d.ts.map +1 -1
  179. package/lib/Cloudflare/index.js +4 -1
  180. package/lib/Cloudflare/index.js.map +1 -1
  181. package/lib/Output.d.ts +13 -1
  182. package/lib/Output.d.ts.map +1 -1
  183. package/lib/Output.js +43 -11
  184. package/lib/Output.js.map +1 -1
  185. package/lib/Plan.d.ts.map +1 -1
  186. package/lib/Plan.js +8 -0
  187. package/lib/Plan.js.map +1 -1
  188. package/lib/Platform.d.ts +5 -5
  189. package/lib/Platform.d.ts.map +1 -1
  190. package/lib/Platform.js +5 -2
  191. package/lib/Platform.js.map +1 -1
  192. package/lib/RemovalPolicy.js +2 -2
  193. package/lib/RemovalPolicy.js.map +1 -1
  194. package/lib/Resource.d.ts +15 -2
  195. package/lib/Resource.d.ts.map +1 -1
  196. package/lib/Resource.js +3 -2
  197. package/lib/Resource.js.map +1 -1
  198. package/lib/tsconfig.test.tsbuildinfo +1 -1
  199. package/package.json +7 -7
  200. package/src/AdoptPolicy.ts +1 -1
  201. package/src/Binding.ts +5 -1
  202. package/src/Cli/commands/cloudflare.ts +394 -1
  203. package/src/Cloudflare/AiGateway/AiGateway.ts +2 -1
  204. package/src/Cloudflare/ApiToken/AccountApiToken.ts +60 -7
  205. package/src/Cloudflare/ApiToken/Common.ts +45 -6
  206. package/src/Cloudflare/ApiToken/UserApiToken.ts +58 -7
  207. package/src/Cloudflare/Browser/Browser.ts +140 -0
  208. package/src/Cloudflare/Browser/BrowserBinding.ts +128 -0
  209. package/src/Cloudflare/Browser/index.ts +11 -0
  210. package/src/Cloudflare/D1/D1Database.ts +6 -2
  211. package/src/Cloudflare/Dns/DnsBinding.ts +123 -0
  212. package/src/Cloudflare/Dns/DnsRead.ts +129 -0
  213. package/src/Cloudflare/Dns/DnsReadWrite.ts +99 -0
  214. package/src/Cloudflare/Dns/DnsWrite.ts +209 -0
  215. package/src/Cloudflare/Dns/index.ts +4 -0
  216. package/src/Cloudflare/Email/EmailRouting.ts +1 -1
  217. package/src/Cloudflare/Email/EmailRule.ts +6 -6
  218. package/src/Cloudflare/Images/Images.ts +40 -27
  219. package/src/Cloudflare/Providers.ts +22 -3
  220. package/src/Cloudflare/R2/R2Bucket.ts +17 -9
  221. package/src/Cloudflare/RateLimit/RateLimit.ts +160 -0
  222. package/src/Cloudflare/RateLimit/RateLimitBinding.ts +90 -0
  223. package/src/Cloudflare/RateLimit/index.ts +2 -0
  224. package/src/Cloudflare/SecretsStore/Secret.ts +12 -6
  225. package/src/Cloudflare/Tunnel/Tunnel.ts +27 -0
  226. package/src/Cloudflare/Tunnel/TunnelBinding.ts +114 -0
  227. package/src/Cloudflare/Tunnel/TunnelRead.ts +168 -0
  228. package/src/Cloudflare/Tunnel/TunnelReadWrite.ts +88 -0
  229. package/src/Cloudflare/Tunnel/TunnelWrite.ts +194 -0
  230. package/src/Cloudflare/Tunnel/index.ts +4 -0
  231. package/src/Cloudflare/Vectorize/VectorizeIndex.ts +2 -1
  232. package/src/Cloudflare/VpcService/VpcService.ts +4 -2
  233. package/src/Cloudflare/Workers/DynamicWorkerLoader.ts +139 -13
  234. package/src/Cloudflare/Workers/InferEnv.ts +19 -15
  235. package/src/Cloudflare/Workers/LocalWorkerProvider.ts +13 -2
  236. package/src/Cloudflare/Workers/WorkerAsyncBindings.ts +16 -2
  237. package/src/Cloudflare/Workers/WorkerBinding.ts +6 -2
  238. package/src/Cloudflare/Zaraz/ZarazConfig.ts +1 -1
  239. package/src/Cloudflare/Zone/Zone.ts +349 -0
  240. package/src/Cloudflare/Zone/index.ts +2 -0
  241. package/src/Cloudflare/{Zone.ts → Zone/lookup.ts} +16 -3
  242. package/src/Cloudflare/index.ts +4 -1
  243. package/src/Output.ts +67 -11
  244. package/src/Plan.ts +9 -0
  245. package/src/Platform.ts +19 -15
  246. package/src/RemovalPolicy.ts +2 -2
  247. package/src/Resource.ts +19 -2
  248. package/lib/Cloudflare/BrowserRendering/BrowserRendering.d.ts +0 -116
  249. package/lib/Cloudflare/BrowserRendering/BrowserRendering.d.ts.map +0 -1
  250. package/lib/Cloudflare/BrowserRendering/BrowserRendering.js.map +0 -1
  251. package/lib/Cloudflare/BrowserRendering/BrowserRenderingBinding.d.ts +0 -55
  252. package/lib/Cloudflare/BrowserRendering/BrowserRenderingBinding.d.ts.map +0 -1
  253. package/lib/Cloudflare/BrowserRendering/BrowserRenderingBinding.js.map +0 -1
  254. package/lib/Cloudflare/BrowserRendering/index.d.ts +0 -3
  255. package/lib/Cloudflare/BrowserRendering/index.d.ts.map +0 -1
  256. package/lib/Cloudflare/BrowserRendering/index.js +0 -3
  257. package/lib/Cloudflare/BrowserRendering/index.js.map +0 -1
  258. package/lib/Cloudflare/Zone.d.ts +0 -14
  259. package/lib/Cloudflare/Zone.d.ts.map +0 -1
  260. package/lib/Cloudflare/Zone.js.map +0 -1
  261. package/src/Cloudflare/BrowserRendering/BrowserRendering.ts +0 -132
  262. package/src/Cloudflare/BrowserRendering/BrowserRenderingBinding.ts +0 -137
  263. package/src/Cloudflare/BrowserRendering/index.ts +0 -11
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "alchemy",
3
- "version": "2.0.0-beta.47",
3
+ "version": "2.0.0-beta.49",
4
4
  "homepage": "https://alchemy.run",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Sam Goodwin <sam@alchemy.run>",
@@ -291,15 +291,15 @@
291
291
  "@alchemy.run/node-utils": "0.0.4",
292
292
  "@aws-sdk/credential-providers": "^3.0.0",
293
293
  "@clack/prompts": "^0.11.0",
294
- "@distilled.cloud/aws": "^0.22.2",
295
- "@distilled.cloud/axiom": "^0.22.2",
296
- "@distilled.cloud/cloudflare": "^0.22.2",
294
+ "@distilled.cloud/aws": "^0.22.4",
295
+ "@distilled.cloud/axiom": "^0.22.4",
296
+ "@distilled.cloud/cloudflare": "^0.22.4",
297
297
  "@distilled.cloud/cloudflare-rolldown-plugin": "0.10.1",
298
298
  "@distilled.cloud/cloudflare-runtime": "0.10.1",
299
299
  "@distilled.cloud/cloudflare-vite-plugin": "0.10.1",
300
- "@distilled.cloud/core": "^0.22.2",
301
- "@distilled.cloud/neon": "^0.22.2",
302
- "@distilled.cloud/planetscale": "^0.22.2",
300
+ "@distilled.cloud/core": "^0.22.4",
301
+ "@distilled.cloud/neon": "^0.22.4",
302
+ "@distilled.cloud/planetscale": "^0.22.4",
303
303
  "@effect/vitest": ">=4.0.0-beta.74 || >=4.0.0",
304
304
  "@libsql/client": "^0.17.0",
305
305
  "@octokit/rest": "^22.0.1",
@@ -139,7 +139,7 @@ export const adopt: {
139
139
  ): <A, E, R2 = never>(
140
140
  effect: Effect.Effect<A, E, R2>,
141
141
  ) => Effect.Effect<A, E, R1 | R2>;
142
- } = ((enabled: boolean | Effect.Effect<boolean, never, any>) =>
142
+ } = ((enabled: boolean | Effect.Effect<boolean, never, any> = true) =>
143
143
  (eff: Effect.Effect<any, any, any>) =>
144
144
  eff.pipe(
145
145
  typeof enabled === "boolean"
package/src/Binding.ts CHANGED
@@ -3,6 +3,7 @@ import * as Effect from "effect/Effect";
3
3
  import * as Layer from "effect/Layer";
4
4
  import * as Option from "effect/Option";
5
5
  import { SingleShotGen } from "effect/Utils";
6
+ import type { Input } from "./Input.ts";
6
7
  import * as Namespace from "./Namespace.ts";
7
8
  import { ALCHEMY_PHASE } from "./Phase.ts";
8
9
  import { tryFindProviderByType } from "./Provider.ts";
@@ -42,7 +43,10 @@ type BindParameters<
42
43
  Parameters extends any[],
43
44
  Req = never,
44
45
  > = Parameters extends [infer First, ...infer Rest]
45
- ? [First | Effect.Effect<First, never, Req>, ...BindParameters<Rest, Req>]
46
+ ? [
47
+ Input<First> | Effect.Effect<First, never, Req>,
48
+ ...BindParameters<Rest, Req>,
49
+ ]
46
50
  : [];
47
51
 
48
52
  /**
@@ -1,3 +1,7 @@
1
+ import * as cfAccounts from "@distilled.cloud/cloudflare/accounts";
2
+ import { apiKeyCredentials } from "@distilled.cloud/cloudflare/Credentials";
3
+ import * as user from "@distilled.cloud/cloudflare/user";
4
+ import * as Config from "effect/Config";
1
5
  import * as ConfigProvider from "effect/ConfigProvider";
2
6
  import * as Console from "effect/Console";
3
7
  import * as Effect from "effect/Effect";
@@ -6,6 +10,7 @@ import * as Logger from "effect/Logger";
6
10
  import * as Option from "effect/Option";
7
11
  import * as Stream from "effect/Stream";
8
12
  import { Command, Flag } from "effect/unstable/cli";
13
+ import * as HttpClient from "effect/unstable/http/HttpClient";
9
14
 
10
15
  import { AuthProviders } from "../../Auth/AuthProvider.ts";
11
16
  import { withProfileOverride } from "../../Auth/Profile.ts";
@@ -16,6 +21,7 @@ import * as CloudflareCredentials from "../../Cloudflare/Credentials.ts";
16
21
  import { CloudflareLogs } from "../../Cloudflare/Logs.ts";
17
22
  import { STATE_STORE_SCRIPT_NAME } from "../../Cloudflare/StateStore/Api.ts";
18
23
  import { bootstrap as bootstrapCloudflare } from "../../Cloudflare/StateStore/State.ts";
24
+ import * as Clank from "../../Util/Clank.ts";
19
25
  import { loadConfigProvider } from "../../Util/ConfigProvider.ts";
20
26
  import { fileLogger } from "../../Util/FileLogger.ts";
21
27
 
@@ -110,6 +116,393 @@ const bootstrapCommand = Command.make(
110
116
  ),
111
117
  );
112
118
 
119
+ /**
120
+ * A single resolved Cloudflare token policy in the shape expected by
121
+ * `POST /user/tokens`.
122
+ */
123
+ type CreateTokenPolicy = {
124
+ effect: "allow";
125
+ permissionGroups: { id: string }[];
126
+ resources: Record<string, unknown>;
127
+ };
128
+
129
+ /**
130
+ * Curated set of permission groups granted by `create-token` when
131
+ * `--all-permissions` is NOT passed. Covers the services Alchemy commonly
132
+ * deploys (Workers + storage + zone/DNS) without handing out a god token.
133
+ * Names are matched against the account's live permission groups; any name
134
+ * not available for the account is silently skipped.
135
+ */
136
+ const ALCHEMY_DEFAULT_PERMISSION_GROUP_NAMES: ReadonlySet<string> = new Set([
137
+ "Account Settings Read",
138
+ "Workers Scripts Read",
139
+ "Workers Scripts Write",
140
+ "Workers KV Storage Read",
141
+ "Workers KV Storage Write",
142
+ "Workers R2 Storage Read",
143
+ "Workers R2 Storage Write",
144
+ "Workers Routes Read",
145
+ "Workers Routes Write",
146
+ "Workers Tail Read",
147
+ "Workers Observability Read",
148
+ "Workers Observability Write",
149
+ "Workers AI Read",
150
+ "Workers AI Write",
151
+ "D1 Read",
152
+ "D1 Write",
153
+ "Queues Read",
154
+ "Queues Write",
155
+ "Hyperdrive Read",
156
+ "Hyperdrive Write",
157
+ "Pages Read",
158
+ "Pages Write",
159
+ "Vectorize Read",
160
+ "Vectorize Write",
161
+ "Pipelines Read",
162
+ "Pipelines Write",
163
+ "AI Gateway Read",
164
+ "AI Gateway Run",
165
+ "AI Gateway Write",
166
+ "Browser Rendering Read",
167
+ "Browser Rendering Write",
168
+ "Images Read",
169
+ "Images Write",
170
+ "Stream Read",
171
+ "Stream Write",
172
+ "Secrets Store Read",
173
+ "Secrets Store Write",
174
+ "DNS Read",
175
+ "DNS Write",
176
+ "Zone Read",
177
+ "Zone Settings Read",
178
+ "Zone Settings Write",
179
+ "SSL and Certificates Read",
180
+ "SSL and Certificates Write",
181
+ ]);
182
+
183
+ /**
184
+ * Group permission groups by their Cloudflare scope and produce one policy
185
+ * per scope, wiring up the right resource selector for each:
186
+ *
187
+ * - `com.cloudflare.api.account` → scoped to the account ID
188
+ * - `com.cloudflare.api.account.zone` → all zones (`*`)
189
+ * - `com.cloudflare.edge.r2.bucket` → all buckets (`*`)
190
+ *
191
+ * Mirrors the upstream `alchemy` "god token" policy shape. Groups with an
192
+ * unrecognized scope are skipped, and empty policies are dropped.
193
+ */
194
+ const buildTokenPolicies = (
195
+ accountId: string,
196
+ groups: readonly { id: string; scopes: readonly string[] }[],
197
+ ): CreateTokenPolicy[] => {
198
+ const buckets: Record<string, CreateTokenPolicy> = {
199
+ "com.cloudflare.api.account": {
200
+ effect: "allow",
201
+ permissionGroups: [],
202
+ resources: { [`com.cloudflare.api.account.${accountId}`]: "*" },
203
+ },
204
+ "com.cloudflare.api.account.zone": {
205
+ effect: "allow",
206
+ permissionGroups: [],
207
+ resources: { "com.cloudflare.api.account.zone.*": "*" },
208
+ },
209
+ "com.cloudflare.edge.r2.bucket": {
210
+ effect: "allow",
211
+ permissionGroups: [],
212
+ resources: { "com.cloudflare.edge.r2.bucket.*": "*" },
213
+ },
214
+ };
215
+ const seen = new Set<string>();
216
+ for (const group of groups) {
217
+ const bucket = buckets[group.scopes[0]!];
218
+ if (!bucket || seen.has(group.id)) continue;
219
+ seen.add(group.id);
220
+ bucket.permissionGroups.push({ id: group.id });
221
+ }
222
+ return Object.values(buckets).filter((p) => p.permissionGroups.length > 0);
223
+ };
224
+
225
+ /**
226
+ * Let the user pick which Cloudflare account the token is scoped to. Lists
227
+ * the accounts visible to the configured credentials and prompts a selection
228
+ * (defaulting the cursor to the profile's account). If there's exactly one
229
+ * account, it's used without prompting; if the API returns none, falls back
230
+ * to the profile's account.
231
+ */
232
+ const selectAccountId = (defaultAccountId: string | undefined) =>
233
+ Effect.gen(function* () {
234
+ const list = yield* cfAccounts.listAccounts;
235
+ const response = yield* list({});
236
+ const accounts = response.result;
237
+
238
+ if (accounts.length === 0) {
239
+ if (defaultAccountId) return defaultAccountId;
240
+ return yield* Effect.die(
241
+ "No Cloudflare accounts found for these credentials.",
242
+ );
243
+ }
244
+ if (accounts.length === 1) {
245
+ const account = accounts[0]!;
246
+ yield* Clank.info(`Using account: ${account.name} (${account.id})`);
247
+ return account.id;
248
+ }
249
+ return yield* Clank.select({
250
+ message: "Select a Cloudflare account",
251
+ initialValue: defaultAccountId,
252
+ options: accounts.map((a) => ({
253
+ value: a.id,
254
+ label: a.name,
255
+ hint: a.id === defaultAccountId ? `${a.id} (current profile)` : a.id,
256
+ })),
257
+ });
258
+ });
259
+
260
+ const allPermissionsFlag = Flag.boolean("all-permissions").pipe(
261
+ Flag.withDescription(
262
+ "Grant the token EVERY Cloudflare permission group (a 'god token'). " +
263
+ "Use with care — it has full access to your account.",
264
+ ),
265
+ Flag.withDefault(false),
266
+ );
267
+
268
+ const tokenNameFlag = Flag.string("name").pipe(
269
+ Flag.withDescription(
270
+ "Name for the API token. Defaults to 'alchemy' (or 'alchemy-all-permissions').",
271
+ ),
272
+ Flag.optional,
273
+ Flag.map(Option.getOrUndefined),
274
+ );
275
+
276
+ const tokenAccountIdFlag = Flag.string("account-id").pipe(
277
+ Flag.withDescription(
278
+ "Cloudflare account ID to scope the token to. Defaults to the profile's account.",
279
+ ),
280
+ Flag.optional,
281
+ Flag.map(Option.getOrUndefined),
282
+ );
283
+
284
+ /**
285
+ * `alchemy cloudflare create-token` — mint a Cloudflare API token
286
+ * (`POST /user/tokens`).
287
+ *
288
+ * This command is **standalone**: it does not use an Alchemy auth profile.
289
+ * Cloudflare only mints a token whose permissions the authenticating
290
+ * credential is allowed to grant — and OAuth/scoped tokens silently produce a
291
+ * token with zero permissions — so it always authenticates with the account's
292
+ * **Global API Key** (read from `CLOUDFLARE_API_KEY` / `CLOUDFLARE_EMAIL`,
293
+ * otherwise prompted). The key is used only to create the token and is never
294
+ * stored.
295
+ *
296
+ * With `--all-permissions` it builds a "superuser" token spanning every
297
+ * permission group (after a confirmation prompt). Otherwise it grants a
298
+ * curated set covering the services Alchemy commonly deploys.
299
+ */
300
+ const createTokenCommand = Command.make(
301
+ "create-token",
302
+ {
303
+ envFile,
304
+ allPermissions: allPermissionsFlag,
305
+ name: tokenNameFlag,
306
+ accountId: tokenAccountIdFlag,
307
+ },
308
+ instrumentCommand(
309
+ "cloudflare.create-token",
310
+ (a: { allPermissions: boolean }) => ({
311
+ "alchemy.all_permissions": a.allPermissions,
312
+ }),
313
+ )(
314
+ Effect.fnUntraced(function* ({ envFile, allPermissions, name, accountId }) {
315
+ const configProvider = ConfigProvider.layer(
316
+ yield* loadConfigProvider(envFile),
317
+ );
318
+
319
+ yield* Effect.gen(function* () {
320
+ // The Global API Key is a dashboard-only secret — there is no API to
321
+ // fetch it — so read it from the environment or prompt for it. It is
322
+ // used only to create the token and is never stored.
323
+ const envApiKey = yield* Config.string("CLOUDFLARE_API_KEY").pipe(
324
+ Config.option,
325
+ Config.map(Option.getOrUndefined),
326
+ );
327
+ const apiKey =
328
+ envApiKey ??
329
+ (yield* Clank.password({
330
+ message:
331
+ "Paste your Global API Key (see bottom of https://dash.cloudflare.com/profile/api-tokens)",
332
+ validate: (v) => (v.trim().length === 0 ? "Required" : undefined),
333
+ }));
334
+
335
+ const envEmail = yield* Config.string("CLOUDFLARE_EMAIL").pipe(
336
+ Config.option,
337
+ Config.map(Option.getOrUndefined),
338
+ );
339
+ const email =
340
+ envEmail ??
341
+ (yield* Clank.text({
342
+ message: "Cloudflare account email",
343
+ validate: (v) => (v.trim().length === 0 ? "Required" : undefined),
344
+ }));
345
+
346
+ const credentials = Effect.succeed(
347
+ apiKeyCredentials({ apiKey, email }),
348
+ );
349
+ const withCreds = <A, E, R>(self: Effect.Effect<A, E, R>) =>
350
+ self.pipe(
351
+ Effect.provideService(
352
+ CloudflareCredentials.Credentials,
353
+ credentials,
354
+ ),
355
+ );
356
+
357
+ const resolvedAccountId =
358
+ accountId ?? (yield* withCreds(selectAccountId(undefined)));
359
+
360
+ const tokenName =
361
+ name ??
362
+ (yield* Clank.text({
363
+ message: "Token name",
364
+ placeholder: allPermissions ? "alchemy-superuser" : "alchemy",
365
+ validate: (v) =>
366
+ v.trim().length === 0 ? "Token name is required" : undefined,
367
+ }));
368
+
369
+ // Resolve permission groups live from Cloudflare instead of a static
370
+ // catalog. Cloudflare silently ignores permission-group IDs it doesn't
371
+ // recognize, so a stale local list yields a token with zero
372
+ // permissions. `/user/tokens/permission_groups` returns exactly the
373
+ // groups (and IDs) valid for this credential.
374
+ //
375
+ // We hit the endpoint with a raw GET rather than the typed distilled
376
+ // client: the client hard-codes the set of valid `scopes` literals,
377
+ // and Cloudflare keeps adding new ones (e.g. `com.cloudflare.edge.
378
+ // worker.script`), which makes the strict schema reject the whole
379
+ // response. Parsing leniently keeps us forward-compatible.
380
+ const http = yield* HttpClient.HttpClient;
381
+ const pgResponse = yield* http.get(
382
+ "https://api.cloudflare.com/client/v4/user/tokens/permission_groups",
383
+ {
384
+ headers: {
385
+ "X-Auth-Key": apiKey,
386
+ "X-Auth-Email": email,
387
+ Accept: "application/json",
388
+ },
389
+ },
390
+ );
391
+ const pgBody = (yield* pgResponse.json) as {
392
+ result?: { id?: string; name?: string; scopes?: string[] }[];
393
+ };
394
+ const liveGroups = (pgBody.result ?? []).flatMap((g) =>
395
+ g.id && g.scopes && g.scopes.length > 0
396
+ ? [{ id: g.id, name: g.name ?? "", scopes: g.scopes }]
397
+ : [],
398
+ );
399
+
400
+ const selected = allPermissions
401
+ ? liveGroups
402
+ : liveGroups.filter((g) =>
403
+ ALCHEMY_DEFAULT_PERMISSION_GROUP_NAMES.has(g.name),
404
+ );
405
+ const policies = buildTokenPolicies(resolvedAccountId, selected);
406
+
407
+ if (policies.length === 0) {
408
+ return yield* Effect.die(
409
+ "No permission groups resolved for this account; cannot create a token.",
410
+ );
411
+ }
412
+
413
+ if (allPermissions) {
414
+ yield* Clank.warn(
415
+ "This token will have FULL access to your Cloudflare account. " +
416
+ "Keep it secret — anyone with it can control your account.",
417
+ );
418
+ const ok = yield* Clank.confirm({
419
+ message: "Create a superuser token with all permissions?",
420
+ initialValue: false,
421
+ });
422
+ if (!ok) {
423
+ yield* Console.log("Cancelled.");
424
+ return;
425
+ }
426
+ }
427
+
428
+ const result = yield* withCreds(
429
+ user.createToken({ name: tokenName, policies }),
430
+ );
431
+
432
+ if (!result.value) {
433
+ return yield* Effect.die(
434
+ "Cloudflare did not return a token value. Try again.",
435
+ );
436
+ }
437
+
438
+ // Cloudflare echoes back the policies it actually accepted. It silently
439
+ // drops permission groups the authenticating user isn't allowed to
440
+ // grant — so a token can come back with zero permissions even though
441
+ // the request was well-formed. Count what was granted and warn loudly
442
+ // if it's empty (almost always an account-role problem, not a bug).
443
+ const granted = (result.policies ?? []).reduce(
444
+ (n, p) => n + (p.permissionGroups?.length ?? 0),
445
+ 0,
446
+ );
447
+
448
+ // Verify the freshly minted token actually authenticates. The
449
+ // Cloudflare dashboard has a long-standing rendering bug where
450
+ // API-created tokens show a blank permission summary (and a disabled
451
+ // "View"), which makes a perfectly good token look empty. A live
452
+ // `/user/tokens/verify` is the source of truth.
453
+ const status = yield* http
454
+ .get("https://api.cloudflare.com/client/v4/user/tokens/verify", {
455
+ headers: {
456
+ Authorization: `Bearer ${result.value}`,
457
+ Accept: "application/json",
458
+ },
459
+ })
460
+ .pipe(
461
+ Effect.flatMap((r) => r.json),
462
+ Effect.map(
463
+ (b) => (b as { result?: { status?: string } }).result?.status,
464
+ ),
465
+ Effect.catch(() => Effect.succeed(undefined)),
466
+ );
467
+
468
+ yield* Console.log("");
469
+ yield* Console.log(
470
+ `Created Cloudflare API token "${result.name ?? tokenName}" (${result.id ?? "unknown id"}).`,
471
+ );
472
+ yield* Console.log(
473
+ `Granted ${granted} permission group(s) across ${result.policies?.length ?? 0} policy(ies)` +
474
+ (status ? `; token status: ${status}.` : "."),
475
+ );
476
+ yield* Console.log("");
477
+ yield* Console.log(result.value);
478
+ yield* Console.log("");
479
+ yield* Console.log(
480
+ "Store this value now — Cloudflare only shows it once. " +
481
+ "Use it as CLOUDFLARE_API_TOKEN.",
482
+ );
483
+
484
+ if (granted === 0) {
485
+ yield* Clank.warn(
486
+ "Cloudflare granted 0 permissions. A token can only carry permissions " +
487
+ "the authenticating user already holds, so this usually means the " +
488
+ "Global API Key's user is not a Super Administrator on the selected " +
489
+ "account. Check the user's role in the Cloudflare dashboard " +
490
+ "(Members) and retry with an owner/Super Administrator.",
491
+ );
492
+ } else {
493
+ // Token is good; preempt the "it looks empty" confusion.
494
+ yield* Clank.info(
495
+ "Heads up: the Cloudflare dashboard often renders API-created tokens " +
496
+ 'with a blank permission summary (and a greyed-out "View") — this is ' +
497
+ "a known UI bug, not a broken token. The permissions above are applied. " +
498
+ 'To see them in the dashboard, open the token and click "← Edit token".',
499
+ );
500
+ }
501
+ }).pipe(Effect.provide(configProvider));
502
+ }),
503
+ ),
504
+ );
505
+
113
506
  const tailFlag = Flag.boolean("tail").pipe(
114
507
  Flag.withDescription(
115
508
  "Stream logs in real time via the Cloudflare tail websocket instead of fetching past entries.",
@@ -220,5 +613,5 @@ const stateCommand = Command.make("state", {}).pipe(
220
613
  );
221
614
 
222
615
  export const cloudflareCommand = Command.make("cloudflare", {}).pipe(
223
- Command.withSubcommands([bootstrapCommand, stateCommand]),
616
+ Command.withSubcommands([bootstrapCommand, createTokenCommand, stateCommand]),
224
617
  );
@@ -448,7 +448,8 @@ export const AiGatewayProvider = () =>
448
448
  rateLimitingLimit: nullIfZero(gateway.rateLimitingLimit),
449
449
  rateLimitingTechnique: gateway.rateLimitingTechnique ?? "fixed",
450
450
  authentication: gateway.authentication ?? false,
451
- dlp: gateway.dlp ?? undefined,
451
+ // Distilled widened generated string enums to open unions (`string & {}`).
452
+ dlp: (gateway.dlp ?? undefined) as AiGatewayDlp | undefined,
452
453
  isDefault: gateway.isDefault ?? false,
453
454
  logManagement: gateway.logManagement ?? 100_000,
454
455
  logManagementStrategy:
@@ -9,9 +9,11 @@ import { CloudflareEnvironment } from "../CloudflareEnvironment.ts";
9
9
  import type { Providers } from "../Providers.ts";
10
10
  import {
11
11
  buildConditionPayload,
12
+ collectPolicies,
12
13
  conditionFingerprint,
13
14
  policyFingerprint,
14
15
  resolvePolicies,
16
+ type ApiTokenBinding,
15
17
  type ApiTokenProps,
16
18
  } from "./Common.ts";
17
19
 
@@ -30,7 +32,7 @@ export type AccountApiToken = Resource<
30
32
  value: Redacted.Redacted<string>;
31
33
  accountId: string;
32
34
  },
33
- never,
35
+ ApiTokenBinding,
34
36
  Providers
35
37
  >;
36
38
 
@@ -70,6 +72,44 @@ export type AccountApiToken = Resource<
70
72
  * value: token.value,
71
73
  * });
72
74
  * ```
75
+ *
76
+ * @section Attaching Policies via Bindings
77
+ * @example Let a downstream capability contribute its own policies
78
+ * A token can be created with no `policies` of its own; the policies are
79
+ * supplied through its binding contract (see {@link ApiTokenBinding}). This is
80
+ * how capabilities like {@link CreateTunnel} provision a least-privilege token.
81
+ * ```typescript
82
+ * const token = yield* Cloudflare.AccountApiToken("scoped-token");
83
+ *
84
+ * yield* token.bind("MyCapability", {
85
+ * policies: [
86
+ * {
87
+ * effect: "allow",
88
+ * permissionGroups: ["Cloudflare Tunnel Write"],
89
+ * resources: { [`com.cloudflare.api.account.${accountId}`]: "*" },
90
+ * },
91
+ * ],
92
+ * });
93
+ * ```
94
+ *
95
+ * @section Exposing a Token to a Worker
96
+ * @example Read the token value at runtime
97
+ * Bind the token's outputs in the Worker's Init phase to get runtime
98
+ * accessors. Binding `token.value` injects it as a `secret_text` Worker
99
+ * binding; the returned accessor reads it back (as `Redacted`) at runtime.
100
+ * ```typescript
101
+ * // init
102
+ * const value = yield* token.value; // Accessor<Redacted<string>>
103
+ * const accountId = yield* token.accountId; // Accessor<string>
104
+ *
105
+ * return {
106
+ * fetch: Effect.gen(function* () {
107
+ * const apiToken = yield* value; // Redacted<string>
108
+ * // ... call the Cloudflare API with `apiToken`
109
+ * return HttpServerResponse.text("ok");
110
+ * }),
111
+ * };
112
+ * ```
73
113
  */
74
114
  export const AccountApiToken = Resource<AccountApiToken>(
75
115
  "Cloudflare.AccountApiToken",
@@ -96,21 +136,25 @@ export const AccountApiTokenProvider = () =>
96
136
  tokenData: {
97
137
  id?: string | null;
98
138
  name?: string | null;
99
- status?: "active" | "disabled" | "expired" | null;
139
+ // Distilled widened generated string enums to open unions (`string & {}`).
140
+ status?: string | null;
100
141
  },
101
142
  value: Redacted.Redacted<string>,
102
143
  accountId: string,
103
144
  ): AccountApiTokenAttributes => ({
104
145
  tokenId: tokenData.id ?? "",
105
146
  name: tokenData.name ?? "",
106
- status: tokenData.status ?? "active",
147
+ status: (tokenData.status ?? "active") as
148
+ | "active"
149
+ | "disabled"
150
+ | "expired",
107
151
  value,
108
152
  accountId,
109
153
  });
110
154
 
111
155
  return {
112
156
  stables: ["tokenId", "accountId"],
113
- diff: Effect.fn(function* ({ id, olds, news, output }) {
157
+ diff: Effect.fn(function* ({ id, olds, news = {}, output }) {
114
158
  if (!isResolved(news)) return undefined;
115
159
  const newAccountId = news.accountId ?? defaultAccountId;
116
160
  const oldAccountId =
@@ -123,7 +167,9 @@ export const AccountApiTokenProvider = () =>
123
167
  const oldPolicyFp = policyFingerprint(
124
168
  resolvePolicies(olds?.policies ?? []),
125
169
  );
126
- const newPolicyFp = policyFingerprint(resolvePolicies(news.policies));
170
+ const newPolicyFp = policyFingerprint(
171
+ resolvePolicies(news.policies ?? []),
172
+ );
127
173
  const oldCondFp = conditionFingerprint(olds?.condition);
128
174
  const newCondFp = conditionFingerprint(news.condition);
129
175
  if (
@@ -136,10 +182,17 @@ export const AccountApiTokenProvider = () =>
136
182
  return { action: "update" } as const;
137
183
  }
138
184
  }),
139
- reconcile: Effect.fn(function* ({ id, news, output }) {
185
+ reconcile: Effect.fn(function* ({ id, news = {}, output, bindings }) {
140
186
  const accountId = news.accountId ?? defaultAccountId;
141
187
  const name = yield* resolveName(id, news.name);
142
- const policies = resolvePolicies(news.policies);
188
+ const collected = collectPolicies(news.policies, bindings);
189
+ if (collected.length === 0) {
190
+ return yield* Effect.die(
191
+ `Cloudflare requires at least one policy on token "${name}". ` +
192
+ "Pass `policies` or attach them via a binding.",
193
+ );
194
+ }
195
+ const policies = resolvePolicies(collected);
143
196
 
144
197
  // Observe — fetch current state if we already know the token id.
145
198
  // Cloudflare reports a deleted token as `InvalidRoute` or