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
@@ -27,10 +27,20 @@ export type ApiTokenPermissionGroupRef =
27
27
  | PermissionGroupName
28
28
  | { id: string; meta?: { key?: string; value?: string } };
29
29
 
30
+ /**
31
+ * Value of a resource entry in an {@link ApiTokenPolicy}. Usually `"*"`, but
32
+ * account-owned tokens must nest zone resources under the account resource —
33
+ * e.g. `{ "com.cloudflare.api.account.<id>": { "com.cloudflare.api.account.zone.*": "*" } }`
34
+ * — so a nested object is also allowed.
35
+ */
36
+ export type ApiTokenResourceScope =
37
+ | string
38
+ | { [K in ApiTokenResourceKey]?: string };
39
+
30
40
  export interface ApiTokenPolicy {
31
41
  effect: "allow" | "deny";
32
42
  permissionGroups: ApiTokenPermissionGroupRef[];
33
- resources: { [K in ApiTokenResourceKey]?: string };
43
+ resources: { [K in ApiTokenResourceKey]?: ApiTokenResourceScope };
34
44
  }
35
45
 
36
46
  export interface ApiTokenCondition {
@@ -52,10 +62,11 @@ export type ApiTokenProps = {
52
62
  */
53
63
  accountId?: string;
54
64
  /**
55
- * Access policies attached to the token. At least one policy is required
56
- * by Cloudflare.
65
+ * Access policies attached to the token. Cloudflare requires at least one
66
+ * policy on a token; if you omit `policies` here, the policies must instead
67
+ * be contributed by bindings (see {@link ApiTokenBinding}).
57
68
  */
58
- policies: ApiTokenPolicy[];
69
+ policies?: ApiTokenPolicy[];
59
70
  /** ISO 8601 expiration timestamp. */
60
71
  expiresOn?: string;
61
72
  /** ISO 8601 "not before" timestamp. */
@@ -64,6 +75,34 @@ export type ApiTokenProps = {
64
75
  condition?: ApiTokenCondition;
65
76
  };
66
77
 
78
+ /**
79
+ * Binding contract for {@link AccountApiToken} / {@link UserApiToken}.
80
+ *
81
+ * A binding contributes additional access policies to the token. This lets a
82
+ * downstream resource (e.g. a runtime capability that needs to call a specific
83
+ * Cloudflare API) create a token and attach exactly the policies it requires,
84
+ * without the token's owner having to enumerate them up front.
85
+ *
86
+ * Binding-contributed policies are merged with any `policies` passed directly
87
+ * as props; the union must contain at least one policy.
88
+ */
89
+ export type ApiTokenBinding = {
90
+ /** Access policies to attach to the token. */
91
+ policies?: ApiTokenPolicy[];
92
+ };
93
+
94
+ /**
95
+ * Collect the policies a token should be created with: those passed directly
96
+ * as props, plus those contributed by bindings.
97
+ */
98
+ export const collectPolicies = (
99
+ props: ApiTokenPolicy[] | undefined,
100
+ bindings: { data: ApiTokenBinding }[],
101
+ ): ApiTokenPolicy[] => [
102
+ ...(props ?? []),
103
+ ...bindings.flatMap((binding) => binding.data.policies ?? []),
104
+ ];
105
+
67
106
  export type ResolvedPolicy = {
68
107
  effect: "allow" | "deny";
69
108
  permissionGroups: { id: string; meta?: { key?: string; value?: string } }[];
@@ -87,8 +126,8 @@ export const resolvePermissionGroup = (ref: ApiTokenPermissionGroupRef) => {
87
126
 
88
127
  const resolveResources = (
89
128
  resources: ApiTokenPolicy["resources"],
90
- ): Record<string, string> => {
91
- const out: Record<string, string> = {};
129
+ ): Record<string, ApiTokenResourceScope> => {
130
+ const out: Record<string, ApiTokenResourceScope> = {};
92
131
  for (const [key, value] of Object.entries(resources)) {
93
132
  if (value === undefined) continue;
94
133
  out[key] = value;
@@ -8,9 +8,11 @@ import { Resource } from "../../Resource.ts";
8
8
  import type { Providers } from "../Providers.ts";
9
9
  import {
10
10
  buildConditionPayload,
11
+ collectPolicies,
11
12
  conditionFingerprint,
12
13
  policyFingerprint,
13
14
  resolvePolicies,
15
+ type ApiTokenBinding,
14
16
  type ApiTokenProps,
15
17
  } from "./Common.ts";
16
18
 
@@ -27,7 +29,7 @@ export type UserApiToken = Resource<
27
29
  */
28
30
  value: Redacted.Redacted<string>;
29
31
  },
30
- never,
32
+ ApiTokenBinding,
31
33
  Providers
32
34
  >;
33
35
 
@@ -60,6 +62,42 @@ export type UserApiToken = Resource<
60
62
  * ],
61
63
  * });
62
64
  * ```
65
+ *
66
+ * @section Attaching Policies via Bindings
67
+ * @example Let a downstream capability contribute its own policies
68
+ * A token can be created with no `policies` of its own; the policies are
69
+ * supplied through its binding contract (see {@link ApiTokenBinding}).
70
+ * ```typescript
71
+ * const token = yield* Cloudflare.UserApiToken("scoped-token");
72
+ *
73
+ * yield* token.bind("MyCapability", {
74
+ * policies: [
75
+ * {
76
+ * effect: "allow",
77
+ * permissionGroups: ["Workers Scripts Read"],
78
+ * resources: { [`com.cloudflare.api.account.${accountId}`]: "*" },
79
+ * },
80
+ * ],
81
+ * });
82
+ * ```
83
+ *
84
+ * @section Exposing a Token to a Worker
85
+ * @example Read the token value at runtime
86
+ * Bind the token's value output in the Worker's Init phase to get a runtime
87
+ * accessor. Binding it injects a `secret_text` Worker binding; the returned
88
+ * accessor reads it back (as `Redacted`) at runtime.
89
+ * ```typescript
90
+ * // init
91
+ * const value = yield* token.value; // Accessor<Redacted<string>>
92
+ *
93
+ * return {
94
+ * fetch: Effect.gen(function* () {
95
+ * const apiToken = yield* value; // Redacted<string>
96
+ * // ... call the Cloudflare API with `apiToken`
97
+ * return HttpServerResponse.text("ok");
98
+ * }),
99
+ * };
100
+ * ```
63
101
  */
64
102
  export const UserApiToken = Resource<UserApiToken>("Cloudflare.UserApiToken");
65
103
 
@@ -83,26 +121,32 @@ export const UserApiTokenProvider = () =>
83
121
  tokenData: {
84
122
  id?: string | null;
85
123
  name?: string | null;
86
- status?: "active" | "disabled" | "expired" | null;
124
+ // Distilled widened generated string enums to open unions (`string & {}`).
125
+ status?: string | null;
87
126
  },
88
127
  value: Redacted.Redacted<string>,
89
128
  ): UserApiTokenAttributes => ({
90
129
  tokenId: tokenData.id ?? "",
91
130
  name: tokenData.name ?? "",
92
- status: tokenData.status ?? "active",
131
+ status: (tokenData.status ?? "active") as
132
+ | "active"
133
+ | "disabled"
134
+ | "expired",
93
135
  value,
94
136
  });
95
137
 
96
138
  return {
97
139
  stables: ["tokenId"],
98
- diff: Effect.fn(function* ({ id, olds, news, output }) {
140
+ diff: Effect.fn(function* ({ id, olds, news = {}, output }) {
99
141
  if (!isResolved(news)) return undefined;
100
142
  const oldName = output?.name ?? (yield* resolveName(id, olds?.name));
101
143
  const newName = yield* resolveName(id, news.name);
102
144
  const oldPolicyFp = policyFingerprint(
103
145
  resolvePolicies(olds?.policies ?? []),
104
146
  );
105
- const newPolicyFp = policyFingerprint(resolvePolicies(news.policies));
147
+ const newPolicyFp = policyFingerprint(
148
+ resolvePolicies(news.policies ?? []),
149
+ );
106
150
  const oldCondFp = conditionFingerprint(olds?.condition);
107
151
  const newCondFp = conditionFingerprint(news.condition);
108
152
  if (
@@ -115,9 +159,16 @@ export const UserApiTokenProvider = () =>
115
159
  return { action: "update" } as const;
116
160
  }
117
161
  }),
118
- reconcile: Effect.fn(function* ({ id, news, output }) {
162
+ reconcile: Effect.fn(function* ({ id, news = {}, output, bindings }) {
119
163
  const name = yield* resolveName(id, news.name);
120
- const policies = resolvePolicies(news.policies);
164
+ const collected = collectPolicies(news.policies, bindings);
165
+ if (collected.length === 0) {
166
+ return yield* Effect.die(
167
+ `Cloudflare requires at least one policy on token "${name}". ` +
168
+ "Pass `policies` or attach them via a binding.",
169
+ );
170
+ }
171
+ const policies = resolvePolicies(collected);
121
172
 
122
173
  // Observe — fetch current state if we know the token id;
123
174
  // Cloudflare reports a deleted token as `TokenNotFound`, which
@@ -0,0 +1,140 @@
1
+ import type * as Effect from "effect/Effect";
2
+ import { SingleShotGen } from "effect/Utils";
3
+ import { BrowserBinding, type BrowserClient } from "./BrowserBinding.ts";
4
+
5
+ type BrowserTypeId = typeof BrowserTypeId;
6
+ const BrowserTypeId = "Cloudflare.Browser" as const;
7
+
8
+ export type BrowserProps = {
9
+ /**
10
+ * Binding name used when `Browser` is bound from inside a Worker init phase
11
+ * (`yield* Cloudflare.Browser(...)`). When passed through
12
+ * `Worker({ env: { ... } })`, the object key remains the binding name.
13
+ *
14
+ * @default "BROWSER"
15
+ */
16
+ name?: string;
17
+ };
18
+
19
+ /**
20
+ * The Effect yielded when a `Browser` marker is used inside a Worker init
21
+ * phase: it attaches the `browser` binding to the surrounding Worker and
22
+ * resolves to the runtime {@link BrowserClient}.
23
+ */
24
+ type BindEffect = Effect.Effect<BrowserClient, never, BrowserBinding>;
25
+
26
+ /**
27
+ * Marker for a Cloudflare Browser Rendering binding.
28
+ *
29
+ * It is a plain data structure (so it can be declared directly on a Worker's
30
+ * `env`) that is **also** yieldable inside an Effect-native Worker. Yielding it
31
+ * (`yield* Cloudflare.Browser(...)`) attaches the binding to the surrounding
32
+ * Worker and returns the runtime {@link BrowserClient} — no separate
33
+ * `.bind(...)` step required.
34
+ *
35
+ * The divergence is achieved via `[Symbol.iterator]`: the object is not an
36
+ * `Effect` (so `InferEnv` resolves it to the native `Fetcher` in the `env`
37
+ * position), but it is iterable as one when `yield*`-ed.
38
+ */
39
+ export interface Browser {
40
+ kind: BrowserTypeId;
41
+ name: string;
42
+ asEffect(): BindEffect;
43
+ [Symbol.iterator](): SingleShotGen<BindEffect, BrowserClient>;
44
+ }
45
+
46
+ export const isBrowser = (value: unknown): value is Browser =>
47
+ typeof value === "object" && (value as Browser)?.kind === BrowserTypeId;
48
+
49
+ /**
50
+ * A Cloudflare Browser Rendering binding for launching headless browser
51
+ * sessions from Workers via `@cloudflare/puppeteer`.
52
+ *
53
+ * @binding
54
+ *
55
+ * @section Effect-style Worker (recommended)
56
+ * @example Render a page title with managed browser cleanup
57
+ * ```typescript
58
+ * import puppeteer from "@cloudflare/puppeteer";
59
+ * import * as Effect from "effect/Effect";
60
+ *
61
+ * Cloudflare.Worker(
62
+ * "BrowserWorker",
63
+ * { main: import.meta.filename },
64
+ * Effect.gen(function* () {
65
+ * // Attaches the binding to this Worker AND returns the runtime client.
66
+ * const browser = yield* Cloudflare.Browser({ name: "BROWSER" });
67
+ *
68
+ * return {
69
+ * fetch: browser.withBrowser(puppeteer, (browser) =>
70
+ * Effect.gen(function* () {
71
+ * const page = yield* Effect.tryPromise(() => browser.newPage());
72
+ * yield* Effect.tryPromise(() => page.goto("https://example.com"));
73
+ * const title = yield* Effect.tryPromise(() => page.title());
74
+ * return Response.json({ title });
75
+ * }),
76
+ * ),
77
+ * };
78
+ * }).pipe(Effect.provide(Cloudflare.BrowserBindingLive)),
79
+ * );
80
+ * ```
81
+ *
82
+ * @section Worker binding metadata
83
+ * @example
84
+ * ```typescript
85
+ * export const Worker = Cloudflare.Worker("Worker", {
86
+ * main: "./src/worker.ts",
87
+ * env: {
88
+ * BROWSER: Cloudflare.Browser(),
89
+ * },
90
+ * });
91
+ *
92
+ * export type WorkerEnv = Cloudflare.InferEnv<typeof Worker>;
93
+ * // { BROWSER: Fetcher }
94
+ * ```
95
+ *
96
+ * @example Async-style worker with the raw runtime binding
97
+ * ```typescript
98
+ * import puppeteer from "@cloudflare/puppeteer";
99
+ * import type { WorkerEnv } from "../alchemy.run.ts";
100
+ *
101
+ * export default {
102
+ * async fetch(request: Request, env: WorkerEnv) {
103
+ * const browser = await puppeteer.launch(env.BROWSER);
104
+ * const page = await browser.newPage();
105
+ * await page.goto("https://example.com");
106
+ * const screenshot = await page.screenshot();
107
+ * await browser.close();
108
+ *
109
+ * return new Response(screenshot, {
110
+ * headers: { "content-type": "image/png" },
111
+ * });
112
+ * },
113
+ * };
114
+ * ```
115
+ *
116
+ * @see https://developers.cloudflare.com/browser-rendering/workers-binding-api/
117
+ */
118
+ export const Browser: {
119
+ (props?: BrowserProps): Browser;
120
+ /**
121
+ * Bind an existing `Browser` marker to the surrounding Worker, returning the
122
+ * runtime client. Equivalent to `yield* browser` — prefer yielding the marker
123
+ * directly.
124
+ */
125
+ bind: typeof BrowserBinding.bind;
126
+ } = Object.assign(
127
+ (props?: BrowserProps): Browser => {
128
+ const self: Browser = {
129
+ kind: BrowserTypeId,
130
+ name: props?.name ?? "BROWSER",
131
+ asEffect: () => BrowserBinding.bind(self),
132
+ [Symbol.iterator]: () => new SingleShotGen(BrowserBinding.bind(self)),
133
+ };
134
+ return self;
135
+ },
136
+ {
137
+ bind: (...args: Parameters<typeof BrowserBinding.bind>) =>
138
+ BrowserBinding.bind(...args),
139
+ },
140
+ );
@@ -0,0 +1,128 @@
1
+ import type * as cf from "@cloudflare/workers-types";
2
+ import * as Data from "effect/Data";
3
+ import * as Effect from "effect/Effect";
4
+ import * as Layer from "effect/Layer";
5
+ import * as Binding from "../../Binding.ts";
6
+ import type { ResourceLike } from "../../Resource.ts";
7
+ import { isWorker, WorkerEnvironment } from "../Workers/Worker.ts";
8
+ import type { Browser as BrowserLike } from "./Browser.ts";
9
+
10
+ export class BrowserError extends Data.TaggedError("BrowserError")<{
11
+ message: string;
12
+ cause: unknown;
13
+ }> {}
14
+
15
+ export interface BrowserHandle {
16
+ close(): Promise<void>;
17
+ }
18
+
19
+ export interface BrowserPuppeteer<Browser extends BrowserHandle> {
20
+ launch(binding: cf.Fetcher): Promise<Browser>;
21
+ }
22
+
23
+ /**
24
+ * Effect-native client for a Cloudflare Browser Rendering binding.
25
+ *
26
+ * Browser Rendering's Workers binding is consumed by `@cloudflare/puppeteer`.
27
+ * Alchemy keeps Puppeteer as a caller-provided dependency while wrapping
28
+ * launch and cleanup in Effects.
29
+ */
30
+ export interface BrowserClient {
31
+ /**
32
+ * Effect resolving to the raw Cloudflare Browser Rendering runtime binding.
33
+ */
34
+ raw: Effect.Effect<cf.Fetcher, never, WorkerEnvironment>;
35
+ /**
36
+ * Launch a Browser Rendering session through `@cloudflare/puppeteer`.
37
+ */
38
+ launch<Browser extends BrowserHandle>(
39
+ puppeteer: BrowserPuppeteer<Browser>,
40
+ ): Effect.Effect<Browser, BrowserError, WorkerEnvironment>;
41
+ /**
42
+ * Launch a browser, run an Effect with it, and close it afterward.
43
+ */
44
+ withBrowser<Browser extends BrowserHandle, A, E, R>(
45
+ puppeteer: BrowserPuppeteer<Browser>,
46
+ use: (browser: Browser) => Effect.Effect<A, E, R>,
47
+ ): Effect.Effect<A, BrowserError | E, WorkerEnvironment | R>;
48
+ }
49
+
50
+ export class BrowserBinding extends Binding.Service<
51
+ BrowserBinding,
52
+ (browser: BrowserLike) => Effect.Effect<BrowserClient>
53
+ >()("Cloudflare.Browser.Binding") {}
54
+
55
+ export const BrowserBindingLive = Layer.effect(
56
+ BrowserBinding,
57
+ Effect.gen(function* () {
58
+ const Policy = yield* BrowserBindingPolicy;
59
+
60
+ return Effect.fn(function* (browser: BrowserLike) {
61
+ yield* Policy(browser);
62
+ // Cloudflare exposes Browser Rendering as a service-style binding for
63
+ // @cloudflare/puppeteer; workers-types has no narrower interface.
64
+ const raw = WorkerEnvironment.useSync(
65
+ (env) => (env as Record<string, cf.Fetcher>)[browser.name]!,
66
+ );
67
+ return makeBrowserClient(raw);
68
+ });
69
+ }),
70
+ );
71
+
72
+ export class BrowserBindingPolicy extends Binding.Policy<
73
+ BrowserBindingPolicy,
74
+ (browser: BrowserLike) => Effect.Effect<void>
75
+ >()("Cloudflare.Browser.Binding") {}
76
+
77
+ export const BrowserBindingPolicyLive = BrowserBindingPolicy.layer.succeed(
78
+ Effect.fn(function* (host: ResourceLike, browser: BrowserLike) {
79
+ if (isWorker(host)) {
80
+ yield* host.bind(browser.name, {
81
+ bindings: [
82
+ {
83
+ type: "browser",
84
+ name: browser.name,
85
+ },
86
+ ],
87
+ });
88
+ } else {
89
+ return yield* Effect.die(
90
+ new Error(`BrowserBinding does not support runtime '${host.Type}'`),
91
+ );
92
+ }
93
+ }),
94
+ );
95
+
96
+ const tryPromise = <T>(fn: () => Promise<T>): Effect.Effect<T, BrowserError> =>
97
+ Effect.tryPromise({
98
+ try: fn,
99
+ catch: (error) =>
100
+ new BrowserError({
101
+ message:
102
+ error instanceof Error
103
+ ? error.message
104
+ : "Unknown Browser Rendering error",
105
+ cause: error,
106
+ }),
107
+ });
108
+
109
+ /** @internal */
110
+ export const makeBrowserClient = (
111
+ raw: Effect.Effect<cf.Fetcher, never, WorkerEnvironment>,
112
+ ): BrowserClient => {
113
+ const launch = <Browser extends BrowserHandle>(
114
+ puppeteer: BrowserPuppeteer<Browser>,
115
+ ) =>
116
+ raw.pipe(
117
+ Effect.flatMap((binding) => tryPromise(() => puppeteer.launch(binding))),
118
+ );
119
+
120
+ return {
121
+ raw,
122
+ launch,
123
+ withBrowser: (puppeteer, use) =>
124
+ Effect.acquireUseRelease(launch(puppeteer), use, (browser) =>
125
+ tryPromise(() => browser.close()),
126
+ ),
127
+ } satisfies BrowserClient;
128
+ };
@@ -0,0 +1,11 @@
1
+ export * from "./Browser.ts";
2
+ export {
3
+ BrowserBinding,
4
+ BrowserBindingLive,
5
+ BrowserBindingPolicy,
6
+ BrowserBindingPolicyLive,
7
+ BrowserError,
8
+ type BrowserClient,
9
+ type BrowserHandle,
10
+ type BrowserPuppeteer,
11
+ } from "./BrowserBinding.ts";
@@ -332,7 +332,10 @@ export const DatabaseProvider = () =>
332
332
  databaseId: db.uuid ?? output.databaseId,
333
333
  databaseName: db.name ?? output.databaseName,
334
334
  jurisdiction: output.jurisdiction,
335
- readReplication: db.readReplication ?? undefined,
335
+ // Distilled widened generated string enums to open unions.
336
+ readReplication: (db.readReplication ?? undefined) as
337
+ | { mode: "auto" | "disabled" }
338
+ | undefined,
336
339
  accountId: output.accountId,
337
340
  migrationsDir: output.migrationsDir,
338
341
  migrationsTable: output.migrationsTable,
@@ -375,7 +378,8 @@ export const DatabaseProvider = () =>
375
378
  | {
376
379
  uuid?: string | null;
377
380
  name?: string | null;
378
- readReplication?: { mode: "auto" | "disabled" } | null;
381
+ // Distilled widened generated string enums to open unions.
382
+ readReplication?: { mode: string } | null;
379
383
  }
380
384
  | undefined;
381
385
  if (output?.databaseId) {
@@ -0,0 +1,123 @@
1
+ import {
2
+ Credentials,
3
+ fromApiToken,
4
+ } from "@distilled.cloud/cloudflare/Credentials";
5
+ import * as Effect from "effect/Effect";
6
+ import * as Layer from "effect/Layer";
7
+ import * as Redacted from "effect/Redacted";
8
+ import * as FetchHttpClient from "effect/unstable/http/FetchHttpClient";
9
+ import { type HttpClient } from "effect/unstable/http/HttpClient";
10
+ import type * as Binding from "../../Binding.ts";
11
+ import * as Output from "../../Output.ts";
12
+ import { RuntimeContext } from "../../RuntimeContext.ts";
13
+ import { AccountApiToken } from "../ApiToken/AccountApiToken.ts";
14
+ import type { ApiTokenPermissionGroupRef } from "../ApiToken/Common.ts";
15
+ import type { Zone } from "../Zone/Zone.ts";
16
+
17
+ /**
18
+ * Runtime accessor for a DNS binding's token, obtained by binding the
19
+ * {@link AccountApiToken}'s `value` output in the Worker's Init phase. Reads the
20
+ * value back from the Worker's environment at runtime. DNS record operations
21
+ * are zone-scoped (the `zoneId` is passed per call), so the account id is not
22
+ * needed at runtime.
23
+ */
24
+ export interface DnsToken {
25
+ /** The token's plaintext value (injected as a `secret_text` binding). */
26
+ value: Effect.Effect<Redacted.Redacted<string>>;
27
+ }
28
+
29
+ /**
30
+ * Bind an {@link AccountApiToken}'s `value` output into the Worker (as a
31
+ * `secret_text` binding) and return the {@link DnsToken} accessor.
32
+ */
33
+ export const bindDnsToken = (token: AccountApiToken) =>
34
+ Effect.gen(function* () {
35
+ const value = yield* token.value;
36
+ return { value } satisfies DnsToken;
37
+ });
38
+
39
+ /**
40
+ * Resolve credentials from a bound token and provide them (plus the
41
+ * fetch-based HTTP client) to a raw SDK operation.
42
+ */
43
+ export const authorizeDns =
44
+ (token: DnsToken) =>
45
+ <A, E>(
46
+ eff: Effect.Effect<A, E, Credentials | HttpClient>,
47
+ ): Effect.Effect<A, E, RuntimeContext> =>
48
+ token.value.pipe(
49
+ Effect.flatMap((value) =>
50
+ eff.pipe(
51
+ Effect.provide(
52
+ fromApiToken({ apiToken: Redacted.value(value) }).pipe(
53
+ Layer.provideMerge(FetchHttpClient.layer),
54
+ ),
55
+ ),
56
+ ),
57
+ ),
58
+ );
59
+
60
+ /**
61
+ * Shared runtime body for a DNS binding: create a token scoped to the requested
62
+ * zone, attach the (narrow) policy, bind the token's value and the zone's id
63
+ * into the Worker, then build the client. Pass the result to
64
+ * `Layer.effect(<Binding>, ...)`.
65
+ *
66
+ * The zone's `zoneId` is bound into the Worker at init time, so the resulting
67
+ * client closes over it and callers never pass it per request — the
68
+ * provisioned token only grants access to that one zone anyway.
69
+ */
70
+ export const makeDnsClient = <C>(
71
+ Policy: Binding.Policy<
72
+ any,
73
+ any,
74
+ (token: AccountApiToken, zone: Zone) => Effect.Effect<void>
75
+ >,
76
+ tokenId: string,
77
+ makeClient: (token: DnsToken, zoneId: Effect.Effect<string>) => C,
78
+ ) =>
79
+ Effect.gen(function* () {
80
+ const Token = yield* AccountApiToken;
81
+ const attach = yield* Policy;
82
+
83
+ return Effect.fn(function* (zone: Zone) {
84
+ const token = yield* Token(tokenId);
85
+ yield* attach(token, zone);
86
+ const zoneId = yield* zone.zoneId;
87
+ return makeClient(yield* bindDnsToken(token), zoneId);
88
+ });
89
+ });
90
+
91
+ /**
92
+ * Build the deploy-time policy layer for a DNS binding: attach an allow policy
93
+ * with the given permission groups, scoped to the requested zone
94
+ * (`com.cloudflare.api.account.zone.<zoneId>`).
95
+ */
96
+ export const makeDnsPolicyLive = <Self, Id extends string>(
97
+ Policy: Binding.Policy<
98
+ Self,
99
+ Id,
100
+ (token: AccountApiToken, zone: Zone) => Effect.Effect<void>
101
+ >,
102
+ sid: string,
103
+ permissionGroups: ApiTokenPermissionGroupRef[],
104
+ ) =>
105
+ Policy.layer.succeed((_host, token, zone) =>
106
+ token.bind`${sid}(${zone})`({
107
+ policies: [
108
+ {
109
+ effect: "allow",
110
+ permissionGroups,
111
+ resources: zone.zoneId.pipe(
112
+ Output.flatMap(
113
+ (zoneId) =>
114
+ Output.interpolate`com.cloudflare.api.account.zone.${zoneId}`,
115
+ ),
116
+ Output.map((zoneId) => ({
117
+ [zoneId]: "*",
118
+ })),
119
+ ),
120
+ },
121
+ ],
122
+ }),
123
+ );