alchemy 2.0.0-beta.65 → 2.0.0-beta.67

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 (959) hide show
  1. package/bin/exec.js +578 -224
  2. package/bin/exec.js.map +1 -1
  3. package/lib/AWS/ApiGateway/common.d.ts.map +1 -1
  4. package/lib/AWS/ApiGateway/common.js +8 -1
  5. package/lib/AWS/ApiGateway/common.js.map +1 -1
  6. package/lib/AWS/Bedrock/LanguageModel.d.ts +208 -0
  7. package/lib/AWS/Bedrock/LanguageModel.d.ts.map +1 -0
  8. package/lib/AWS/Bedrock/LanguageModel.js +508 -0
  9. package/lib/AWS/Bedrock/LanguageModel.js.map +1 -0
  10. package/lib/AWS/Bedrock/LanguageModelHttp.d.ts +10 -0
  11. package/lib/AWS/Bedrock/LanguageModelHttp.d.ts.map +1 -0
  12. package/lib/AWS/Bedrock/LanguageModelHttp.js +26 -0
  13. package/lib/AWS/Bedrock/LanguageModelHttp.js.map +1 -0
  14. package/lib/AWS/Bedrock/index.d.ts +2 -0
  15. package/lib/AWS/Bedrock/index.d.ts.map +1 -1
  16. package/lib/AWS/Bedrock/index.js +2 -0
  17. package/lib/AWS/Bedrock/index.js.map +1 -1
  18. package/lib/AWS/CloudFront/Distribution.d.ts +24 -0
  19. package/lib/AWS/CloudFront/Distribution.d.ts.map +1 -1
  20. package/lib/AWS/CloudFront/Distribution.js +89 -4
  21. package/lib/AWS/CloudFront/Distribution.js.map +1 -1
  22. package/lib/AWS/CostExplorer/AnomalyMonitor.d.ts +1 -1
  23. package/lib/AWS/CostExplorer/AnomalyMonitor.d.ts.map +1 -1
  24. package/lib/AWS/CostExplorer/AnomalySubscription.d.ts +1 -1
  25. package/lib/AWS/CostExplorer/AnomalySubscription.d.ts.map +1 -1
  26. package/lib/AWS/CostExplorer/BindingHttp.d.ts +3 -3
  27. package/lib/AWS/CostExplorer/BindingHttp.d.ts.map +1 -1
  28. package/lib/AWS/CostExplorer/CostCategory.d.ts +1 -1
  29. package/lib/AWS/CostExplorer/CostCategory.d.ts.map +1 -1
  30. package/lib/AWS/CostExplorer/GetAnomaliesHttp.d.ts +1 -1
  31. package/lib/AWS/CostExplorer/GetAnomaliesHttp.d.ts.map +1 -1
  32. package/lib/AWS/CostExplorer/GetApproximateUsageRecordsHttp.d.ts +1 -1
  33. package/lib/AWS/CostExplorer/GetApproximateUsageRecordsHttp.d.ts.map +1 -1
  34. package/lib/AWS/CostExplorer/GetCommitmentPurchaseAnalysisHttp.d.ts +1 -1
  35. package/lib/AWS/CostExplorer/GetCommitmentPurchaseAnalysisHttp.d.ts.map +1 -1
  36. package/lib/AWS/CostExplorer/GetCostAndUsageComparisonsHttp.d.ts +1 -1
  37. package/lib/AWS/CostExplorer/GetCostAndUsageComparisonsHttp.d.ts.map +1 -1
  38. package/lib/AWS/CostExplorer/GetCostAndUsageHttp.d.ts +1 -1
  39. package/lib/AWS/CostExplorer/GetCostAndUsageHttp.d.ts.map +1 -1
  40. package/lib/AWS/CostExplorer/GetCostAndUsageWithResourcesHttp.d.ts +1 -1
  41. package/lib/AWS/CostExplorer/GetCostAndUsageWithResourcesHttp.d.ts.map +1 -1
  42. package/lib/AWS/CostExplorer/GetCostCategoriesHttp.d.ts +1 -1
  43. package/lib/AWS/CostExplorer/GetCostCategoriesHttp.d.ts.map +1 -1
  44. package/lib/AWS/CostExplorer/GetCostComparisonDriversHttp.d.ts +1 -1
  45. package/lib/AWS/CostExplorer/GetCostComparisonDriversHttp.d.ts.map +1 -1
  46. package/lib/AWS/CostExplorer/GetCostForecastHttp.d.ts +1 -1
  47. package/lib/AWS/CostExplorer/GetCostForecastHttp.d.ts.map +1 -1
  48. package/lib/AWS/CostExplorer/GetDimensionValuesHttp.d.ts +1 -1
  49. package/lib/AWS/CostExplorer/GetDimensionValuesHttp.d.ts.map +1 -1
  50. package/lib/AWS/CostExplorer/GetReservationCoverageHttp.d.ts +1 -1
  51. package/lib/AWS/CostExplorer/GetReservationCoverageHttp.d.ts.map +1 -1
  52. package/lib/AWS/CostExplorer/GetReservationPurchaseRecommendationHttp.d.ts +1 -1
  53. package/lib/AWS/CostExplorer/GetReservationPurchaseRecommendationHttp.d.ts.map +1 -1
  54. package/lib/AWS/CostExplorer/GetReservationUtilizationHttp.d.ts +1 -1
  55. package/lib/AWS/CostExplorer/GetReservationUtilizationHttp.d.ts.map +1 -1
  56. package/lib/AWS/CostExplorer/GetRightsizingRecommendationHttp.d.ts +1 -1
  57. package/lib/AWS/CostExplorer/GetRightsizingRecommendationHttp.d.ts.map +1 -1
  58. package/lib/AWS/CostExplorer/GetSavingsPlanPurchaseRecommendationDetailsHttp.d.ts +1 -1
  59. package/lib/AWS/CostExplorer/GetSavingsPlanPurchaseRecommendationDetailsHttp.d.ts.map +1 -1
  60. package/lib/AWS/CostExplorer/GetSavingsPlansCoverageHttp.d.ts +1 -1
  61. package/lib/AWS/CostExplorer/GetSavingsPlansCoverageHttp.d.ts.map +1 -1
  62. package/lib/AWS/CostExplorer/GetSavingsPlansPurchaseRecommendationHttp.d.ts +1 -1
  63. package/lib/AWS/CostExplorer/GetSavingsPlansPurchaseRecommendationHttp.d.ts.map +1 -1
  64. package/lib/AWS/CostExplorer/GetSavingsPlansUtilizationDetailsHttp.d.ts +1 -1
  65. package/lib/AWS/CostExplorer/GetSavingsPlansUtilizationDetailsHttp.d.ts.map +1 -1
  66. package/lib/AWS/CostExplorer/GetSavingsPlansUtilizationHttp.d.ts +1 -1
  67. package/lib/AWS/CostExplorer/GetSavingsPlansUtilizationHttp.d.ts.map +1 -1
  68. package/lib/AWS/CostExplorer/GetTagsHttp.d.ts +1 -1
  69. package/lib/AWS/CostExplorer/GetTagsHttp.d.ts.map +1 -1
  70. package/lib/AWS/CostExplorer/GetUsageForecastHttp.d.ts +1 -1
  71. package/lib/AWS/CostExplorer/GetUsageForecastHttp.d.ts.map +1 -1
  72. package/lib/AWS/CostExplorer/ListCommitmentPurchaseAnalysesHttp.d.ts +1 -1
  73. package/lib/AWS/CostExplorer/ListCommitmentPurchaseAnalysesHttp.d.ts.map +1 -1
  74. package/lib/AWS/CostExplorer/ListCostAllocationTagBackfillHistoryHttp.d.ts +1 -1
  75. package/lib/AWS/CostExplorer/ListCostAllocationTagBackfillHistoryHttp.d.ts.map +1 -1
  76. package/lib/AWS/CostExplorer/ListCostAllocationTagsHttp.d.ts +1 -1
  77. package/lib/AWS/CostExplorer/ListCostAllocationTagsHttp.d.ts.map +1 -1
  78. package/lib/AWS/CostExplorer/ListCostCategoryResourceAssociationsHttp.d.ts +1 -1
  79. package/lib/AWS/CostExplorer/ListCostCategoryResourceAssociationsHttp.d.ts.map +1 -1
  80. package/lib/AWS/CostExplorer/ListSavingsPlansPurchaseRecommendationGenerationHttp.d.ts +1 -1
  81. package/lib/AWS/CostExplorer/ListSavingsPlansPurchaseRecommendationGenerationHttp.d.ts.map +1 -1
  82. package/lib/AWS/CostExplorer/ProvideAnomalyFeedbackHttp.d.ts +1 -1
  83. package/lib/AWS/CostExplorer/ProvideAnomalyFeedbackHttp.d.ts.map +1 -1
  84. package/lib/AWS/CostExplorer/StartCommitmentPurchaseAnalysisHttp.d.ts +1 -1
  85. package/lib/AWS/CostExplorer/StartCommitmentPurchaseAnalysisHttp.d.ts.map +1 -1
  86. package/lib/AWS/CostExplorer/StartCostAllocationTagBackfillHttp.d.ts +1 -1
  87. package/lib/AWS/CostExplorer/StartCostAllocationTagBackfillHttp.d.ts.map +1 -1
  88. package/lib/AWS/CostExplorer/StartSavingsPlansPurchaseRecommendationGenerationHttp.d.ts +1 -1
  89. package/lib/AWS/CostExplorer/StartSavingsPlansPurchaseRecommendationGenerationHttp.d.ts.map +1 -1
  90. package/lib/AWS/CostExplorer/UpdateCostAllocationTagsStatusHttp.d.ts +1 -1
  91. package/lib/AWS/CostExplorer/UpdateCostAllocationTagsStatusHttp.d.ts.map +1 -1
  92. package/lib/AWS/CostExplorer/common.d.ts +5 -3
  93. package/lib/AWS/CostExplorer/common.d.ts.map +1 -1
  94. package/lib/AWS/CostExplorer/common.js +16 -1
  95. package/lib/AWS/CostExplorer/common.js.map +1 -1
  96. package/lib/AWS/DataSync/Task.d.ts.map +1 -1
  97. package/lib/AWS/DataSync/Task.js +16 -2
  98. package/lib/AWS/DataSync/Task.js.map +1 -1
  99. package/lib/AWS/EC2/Vpc.d.ts.map +1 -1
  100. package/lib/AWS/EC2/Vpc.js +15 -2
  101. package/lib/AWS/EC2/Vpc.js.map +1 -1
  102. package/lib/AWS/EC2/hosted.d.ts.map +1 -1
  103. package/lib/AWS/EC2/hosted.js +10 -2
  104. package/lib/AWS/EC2/hosted.js.map +1 -1
  105. package/lib/AWS/ECR/Image.d.ts +132 -13
  106. package/lib/AWS/ECR/Image.d.ts.map +1 -1
  107. package/lib/AWS/ECR/ImageSource.d.ts.map +1 -1
  108. package/lib/AWS/ECR/ImageSource.js +10 -2
  109. package/lib/AWS/ECR/ImageSource.js.map +1 -1
  110. package/lib/AWS/ECR/Repository.d.ts +2 -0
  111. package/lib/AWS/ECR/Repository.d.ts.map +1 -1
  112. package/lib/AWS/ECR/Repository.js +28 -3
  113. package/lib/AWS/ECR/Repository.js.map +1 -1
  114. package/lib/AWS/ECS/Service.d.ts +27 -1
  115. package/lib/AWS/ECS/Service.d.ts.map +1 -1
  116. package/lib/AWS/ECS/Service.js +184 -22
  117. package/lib/AWS/ECS/Service.js.map +1 -1
  118. package/lib/AWS/ECS/Task.d.ts +32 -8
  119. package/lib/AWS/ECS/Task.d.ts.map +1 -1
  120. package/lib/AWS/ECS/Task.js +62 -29
  121. package/lib/AWS/ECS/Task.js.map +1 -1
  122. package/lib/AWS/EKS/Deployment.d.ts.map +1 -1
  123. package/lib/AWS/EKS/Deployment.js +10 -2
  124. package/lib/AWS/EKS/Deployment.js.map +1 -1
  125. package/lib/AWS/EKS/Job.d.ts.map +1 -1
  126. package/lib/AWS/EKS/Job.js +10 -2
  127. package/lib/AWS/EKS/Job.js.map +1 -1
  128. package/lib/AWS/Lambda/Function.d.ts +15 -0
  129. package/lib/AWS/Lambda/Function.d.ts.map +1 -1
  130. package/lib/AWS/Lambda/Function.js +67 -29
  131. package/lib/AWS/Lambda/Function.js.map +1 -1
  132. package/lib/AWS/Lambda/HttpServer.d.ts.map +1 -1
  133. package/lib/AWS/Lambda/HttpServer.js +7 -1
  134. package/lib/AWS/Lambda/HttpServer.js.map +1 -1
  135. package/lib/AWS/Lambda/LayerVersion.d.ts +176 -0
  136. package/lib/AWS/Lambda/LayerVersion.d.ts.map +1 -0
  137. package/lib/AWS/Lambda/LayerVersion.js +288 -0
  138. package/lib/AWS/Lambda/LayerVersion.js.map +1 -0
  139. package/lib/AWS/Lambda/MicrovmBundle.d.ts.map +1 -1
  140. package/lib/AWS/Lambda/MicrovmBundle.js +10 -2
  141. package/lib/AWS/Lambda/MicrovmBundle.js.map +1 -1
  142. package/lib/AWS/Lambda/index.d.ts +1 -0
  143. package/lib/AWS/Lambda/index.d.ts.map +1 -1
  144. package/lib/AWS/Lambda/index.js +1 -0
  145. package/lib/AWS/Lambda/index.js.map +1 -1
  146. package/lib/AWS/Providers.d.ts.map +1 -1
  147. package/lib/AWS/Providers.js +6 -1
  148. package/lib/AWS/Providers.js.map +1 -1
  149. package/lib/AWS/SES/ActiveReceiptRuleSet.d.ts +40 -0
  150. package/lib/AWS/SES/ActiveReceiptRuleSet.d.ts.map +1 -0
  151. package/lib/AWS/SES/ActiveReceiptRuleSet.js +58 -0
  152. package/lib/AWS/SES/ActiveReceiptRuleSet.js.map +1 -0
  153. package/lib/AWS/SES/ReceiptFilter.d.ts +67 -0
  154. package/lib/AWS/SES/ReceiptFilter.d.ts.map +1 -0
  155. package/lib/AWS/SES/ReceiptFilter.js +92 -0
  156. package/lib/AWS/SES/ReceiptFilter.js.map +1 -0
  157. package/lib/AWS/SES/ReceiptRule.d.ts +133 -0
  158. package/lib/AWS/SES/ReceiptRule.d.ts.map +1 -0
  159. package/lib/AWS/SES/ReceiptRule.js +178 -0
  160. package/lib/AWS/SES/ReceiptRule.js.map +1 -0
  161. package/lib/AWS/SES/ReceiptRuleSet.d.ts +55 -0
  162. package/lib/AWS/SES/ReceiptRuleSet.d.ts.map +1 -0
  163. package/lib/AWS/SES/ReceiptRuleSet.js +112 -0
  164. package/lib/AWS/SES/ReceiptRuleSet.js.map +1 -0
  165. package/lib/AWS/SES/SendBounce.d.ts +37 -0
  166. package/lib/AWS/SES/SendBounce.d.ts.map +1 -0
  167. package/lib/AWS/SES/SendBounce.js +3 -0
  168. package/lib/AWS/SES/SendBounce.js.map +1 -0
  169. package/lib/AWS/SES/SendBounceHttp.d.ts +4 -0
  170. package/lib/AWS/SES/SendBounceHttp.d.ts.map +1 -0
  171. package/lib/AWS/SES/SendBounceHttp.js +10 -0
  172. package/lib/AWS/SES/SendBounceHttp.js.map +1 -0
  173. package/lib/AWS/SES/index.d.ts +6 -0
  174. package/lib/AWS/SES/index.d.ts.map +1 -1
  175. package/lib/AWS/SES/index.js +6 -0
  176. package/lib/AWS/SES/index.js.map +1 -1
  177. package/lib/AWS/StateStore/State.d.ts +11 -0
  178. package/lib/AWS/StateStore/State.d.ts.map +1 -1
  179. package/lib/AWS/StateStore/State.js +117 -38
  180. package/lib/AWS/StateStore/State.js.map +1 -1
  181. package/lib/Apply.d.ts +37 -1
  182. package/lib/Apply.d.ts.map +1 -1
  183. package/lib/Apply.js +270 -194
  184. package/lib/Apply.js.map +1 -1
  185. package/lib/Axiom/Telemetry.d.ts +66 -0
  186. package/lib/Axiom/Telemetry.d.ts.map +1 -0
  187. package/lib/Axiom/Telemetry.js +69 -0
  188. package/lib/Axiom/Telemetry.js.map +1 -0
  189. package/lib/Axiom/index.d.ts +1 -0
  190. package/lib/Axiom/index.d.ts.map +1 -1
  191. package/lib/Axiom/index.js +1 -0
  192. package/lib/Axiom/index.js.map +1 -1
  193. package/lib/Bundle/Bundle.d.ts +1 -1
  194. package/lib/Bundle/Bundle.d.ts.map +1 -1
  195. package/lib/Bundle/Bundle.js +14 -6
  196. package/lib/Bundle/Bundle.js.map +1 -1
  197. package/lib/Bundle/BundleAnalyzerPlugin.d.ts +1 -1
  198. package/lib/Bundle/BundleAnalyzerPlugin.d.ts.map +1 -1
  199. package/lib/Bundle/BundleAnalyzerPlugin.js +10 -5
  200. package/lib/Bundle/BundleAnalyzerPlugin.js.map +1 -1
  201. package/lib/Bundle/PurePlugin.d.ts +1 -1
  202. package/lib/Bundle/PurePlugin.d.ts.map +1 -1
  203. package/lib/Bundle/PurePlugin.js +15 -7
  204. package/lib/Bundle/PurePlugin.js.map +1 -1
  205. package/lib/Cli/checkVersion.d.ts +11 -5
  206. package/lib/Cli/checkVersion.d.ts.map +1 -1
  207. package/lib/Cli/checkVersion.js +73 -12
  208. package/lib/Cli/checkVersion.js.map +1 -1
  209. package/lib/Cli/commands/_shared.d.ts.map +1 -1
  210. package/lib/Cli/commands/_shared.js +2 -1
  211. package/lib/Cli/commands/_shared.js.map +1 -1
  212. package/lib/Cli/commands/cloudflare.d.ts +1 -1
  213. package/lib/Cli/commands/deploy.d.ts.map +1 -1
  214. package/lib/Cli/commands/deploy.js +3 -11
  215. package/lib/Cli/commands/deploy.js.map +1 -1
  216. package/lib/Cli/commands/logs.d.ts.map +1 -1
  217. package/lib/Cli/commands/logs.js +3 -1
  218. package/lib/Cli/commands/logs.js.map +1 -1
  219. package/lib/Cli/commands/tail.d.ts.map +1 -1
  220. package/lib/Cli/commands/tail.js +3 -1
  221. package/lib/Cli/commands/tail.js.map +1 -1
  222. package/lib/Cli/main.d.ts.map +1 -1
  223. package/lib/Cli/main.js +5 -4
  224. package/lib/Cli/main.js.map +1 -1
  225. package/lib/Cloudflare/AI/Search.d.ts +4 -15
  226. package/lib/Cloudflare/AI/Search.d.ts.map +1 -1
  227. package/lib/Cloudflare/AI/Search.js +4 -6
  228. package/lib/Cloudflare/AI/Search.js.map +1 -1
  229. package/lib/Cloudflare/AI/SearchInstance.d.ts +7 -26
  230. package/lib/Cloudflare/AI/SearchInstance.d.ts.map +1 -1
  231. package/lib/Cloudflare/AI/SearchInstance.js +7 -26
  232. package/lib/Cloudflare/AI/SearchInstance.js.map +1 -1
  233. package/lib/Cloudflare/ApiToken/AccountApiToken.d.ts +3 -0
  234. package/lib/Cloudflare/ApiToken/AccountApiToken.d.ts.map +1 -1
  235. package/lib/Cloudflare/ApiToken/AccountApiToken.js +3 -0
  236. package/lib/Cloudflare/ApiToken/AccountApiToken.js.map +1 -1
  237. package/lib/Cloudflare/Auth/AuthProvider.d.ts +2 -1
  238. package/lib/Cloudflare/Auth/AuthProvider.d.ts.map +1 -1
  239. package/lib/Cloudflare/Auth/AuthProvider.js +8 -3
  240. package/lib/Cloudflare/Auth/AuthProvider.js.map +1 -1
  241. package/lib/Cloudflare/Auth/OAuthClient.d.ts +5 -0
  242. package/lib/Cloudflare/Auth/OAuthClient.d.ts.map +1 -1
  243. package/lib/Cloudflare/Auth/OAuthClient.js +50 -4
  244. package/lib/Cloudflare/Auth/OAuthClient.js.map +1 -1
  245. package/lib/Cloudflare/Containers/Container.d.ts +81 -3
  246. package/lib/Cloudflare/Containers/Container.d.ts.map +1 -1
  247. package/lib/Cloudflare/Containers/Container.js +70 -0
  248. package/lib/Cloudflare/Containers/Container.js.map +1 -1
  249. package/lib/Cloudflare/Containers/ContainerApplication.d.ts +22 -1
  250. package/lib/Cloudflare/Containers/ContainerApplication.d.ts.map +1 -1
  251. package/lib/Cloudflare/Containers/ContainerApplication.js +4 -1
  252. package/lib/Cloudflare/Containers/ContainerApplication.js.map +1 -1
  253. package/lib/Cloudflare/Containers/ContainerBundle.d.ts.map +1 -1
  254. package/lib/Cloudflare/Containers/ContainerBundle.js +10 -2
  255. package/lib/Cloudflare/Containers/ContainerBundle.js.map +1 -1
  256. package/lib/Cloudflare/Containers/ContainerProvider.d.ts.map +1 -1
  257. package/lib/Cloudflare/Containers/ContainerProvider.js +43 -0
  258. package/lib/Cloudflare/Containers/ContainerProvider.js.map +1 -1
  259. package/lib/Cloudflare/Credentials.d.ts +21 -1
  260. package/lib/Cloudflare/Credentials.d.ts.map +1 -1
  261. package/lib/Cloudflare/Credentials.js +52 -9
  262. package/lib/Cloudflare/Credentials.js.map +1 -1
  263. package/lib/Cloudflare/D1/ApplyMigrations.d.ts +30 -5
  264. package/lib/Cloudflare/D1/ApplyMigrations.d.ts.map +1 -1
  265. package/lib/Cloudflare/D1/ApplyMigrations.js +36 -26
  266. package/lib/Cloudflare/D1/ApplyMigrations.js.map +1 -1
  267. package/lib/Cloudflare/D1/Database.d.ts +18 -1
  268. package/lib/Cloudflare/D1/Database.d.ts.map +1 -1
  269. package/lib/Cloudflare/D1/Database.js +134 -2
  270. package/lib/Cloudflare/D1/Database.js.map +1 -1
  271. package/lib/Cloudflare/D1/LocalD1Gateway.d.ts +39 -0
  272. package/lib/Cloudflare/D1/LocalD1Gateway.d.ts.map +1 -0
  273. package/lib/Cloudflare/D1/LocalD1Gateway.js +100 -0
  274. package/lib/Cloudflare/D1/LocalD1Gateway.js.map +1 -0
  275. package/lib/Cloudflare/D1/QueryDatabaseHttpClient.d.ts +31 -0
  276. package/lib/Cloudflare/D1/QueryDatabaseHttpClient.d.ts.map +1 -1
  277. package/lib/Cloudflare/D1/QueryDatabaseHttpClient.js +13 -6
  278. package/lib/Cloudflare/D1/QueryDatabaseHttpClient.js.map +1 -1
  279. package/lib/Cloudflare/D1/QueryDatabaseLocal.d.ts.map +1 -1
  280. package/lib/Cloudflare/D1/QueryDatabaseLocal.js +21 -2
  281. package/lib/Cloudflare/D1/QueryDatabaseLocal.js.map +1 -1
  282. package/lib/Cloudflare/DNS/Record.d.ts +3 -1
  283. package/lib/Cloudflare/DNS/Record.d.ts.map +1 -1
  284. package/lib/Cloudflare/DNS/Record.js +23 -4
  285. package/lib/Cloudflare/DNS/Record.js.map +1 -1
  286. package/lib/Cloudflare/Email/Address.d.ts.map +1 -1
  287. package/lib/Cloudflare/Email/Address.js +15 -1
  288. package/lib/Cloudflare/Email/Address.js.map +1 -1
  289. package/lib/Cloudflare/Email/SendBinding.d.ts.map +1 -1
  290. package/lib/Cloudflare/Email/SendBinding.js +1 -0
  291. package/lib/Cloudflare/Email/SendBinding.js.map +1 -1
  292. package/lib/Cloudflare/Email/SendEmail.d.ts +12 -0
  293. package/lib/Cloudflare/Email/SendEmail.d.ts.map +1 -1
  294. package/lib/Cloudflare/Email/SendEmail.js +1 -0
  295. package/lib/Cloudflare/Email/SendEmail.js.map +1 -1
  296. package/lib/Cloudflare/Flagship/App.d.ts.map +1 -1
  297. package/lib/Cloudflare/Flagship/App.js +8 -3
  298. package/lib/Cloudflare/Flagship/App.js.map +1 -1
  299. package/lib/Cloudflare/KV/LocalKVGateway.d.ts +15 -0
  300. package/lib/Cloudflare/KV/LocalKVGateway.d.ts.map +1 -0
  301. package/lib/Cloudflare/KV/LocalKVGateway.js +64 -0
  302. package/lib/Cloudflare/KV/LocalKVGateway.js.map +1 -0
  303. package/lib/Cloudflare/KV/Namespace.d.ts +9 -1
  304. package/lib/Cloudflare/KV/Namespace.d.ts.map +1 -1
  305. package/lib/Cloudflare/KV/Namespace.js +45 -1
  306. package/lib/Cloudflare/KV/Namespace.js.map +1 -1
  307. package/lib/Cloudflare/KV/NamespaceLocal.d.ts +16 -5
  308. package/lib/Cloudflare/KV/NamespaceLocal.d.ts.map +1 -1
  309. package/lib/Cloudflare/KV/NamespaceLocal.js +21 -4
  310. package/lib/Cloudflare/KV/NamespaceLocal.js.map +1 -1
  311. package/lib/Cloudflare/KV/ReadNamespaceLocal.d.ts.map +1 -1
  312. package/lib/Cloudflare/KV/ReadNamespaceLocal.js +5 -1
  313. package/lib/Cloudflare/KV/ReadNamespaceLocal.js.map +1 -1
  314. package/lib/Cloudflare/KV/ReadWriteNamespaceLocal.d.ts.map +1 -1
  315. package/lib/Cloudflare/KV/ReadWriteNamespaceLocal.js +5 -1
  316. package/lib/Cloudflare/KV/ReadWriteNamespaceLocal.js.map +1 -1
  317. package/lib/Cloudflare/KV/WriteNamespaceLocal.d.ts.map +1 -1
  318. package/lib/Cloudflare/KV/WriteNamespaceLocal.js +5 -1
  319. package/lib/Cloudflare/KV/WriteNamespaceLocal.js.map +1 -1
  320. package/lib/Cloudflare/LocalGateway.d.ts +25 -0
  321. package/lib/Cloudflare/LocalGateway.d.ts.map +1 -0
  322. package/lib/Cloudflare/LocalGateway.js +70 -0
  323. package/lib/Cloudflare/LocalGateway.js.map +1 -0
  324. package/lib/Cloudflare/LocalRuntime.d.ts +14 -1
  325. package/lib/Cloudflare/LocalRuntime.d.ts.map +1 -1
  326. package/lib/Cloudflare/LocalRuntime.js +16 -1
  327. package/lib/Cloudflare/LocalRuntime.js.map +1 -1
  328. package/lib/Cloudflare/Logs.d.ts.map +1 -1
  329. package/lib/Cloudflare/Logs.js +18 -6
  330. package/lib/Cloudflare/Logs.js.map +1 -1
  331. package/lib/Cloudflare/Providers.d.ts +1 -1
  332. package/lib/Cloudflare/Providers.d.ts.map +1 -1
  333. package/lib/Cloudflare/Providers.js +6 -2
  334. package/lib/Cloudflare/Providers.js.map +1 -1
  335. package/lib/Cloudflare/Queues/Consumer.d.ts +16 -3
  336. package/lib/Cloudflare/Queues/Consumer.d.ts.map +1 -1
  337. package/lib/Cloudflare/Queues/Consumer.js +51 -3
  338. package/lib/Cloudflare/Queues/Consumer.js.map +1 -1
  339. package/lib/Cloudflare/Queues/Queue.d.ts +4 -3
  340. package/lib/Cloudflare/Queues/Queue.d.ts.map +1 -1
  341. package/lib/Cloudflare/Queues/Queue.js +7 -17
  342. package/lib/Cloudflare/Queues/Queue.js.map +1 -1
  343. package/lib/Cloudflare/Queues/QueueBinding.d.ts.map +1 -1
  344. package/lib/Cloudflare/Queues/QueueBinding.js +19 -0
  345. package/lib/Cloudflare/Queues/QueueBinding.js.map +1 -1
  346. package/lib/Cloudflare/Queues/QueueShim.d.ts +55 -0
  347. package/lib/Cloudflare/Queues/QueueShim.d.ts.map +1 -0
  348. package/lib/Cloudflare/Queues/QueueShim.js +177 -0
  349. package/lib/Cloudflare/Queues/QueueShim.js.map +1 -0
  350. package/lib/Cloudflare/R2/Bucket.d.ts +14 -1
  351. package/lib/Cloudflare/R2/Bucket.d.ts.map +1 -1
  352. package/lib/Cloudflare/R2/Bucket.js +53 -1
  353. package/lib/Cloudflare/R2/Bucket.js.map +1 -1
  354. package/lib/Cloudflare/R2/BucketBinding.d.ts +10 -0
  355. package/lib/Cloudflare/R2/BucketBinding.d.ts.map +1 -1
  356. package/lib/Cloudflare/R2/BucketBinding.js +14 -3
  357. package/lib/Cloudflare/R2/BucketBinding.js.map +1 -1
  358. package/lib/Cloudflare/R2/BucketLocal.d.ts +13 -6
  359. package/lib/Cloudflare/R2/BucketLocal.d.ts.map +1 -1
  360. package/lib/Cloudflare/R2/BucketLocal.js +19 -6
  361. package/lib/Cloudflare/R2/BucketLocal.js.map +1 -1
  362. package/lib/Cloudflare/R2/LocalR2Gateway.d.ts +15 -0
  363. package/lib/Cloudflare/R2/LocalR2Gateway.d.ts.map +1 -0
  364. package/lib/Cloudflare/R2/LocalR2Gateway.js +65 -0
  365. package/lib/Cloudflare/R2/LocalR2Gateway.js.map +1 -0
  366. package/lib/Cloudflare/R2/ReadBucketLocal.d.ts.map +1 -1
  367. package/lib/Cloudflare/R2/ReadBucketLocal.js +5 -1
  368. package/lib/Cloudflare/R2/ReadBucketLocal.js.map +1 -1
  369. package/lib/Cloudflare/R2/ReadWriteBucketLocal.d.ts.map +1 -1
  370. package/lib/Cloudflare/R2/ReadWriteBucketLocal.js +5 -1
  371. package/lib/Cloudflare/R2/ReadWriteBucketLocal.js.map +1 -1
  372. package/lib/Cloudflare/R2/WriteBucketLocal.d.ts.map +1 -1
  373. package/lib/Cloudflare/R2/WriteBucketLocal.js +5 -1
  374. package/lib/Cloudflare/R2/WriteBucketLocal.js.map +1 -1
  375. package/lib/Cloudflare/Ruleset/AccountEntrypoint.d.ts.map +1 -1
  376. package/lib/Cloudflare/Ruleset/AccountEntrypoint.js +5 -1
  377. package/lib/Cloudflare/Ruleset/AccountEntrypoint.js.map +1 -1
  378. package/lib/Cloudflare/Ruleset/Ruleset.d.ts.map +1 -1
  379. package/lib/Cloudflare/Ruleset/Ruleset.js +19 -6
  380. package/lib/Cloudflare/Ruleset/Ruleset.js.map +1 -1
  381. package/lib/Cloudflare/Rum/Site.js +10 -2
  382. package/lib/Cloudflare/Rum/Site.js.map +1 -1
  383. package/lib/Cloudflare/StateStore/Api.d.ts +31 -2
  384. package/lib/Cloudflare/StateStore/Api.js +1 -1
  385. package/lib/Cloudflare/StateStore/Api.js.map +1 -1
  386. package/lib/Cloudflare/StateStore/State.d.ts +1 -1
  387. package/lib/Cloudflare/StateStore/State.d.ts.map +1 -1
  388. package/lib/Cloudflare/Tunnel/Tunnel.d.ts.map +1 -1
  389. package/lib/Cloudflare/Tunnel/Tunnel.js +20 -1
  390. package/lib/Cloudflare/Tunnel/Tunnel.js.map +1 -1
  391. package/lib/Cloudflare/Website/StaticSite.d.ts +34 -7
  392. package/lib/Cloudflare/Website/StaticSite.d.ts.map +1 -1
  393. package/lib/Cloudflare/Website/StaticSite.js +108 -24
  394. package/lib/Cloudflare/Website/StaticSite.js.map +1 -1
  395. package/lib/Cloudflare/Website/Vite.d.ts +33 -19
  396. package/lib/Cloudflare/Website/Vite.d.ts.map +1 -1
  397. package/lib/Cloudflare/Website/Vite.js +33 -19
  398. package/lib/Cloudflare/Website/Vite.js.map +1 -1
  399. package/lib/Cloudflare/Workers/Compatibility.d.ts.map +1 -1
  400. package/lib/Cloudflare/Workers/Compatibility.js +26 -7
  401. package/lib/Cloudflare/Workers/Compatibility.js.map +1 -1
  402. package/lib/Cloudflare/Workers/DurableObject.d.ts +22 -0
  403. package/lib/Cloudflare/Workers/DurableObject.d.ts.map +1 -1
  404. package/lib/Cloudflare/Workers/DurableObject.js +22 -0
  405. package/lib/Cloudflare/Workers/DurableObject.js.map +1 -1
  406. package/lib/Cloudflare/Workers/DurableObjectBridge.d.ts.map +1 -1
  407. package/lib/Cloudflare/Workers/DurableObjectBridge.js +20 -5
  408. package/lib/Cloudflare/Workers/DurableObjectBridge.js.map +1 -1
  409. package/lib/Cloudflare/Workers/InferEnv.d.ts +4 -2
  410. package/lib/Cloudflare/Workers/InferEnv.d.ts.map +1 -1
  411. package/lib/Cloudflare/Workers/LocalWorkerProvider.d.ts +7 -3
  412. package/lib/Cloudflare/Workers/LocalWorkerProvider.d.ts.map +1 -1
  413. package/lib/Cloudflare/Workers/LocalWorkerProvider.js +483 -298
  414. package/lib/Cloudflare/Workers/LocalWorkerProvider.js.map +1 -1
  415. package/lib/Cloudflare/Workers/Worker.d.ts +612 -13
  416. package/lib/Cloudflare/Workers/Worker.d.ts.map +1 -1
  417. package/lib/Cloudflare/Workers/Worker.js +222 -1
  418. package/lib/Cloudflare/Workers/Worker.js.map +1 -1
  419. package/lib/Cloudflare/Workers/WorkerAsyncBindings.d.ts +13 -1
  420. package/lib/Cloudflare/Workers/WorkerAsyncBindings.d.ts.map +1 -1
  421. package/lib/Cloudflare/Workers/WorkerAsyncBindings.js +171 -21
  422. package/lib/Cloudflare/Workers/WorkerAsyncBindings.js.map +1 -1
  423. package/lib/Cloudflare/Workers/WorkerBinding.d.ts +130 -28
  424. package/lib/Cloudflare/Workers/WorkerBinding.d.ts.map +1 -1
  425. package/lib/Cloudflare/Workers/WorkerBinding.js.map +1 -1
  426. package/lib/Cloudflare/Workers/WorkerBridge.d.ts +6 -2
  427. package/lib/Cloudflare/Workers/WorkerBridge.d.ts.map +1 -1
  428. package/lib/Cloudflare/Workers/WorkerBridge.js +18 -2
  429. package/lib/Cloudflare/Workers/WorkerBridge.js.map +1 -1
  430. package/lib/Cloudflare/Workers/WorkerBundle.d.ts.map +1 -1
  431. package/lib/Cloudflare/Workers/WorkerBundle.js +4 -1
  432. package/lib/Cloudflare/Workers/WorkerBundle.js.map +1 -1
  433. package/lib/Cloudflare/Workers/WorkerProvider.d.ts +186 -11
  434. package/lib/Cloudflare/Workers/WorkerProvider.d.ts.map +1 -1
  435. package/lib/Cloudflare/Workers/WorkerProvider.js +1765 -236
  436. package/lib/Cloudflare/Workers/WorkerProvider.js.map +1 -1
  437. package/lib/Cloudflare/Workflows/WorkflowBridge.d.ts +7 -1
  438. package/lib/Cloudflare/Workflows/WorkflowBridge.d.ts.map +1 -1
  439. package/lib/Cloudflare/Workflows/WorkflowBridge.js +14 -4
  440. package/lib/Cloudflare/Workflows/WorkflowBridge.js.map +1 -1
  441. package/lib/Command/Build.d.ts +2 -2
  442. package/lib/Command/Build.d.ts.map +1 -1
  443. package/lib/Command/Build.js +4 -7
  444. package/lib/Command/Build.js.map +1 -1
  445. package/lib/Command/Command.d.ts +34 -4
  446. package/lib/Command/Command.d.ts.map +1 -1
  447. package/lib/Command/Command.js +125 -30
  448. package/lib/Command/Command.js.map +1 -1
  449. package/lib/Command/Dev.d.ts +2 -2
  450. package/lib/Command/Dev.d.ts.map +1 -1
  451. package/lib/Command/Dev.js +51 -73
  452. package/lib/Command/Dev.js.map +1 -1
  453. package/lib/Command/Exec.d.ts +11 -2
  454. package/lib/Command/Exec.d.ts.map +1 -1
  455. package/lib/Command/Exec.js +9 -0
  456. package/lib/Command/Exec.js.map +1 -1
  457. package/lib/Command/Memo.d.ts +9 -1
  458. package/lib/Command/Memo.d.ts.map +1 -1
  459. package/lib/Command/Memo.js +14 -2
  460. package/lib/Command/Memo.js.map +1 -1
  461. package/lib/Command/Providers.d.ts +1 -1
  462. package/lib/Command/Providers.d.ts.map +1 -1
  463. package/lib/Command/Redaction.d.ts +10 -0
  464. package/lib/Command/Redaction.d.ts.map +1 -0
  465. package/lib/Command/Redaction.js +119 -0
  466. package/lib/Command/Redaction.js.map +1 -0
  467. package/lib/Deploy.d.ts +2 -1
  468. package/lib/Deploy.d.ts.map +1 -1
  469. package/lib/Destroy.d.ts +2 -1
  470. package/lib/Destroy.d.ts.map +1 -1
  471. package/lib/Destroy.js +1 -9
  472. package/lib/Destroy.js.map +1 -1
  473. package/lib/Docker/Container.d.ts +15 -1
  474. package/lib/Docker/Container.d.ts.map +1 -1
  475. package/lib/Docker/Container.js +34 -11
  476. package/lib/Docker/Container.js.map +1 -1
  477. package/lib/Docker/Context.d.ts +84 -0
  478. package/lib/Docker/Context.d.ts.map +1 -0
  479. package/lib/Docker/Context.js +145 -0
  480. package/lib/Docker/Context.js.map +1 -0
  481. package/lib/Docker/Docker.d.ts +190 -14
  482. package/lib/Docker/Docker.d.ts.map +1 -1
  483. package/lib/Docker/Docker.js +149 -28
  484. package/lib/Docker/Docker.js.map +1 -1
  485. package/lib/Docker/Image.d.ts +12 -0
  486. package/lib/Docker/Image.d.ts.map +1 -1
  487. package/lib/Docker/Image.js +24 -7
  488. package/lib/Docker/Image.js.map +1 -1
  489. package/lib/Docker/Network.d.ts +7 -0
  490. package/lib/Docker/Network.d.ts.map +1 -1
  491. package/lib/Docker/Network.js +16 -7
  492. package/lib/Docker/Network.js.map +1 -1
  493. package/lib/Docker/Providers.d.ts +1 -1
  494. package/lib/Docker/Providers.d.ts.map +1 -1
  495. package/lib/Docker/Providers.js +13 -1
  496. package/lib/Docker/Providers.js.map +1 -1
  497. package/lib/Docker/RemoteImage.d.ts +12 -0
  498. package/lib/Docker/RemoteImage.d.ts.map +1 -1
  499. package/lib/Docker/RemoteImage.js +22 -8
  500. package/lib/Docker/RemoteImage.js.map +1 -1
  501. package/lib/Docker/Service.d.ts +445 -0
  502. package/lib/Docker/Service.d.ts.map +1 -0
  503. package/lib/Docker/Service.js +578 -0
  504. package/lib/Docker/Service.js.map +1 -0
  505. package/lib/Docker/ServiceImage.d.ts +78 -0
  506. package/lib/Docker/ServiceImage.d.ts.map +1 -0
  507. package/lib/Docker/ServiceImage.js +232 -0
  508. package/lib/Docker/ServiceImage.js.map +1 -0
  509. package/lib/Docker/Swarm.d.ts +117 -0
  510. package/lib/Docker/Swarm.d.ts.map +1 -0
  511. package/lib/Docker/Swarm.js +149 -0
  512. package/lib/Docker/Swarm.js.map +1 -0
  513. package/lib/Docker/Volume.d.ts +11 -0
  514. package/lib/Docker/Volume.d.ts.map +1 -1
  515. package/lib/Docker/Volume.js +21 -6
  516. package/lib/Docker/Volume.js.map +1 -1
  517. package/lib/Docker/index.d.ts +3 -0
  518. package/lib/Docker/index.d.ts.map +1 -1
  519. package/lib/Docker/index.js +3 -0
  520. package/lib/Docker/index.js.map +1 -1
  521. package/lib/Http.d.ts +8 -1
  522. package/lib/Http.d.ts.map +1 -1
  523. package/lib/Http.js +7 -2
  524. package/lib/Http.js.map +1 -1
  525. package/lib/Local/LocalProvider.d.ts +182 -0
  526. package/lib/Local/LocalProvider.d.ts.map +1 -0
  527. package/lib/Local/LocalProvider.js +226 -0
  528. package/lib/Local/LocalProvider.js.map +1 -0
  529. package/lib/Local/ProviderLayer.d.ts +43 -2
  530. package/lib/Local/ProviderLayer.d.ts.map +1 -1
  531. package/lib/Local/ProviderLayer.js +80 -2
  532. package/lib/Local/ProviderLayer.js.map +1 -1
  533. package/lib/Local/RpcProvider.d.ts +7 -0
  534. package/lib/Local/RpcProvider.d.ts.map +1 -1
  535. package/lib/Local/RpcProvider.js +8 -3
  536. package/lib/Local/RpcProvider.js.map +1 -1
  537. package/lib/Local/RpcProviderProxy.d.ts.map +1 -1
  538. package/lib/Local/RpcProviderProxy.js +3 -0
  539. package/lib/Local/RpcProviderProxy.js.map +1 -1
  540. package/lib/Local/RpcServer.d.ts +8 -1
  541. package/lib/Local/RpcServer.d.ts.map +1 -1
  542. package/lib/Local/RpcServer.js +4 -1
  543. package/lib/Local/RpcServer.js.map +1 -1
  544. package/lib/Plan.d.ts +34 -0
  545. package/lib/Plan.d.ts.map +1 -1
  546. package/lib/Plan.js +127 -40
  547. package/lib/Plan.js.map +1 -1
  548. package/lib/Planetscale/AuthProvider.d.ts.map +1 -1
  549. package/lib/Planetscale/AuthProvider.js +6 -2
  550. package/lib/Planetscale/AuthProvider.js.map +1 -1
  551. package/lib/Planetscale/Branch.d.ts.map +1 -1
  552. package/lib/Planetscale/Branch.js +36 -6
  553. package/lib/Planetscale/Branch.js.map +1 -1
  554. package/lib/Planetscale/MySQL/MySQLDatabase.d.ts.map +1 -1
  555. package/lib/Planetscale/MySQL/MySQLDatabase.js +31 -6
  556. package/lib/Planetscale/MySQL/MySQLDatabase.js.map +1 -1
  557. package/lib/Planetscale/OAuthClient.d.ts +7 -1
  558. package/lib/Planetscale/OAuthClient.d.ts.map +1 -1
  559. package/lib/Planetscale/OAuthClient.js +51 -4
  560. package/lib/Planetscale/OAuthClient.js.map +1 -1
  561. package/lib/Planetscale/Postgres/PostgresDatabase.d.ts.map +1 -1
  562. package/lib/Planetscale/Postgres/PostgresDatabase.js +30 -5
  563. package/lib/Planetscale/Postgres/PostgresDatabase.js.map +1 -1
  564. package/lib/Platform.d.ts.map +1 -1
  565. package/lib/Platform.js +25 -14
  566. package/lib/Platform.js.map +1 -1
  567. package/lib/Prisma/App.d.ts +93 -0
  568. package/lib/Prisma/App.d.ts.map +1 -0
  569. package/lib/Prisma/App.js +251 -0
  570. package/lib/Prisma/App.js.map +1 -0
  571. package/lib/Prisma/AuthProvider.d.ts +28 -0
  572. package/lib/Prisma/AuthProvider.d.ts.map +1 -0
  573. package/lib/Prisma/AuthProvider.js +131 -0
  574. package/lib/Prisma/AuthProvider.js.map +1 -0
  575. package/lib/Prisma/Branch.d.ts +96 -0
  576. package/lib/Prisma/Branch.d.ts.map +1 -0
  577. package/lib/Prisma/Branch.js +236 -0
  578. package/lib/Prisma/Branch.js.map +1 -0
  579. package/lib/Prisma/Client.d.ts +178 -0
  580. package/lib/Prisma/Client.d.ts.map +1 -0
  581. package/lib/Prisma/Client.js +562 -0
  582. package/lib/Prisma/Client.js.map +1 -0
  583. package/lib/Prisma/Compute.d.ts +585 -0
  584. package/lib/Prisma/Compute.d.ts.map +1 -0
  585. package/lib/Prisma/Compute.js +1545 -0
  586. package/lib/Prisma/Compute.js.map +1 -0
  587. package/lib/Prisma/ComputeArchive.d.ts +63 -0
  588. package/lib/Prisma/ComputeArchive.d.ts.map +1 -0
  589. package/lib/Prisma/ComputeArchive.js +227 -0
  590. package/lib/Prisma/ComputeArchive.js.map +1 -0
  591. package/lib/Prisma/ComputeBuild.d.ts +106 -0
  592. package/lib/Prisma/ComputeBuild.d.ts.map +1 -0
  593. package/lib/Prisma/ComputeBuild.js +987 -0
  594. package/lib/Prisma/ComputeBuild.js.map +1 -0
  595. package/lib/Prisma/ComputeLifecycle.d.ts +84 -0
  596. package/lib/Prisma/ComputeLifecycle.d.ts.map +1 -0
  597. package/lib/Prisma/ComputeLifecycle.js +186 -0
  598. package/lib/Prisma/ComputeLifecycle.js.map +1 -0
  599. package/lib/Prisma/Connect.d.ts +122 -0
  600. package/lib/Prisma/Connect.d.ts.map +1 -0
  601. package/lib/Prisma/Connect.js +187 -0
  602. package/lib/Prisma/Connect.js.map +1 -0
  603. package/lib/Prisma/Connection.d.ts +219 -0
  604. package/lib/Prisma/Connection.d.ts.map +1 -0
  605. package/lib/Prisma/Connection.js +360 -0
  606. package/lib/Prisma/Connection.js.map +1 -0
  607. package/lib/Prisma/CustomDomain.d.ts +91 -0
  608. package/lib/Prisma/CustomDomain.d.ts.map +1 -0
  609. package/lib/Prisma/CustomDomain.js +184 -0
  610. package/lib/Prisma/CustomDomain.js.map +1 -0
  611. package/lib/Prisma/Database.d.ts +222 -0
  612. package/lib/Prisma/Database.d.ts.map +1 -0
  613. package/lib/Prisma/Database.js +418 -0
  614. package/lib/Prisma/Database.js.map +1 -0
  615. package/lib/Prisma/Deployment.d.ts +141 -0
  616. package/lib/Prisma/Deployment.d.ts.map +1 -0
  617. package/lib/Prisma/Deployment.js +353 -0
  618. package/lib/Prisma/Deployment.js.map +1 -0
  619. package/lib/Prisma/EnvironmentVariable.d.ts +105 -0
  620. package/lib/Prisma/EnvironmentVariable.d.ts.map +1 -0
  621. package/lib/Prisma/EnvironmentVariable.js +229 -0
  622. package/lib/Prisma/EnvironmentVariable.js.map +1 -0
  623. package/lib/Prisma/Internal/AppIdentity.d.ts +10 -0
  624. package/lib/Prisma/Internal/AppIdentity.d.ts.map +1 -0
  625. package/lib/Prisma/Internal/AppIdentity.js +6 -0
  626. package/lib/Prisma/Internal/AppIdentity.js.map +1 -0
  627. package/lib/Prisma/Internal/AppPromotion.d.ts +19 -0
  628. package/lib/Prisma/Internal/AppPromotion.d.ts.map +1 -0
  629. package/lib/Prisma/Internal/AppPromotion.js +107 -0
  630. package/lib/Prisma/Internal/AppPromotion.js.map +1 -0
  631. package/lib/Prisma/Internal/ArchivePlatform.d.ts +42 -0
  632. package/lib/Prisma/Internal/ArchivePlatform.d.ts.map +1 -0
  633. package/lib/Prisma/Internal/ArchivePlatform.js +214 -0
  634. package/lib/Prisma/Internal/ArchivePlatform.js.map +1 -0
  635. package/lib/Prisma/Internal/ArtifactFile.d.ts +58 -0
  636. package/lib/Prisma/Internal/ArtifactFile.d.ts.map +1 -0
  637. package/lib/Prisma/Internal/ArtifactFile.js +134 -0
  638. package/lib/Prisma/Internal/ArtifactFile.js.map +1 -0
  639. package/lib/Prisma/Internal/ArtifactUpload.d.ts +5 -0
  640. package/lib/Prisma/Internal/ArtifactUpload.d.ts.map +1 -0
  641. package/lib/Prisma/Internal/ArtifactUpload.js +51 -0
  642. package/lib/Prisma/Internal/ArtifactUpload.js.map +1 -0
  643. package/lib/Prisma/Internal/BundlePaths.d.ts +4 -0
  644. package/lib/Prisma/Internal/BundlePaths.d.ts.map +1 -0
  645. package/lib/Prisma/Internal/BundlePaths.js +16 -0
  646. package/lib/Prisma/Internal/BundlePaths.js.map +1 -0
  647. package/lib/Prisma/Internal/CleanupFailure.d.ts +8 -0
  648. package/lib/Prisma/Internal/CleanupFailure.d.ts.map +1 -0
  649. package/lib/Prisma/Internal/CleanupFailure.js +8 -0
  650. package/lib/Prisma/Internal/CleanupFailure.js.map +1 -0
  651. package/lib/Prisma/Internal/DatabaseSecrets.d.ts +35 -0
  652. package/lib/Prisma/Internal/DatabaseSecrets.d.ts.map +1 -0
  653. package/lib/Prisma/Internal/DatabaseSecrets.js +85 -0
  654. package/lib/Prisma/Internal/DatabaseSecrets.js.map +1 -0
  655. package/lib/Prisma/Internal/DeploymentActions.d.ts +5 -0
  656. package/lib/Prisma/Internal/DeploymentActions.d.ts.map +1 -0
  657. package/lib/Prisma/Internal/DeploymentActions.js +16 -0
  658. package/lib/Prisma/Internal/DeploymentActions.js.map +1 -0
  659. package/lib/Prisma/Internal/DeploymentIdentity.d.ts +8 -0
  660. package/lib/Prisma/Internal/DeploymentIdentity.d.ts.map +1 -0
  661. package/lib/Prisma/Internal/DeploymentIdentity.js +12 -0
  662. package/lib/Prisma/Internal/DeploymentIdentity.js.map +1 -0
  663. package/lib/Prisma/Internal/DeploymentObserve.d.ts +3 -0
  664. package/lib/Prisma/Internal/DeploymentObserve.d.ts.map +1 -0
  665. package/lib/Prisma/Internal/DeploymentObserve.js +2 -0
  666. package/lib/Prisma/Internal/DeploymentObserve.js.map +1 -0
  667. package/lib/Prisma/Internal/EnvName.d.ts +7 -0
  668. package/lib/Prisma/Internal/EnvName.d.ts.map +1 -0
  669. package/lib/Prisma/Internal/EnvName.js +23 -0
  670. package/lib/Prisma/Internal/EnvName.js.map +1 -0
  671. package/lib/Prisma/Internal/HttpClient.d.ts +21 -0
  672. package/lib/Prisma/Internal/HttpClient.d.ts.map +1 -0
  673. package/lib/Prisma/Internal/HttpClient.js +23 -0
  674. package/lib/Prisma/Internal/HttpClient.js.map +1 -0
  675. package/lib/Prisma/Operations.d.ts +143 -0
  676. package/lib/Prisma/Operations.d.ts.map +1 -0
  677. package/lib/Prisma/Operations.js +75 -0
  678. package/lib/Prisma/Operations.js.map +1 -0
  679. package/lib/Prisma/Postgres.d.ts +25 -0
  680. package/lib/Prisma/Postgres.d.ts.map +1 -0
  681. package/lib/Prisma/Postgres.js +24 -0
  682. package/lib/Prisma/Postgres.js.map +1 -0
  683. package/lib/Prisma/PostgresOrigin.d.ts +31 -0
  684. package/lib/Prisma/PostgresOrigin.d.ts.map +1 -0
  685. package/lib/Prisma/PostgresOrigin.js +30 -0
  686. package/lib/Prisma/PostgresOrigin.js.map +1 -0
  687. package/lib/Prisma/PrismaDevDatabase.d.ts +25 -0
  688. package/lib/Prisma/PrismaDevDatabase.d.ts.map +1 -0
  689. package/lib/Prisma/PrismaDevDatabase.js +280 -0
  690. package/lib/Prisma/PrismaDevDatabase.js.map +1 -0
  691. package/lib/Prisma/PrismaEnvironment.d.ts +14 -0
  692. package/lib/Prisma/PrismaEnvironment.d.ts.map +1 -0
  693. package/lib/Prisma/PrismaEnvironment.js +50 -0
  694. package/lib/Prisma/PrismaEnvironment.js.map +1 -0
  695. package/lib/Prisma/PrismaLogs.d.ts +42 -0
  696. package/lib/Prisma/PrismaLogs.d.ts.map +1 -0
  697. package/lib/Prisma/PrismaLogs.js +318 -0
  698. package/lib/Prisma/PrismaLogs.js.map +1 -0
  699. package/lib/Prisma/Project.d.ts +123 -0
  700. package/lib/Prisma/Project.d.ts.map +1 -0
  701. package/lib/Prisma/Project.js +366 -0
  702. package/lib/Prisma/Project.js.map +1 -0
  703. package/lib/Prisma/Providers.d.ts +63 -0
  704. package/lib/Prisma/Providers.d.ts.map +1 -0
  705. package/lib/Prisma/Providers.js +306 -0
  706. package/lib/Prisma/Providers.js.map +1 -0
  707. package/lib/Prisma/Refs.d.ts +19 -0
  708. package/lib/Prisma/Refs.d.ts.map +1 -0
  709. package/lib/Prisma/Refs.js +25 -0
  710. package/lib/Prisma/Refs.js.map +1 -0
  711. package/lib/Prisma/SourceRepository.d.ts +127 -0
  712. package/lib/Prisma/SourceRepository.d.ts.map +1 -0
  713. package/lib/Prisma/SourceRepository.js +239 -0
  714. package/lib/Prisma/SourceRepository.js.map +1 -0
  715. package/lib/Prisma/Types.d.ts +539 -0
  716. package/lib/Prisma/Types.d.ts.map +1 -0
  717. package/lib/Prisma/Types.js +13 -0
  718. package/lib/Prisma/Types.js.map +1 -0
  719. package/lib/Prisma/index.d.ts +25 -0
  720. package/lib/Prisma/index.d.ts.map +1 -0
  721. package/lib/Prisma/index.js +24 -0
  722. package/lib/Prisma/index.js.map +1 -0
  723. package/lib/Provider.d.ts +57 -5
  724. package/lib/Provider.d.ts.map +1 -1
  725. package/lib/Provider.js +54 -30
  726. package/lib/Provider.js.map +1 -1
  727. package/lib/ProviderMode.d.ts +93 -0
  728. package/lib/ProviderMode.d.ts.map +1 -0
  729. package/lib/ProviderMode.js +77 -0
  730. package/lib/ProviderMode.js.map +1 -0
  731. package/lib/Resource.d.ts +8 -0
  732. package/lib/Resource.d.ts.map +1 -1
  733. package/lib/Resource.js +31 -0
  734. package/lib/Resource.js.map +1 -1
  735. package/lib/RuntimeContext.d.ts +7 -0
  736. package/lib/RuntimeContext.d.ts.map +1 -1
  737. package/lib/RuntimeContext.js.map +1 -1
  738. package/lib/Server/Process.d.ts +8 -0
  739. package/lib/Server/Process.d.ts.map +1 -1
  740. package/lib/Server/Process.js +29 -0
  741. package/lib/Server/Process.js.map +1 -1
  742. package/lib/State/InMemoryState.d.ts.map +1 -1
  743. package/lib/State/InMemoryState.js +2 -0
  744. package/lib/State/InMemoryState.js.map +1 -1
  745. package/lib/State/ResourceState.d.ts +18 -0
  746. package/lib/State/ResourceState.d.ts.map +1 -1
  747. package/lib/Sync.d.ts.map +1 -1
  748. package/lib/Sync.js +8 -2
  749. package/lib/Sync.js.map +1 -1
  750. package/lib/Telemetry.d.ts +150 -0
  751. package/lib/Telemetry.d.ts.map +1 -0
  752. package/lib/Telemetry.js +512 -0
  753. package/lib/Telemetry.js.map +1 -0
  754. package/lib/Test/Alchemy.d.ts.map +1 -1
  755. package/lib/Test/Alchemy.js +3 -1
  756. package/lib/Test/Alchemy.js.map +1 -1
  757. package/lib/Test/Core.d.ts +2 -2
  758. package/lib/Test/Core.d.ts.map +1 -1
  759. package/lib/Test/Core.js +1 -8
  760. package/lib/Test/Core.js.map +1 -1
  761. package/lib/Util/Clank.d.ts +11 -6
  762. package/lib/Util/Clank.d.ts.map +1 -1
  763. package/lib/Util/Clank.js +22 -9
  764. package/lib/Util/Clank.js.map +1 -1
  765. package/lib/Util/zip.d.ts +11 -0
  766. package/lib/Util/zip.d.ts.map +1 -1
  767. package/lib/Util/zip.js +30 -0
  768. package/lib/Util/zip.js.map +1 -1
  769. package/lib/index.d.ts +3 -0
  770. package/lib/index.d.ts.map +1 -1
  771. package/lib/index.js +3 -0
  772. package/lib/index.js.map +1 -1
  773. package/package.json +46 -13
  774. package/src/AWS/ApiGateway/common.ts +9 -0
  775. package/src/AWS/Bedrock/LanguageModel.ts +883 -0
  776. package/src/AWS/Bedrock/LanguageModelHttp.ts +36 -0
  777. package/src/AWS/Bedrock/index.ts +2 -0
  778. package/src/AWS/CloudFront/Distribution.ts +116 -6
  779. package/src/AWS/CostExplorer/common.ts +23 -2
  780. package/src/AWS/DataSync/Task.ts +30 -15
  781. package/src/AWS/EC2/Vpc.ts +35 -21
  782. package/src/AWS/EC2/hosted.ts +10 -2
  783. package/src/AWS/ECR/ImageSource.ts +10 -2
  784. package/src/AWS/ECR/Repository.ts +39 -4
  785. package/src/AWS/ECS/Service.ts +322 -22
  786. package/src/AWS/ECS/Task.ts +82 -32
  787. package/src/AWS/EKS/Deployment.ts +10 -2
  788. package/src/AWS/EKS/Job.ts +10 -2
  789. package/src/AWS/Lambda/Function.ts +101 -43
  790. package/src/AWS/Lambda/HttpServer.ts +7 -1
  791. package/src/AWS/Lambda/LayerVersion.ts +490 -0
  792. package/src/AWS/Lambda/MicrovmBundle.ts +10 -2
  793. package/src/AWS/Lambda/index.ts +1 -0
  794. package/src/AWS/Providers.ts +10 -0
  795. package/src/AWS/SES/ActiveReceiptRuleSet.ts +88 -0
  796. package/src/AWS/SES/ReceiptFilter.ts +169 -0
  797. package/src/AWS/SES/ReceiptRule.ts +300 -0
  798. package/src/AWS/SES/ReceiptRuleSet.ts +164 -0
  799. package/src/AWS/SES/SendBounce.ts +44 -0
  800. package/src/AWS/SES/SendBounceHttp.ts +13 -0
  801. package/src/AWS/SES/index.ts +6 -0
  802. package/src/AWS/StateStore/State.ts +159 -42
  803. package/src/Apply.ts +199 -83
  804. package/src/Axiom/Telemetry.ts +119 -0
  805. package/src/Axiom/index.ts +1 -0
  806. package/src/Bundle/Bundle.ts +17 -7
  807. package/src/Bundle/BundleAnalyzerPlugin.ts +9 -4
  808. package/src/Bundle/PurePlugin.ts +19 -9
  809. package/src/Cli/checkVersion.ts +102 -16
  810. package/src/Cli/commands/_shared.ts +2 -0
  811. package/src/Cli/commands/deploy.ts +3 -14
  812. package/src/Cli/commands/logs.ts +6 -1
  813. package/src/Cli/commands/tail.ts +6 -1
  814. package/src/Cli/main.ts +5 -4
  815. package/src/Cloudflare/AI/Search.ts +4 -18
  816. package/src/Cloudflare/AI/SearchInstance.ts +7 -26
  817. package/src/Cloudflare/ApiToken/AccountApiToken.ts +3 -0
  818. package/src/Cloudflare/Auth/AuthProvider.ts +17 -3
  819. package/src/Cloudflare/Auth/OAuthClient.ts +58 -2
  820. package/src/Cloudflare/Containers/Container.ts +92 -3
  821. package/src/Cloudflare/Containers/ContainerApplication.ts +31 -5
  822. package/src/Cloudflare/Containers/ContainerBundle.ts +10 -2
  823. package/src/Cloudflare/Containers/ContainerProvider.ts +62 -0
  824. package/src/Cloudflare/Credentials.ts +69 -9
  825. package/src/Cloudflare/D1/ApplyMigrations.ts +72 -77
  826. package/src/Cloudflare/D1/Database.ts +169 -2
  827. package/src/Cloudflare/D1/LocalD1Gateway.ts +150 -0
  828. package/src/Cloudflare/D1/QueryDatabaseHttpClient.ts +49 -11
  829. package/src/Cloudflare/D1/QueryDatabaseLocal.ts +44 -2
  830. package/src/Cloudflare/DNS/Record.ts +30 -4
  831. package/src/Cloudflare/Email/Address.ts +18 -1
  832. package/src/Cloudflare/Email/SendBinding.ts +1 -0
  833. package/src/Cloudflare/Email/SendEmail.ts +13 -0
  834. package/src/Cloudflare/Flagship/App.ts +18 -6
  835. package/src/Cloudflare/KV/LocalKVGateway.ts +86 -0
  836. package/src/Cloudflare/KV/Namespace.ts +47 -1
  837. package/src/Cloudflare/KV/NamespaceLocal.ts +29 -6
  838. package/src/Cloudflare/KV/ReadNamespaceLocal.ts +5 -1
  839. package/src/Cloudflare/KV/ReadWriteNamespaceLocal.ts +5 -1
  840. package/src/Cloudflare/KV/WriteNamespaceLocal.ts +5 -1
  841. package/src/Cloudflare/LocalGateway.ts +86 -0
  842. package/src/Cloudflare/LocalRuntime.ts +21 -1
  843. package/src/Cloudflare/Logs.ts +58 -28
  844. package/src/Cloudflare/Providers.ts +4 -2
  845. package/src/Cloudflare/Queues/Consumer.ts +70 -2
  846. package/src/Cloudflare/Queues/Queue.ts +7 -16
  847. package/src/Cloudflare/Queues/QueueBinding.ts +21 -0
  848. package/src/Cloudflare/Queues/QueueShim.ts +193 -0
  849. package/src/Cloudflare/R2/Bucket.ts +55 -1
  850. package/src/Cloudflare/R2/BucketBinding.ts +17 -3
  851. package/src/Cloudflare/R2/BucketLocal.ts +25 -8
  852. package/src/Cloudflare/R2/LocalR2Gateway.ts +84 -0
  853. package/src/Cloudflare/R2/ReadBucketLocal.ts +7 -1
  854. package/src/Cloudflare/R2/ReadWriteBucketLocal.ts +7 -1
  855. package/src/Cloudflare/R2/WriteBucketLocal.ts +7 -1
  856. package/src/Cloudflare/Ruleset/AccountEntrypoint.ts +5 -1
  857. package/src/Cloudflare/Ruleset/Ruleset.ts +20 -6
  858. package/src/Cloudflare/Rum/Site.ts +11 -4
  859. package/src/Cloudflare/StateStore/Api.ts +1 -1
  860. package/src/Cloudflare/Tunnel/Tunnel.ts +23 -1
  861. package/src/Cloudflare/Website/StaticSite.ts +117 -28
  862. package/src/Cloudflare/Website/Vite.ts +33 -19
  863. package/src/Cloudflare/Workers/Compatibility.ts +27 -7
  864. package/src/Cloudflare/Workers/DurableObject.ts +22 -0
  865. package/src/Cloudflare/Workers/DurableObjectBridge.ts +29 -10
  866. package/src/Cloudflare/Workers/InferEnv.ts +74 -59
  867. package/src/Cloudflare/Workers/LocalWorkerProvider.ts +619 -401
  868. package/src/Cloudflare/Workers/Worker.ts +676 -28
  869. package/src/Cloudflare/Workers/WorkerAsyncBindings.ts +209 -25
  870. package/src/Cloudflare/Workers/WorkerBinding.ts +67 -3
  871. package/src/Cloudflare/Workers/WorkerBridge.ts +26 -4
  872. package/src/Cloudflare/Workers/WorkerBundle.ts +6 -1
  873. package/src/Cloudflare/Workers/WorkerProvider.ts +2269 -277
  874. package/src/Cloudflare/Workflows/WorkflowBridge.ts +31 -13
  875. package/src/Command/Build.ts +8 -8
  876. package/src/Command/Command.ts +217 -42
  877. package/src/Command/Dev.ts +79 -107
  878. package/src/Command/Exec.ts +11 -2
  879. package/src/Command/Memo.ts +27 -3
  880. package/src/Command/Redaction.ts +181 -0
  881. package/src/Destroy.ts +1 -10
  882. package/src/Docker/Container.ts +55 -15
  883. package/src/Docker/Context.ts +236 -0
  884. package/src/Docker/Docker.ts +393 -43
  885. package/src/Docker/Image.ts +28 -7
  886. package/src/Docker/Network.ts +38 -14
  887. package/src/Docker/Providers.ts +16 -1
  888. package/src/Docker/RemoteImage.ts +23 -7
  889. package/src/Docker/Service.ts +1119 -0
  890. package/src/Docker/ServiceImage.ts +331 -0
  891. package/src/Docker/Swarm.ts +226 -0
  892. package/src/Docker/Volume.ts +25 -6
  893. package/src/Docker/index.ts +3 -0
  894. package/src/Http.ts +15 -2
  895. package/src/Local/LocalProvider.ts +518 -0
  896. package/src/Local/ProviderLayer.ts +113 -11
  897. package/src/Local/RpcProvider.ts +10 -5
  898. package/src/Local/RpcProviderProxy.ts +4 -1
  899. package/src/Local/RpcServer.ts +20 -2
  900. package/src/Plan.ts +182 -54
  901. package/src/Planetscale/AuthProvider.ts +15 -2
  902. package/src/Planetscale/Branch.ts +39 -6
  903. package/src/Planetscale/MySQL/MySQLDatabase.ts +34 -6
  904. package/src/Planetscale/OAuthClient.ts +59 -3
  905. package/src/Planetscale/Postgres/PostgresDatabase.ts +33 -5
  906. package/src/Platform.ts +29 -17
  907. package/src/Prisma/App.ts +433 -0
  908. package/src/Prisma/AuthProvider.ts +260 -0
  909. package/src/Prisma/Branch.ts +417 -0
  910. package/src/Prisma/Client.ts +1337 -0
  911. package/src/Prisma/Compute.ts +2878 -0
  912. package/src/Prisma/ComputeArchive.ts +432 -0
  913. package/src/Prisma/ComputeBuild.ts +1652 -0
  914. package/src/Prisma/ComputeLifecycle.ts +391 -0
  915. package/src/Prisma/Connect.ts +433 -0
  916. package/src/Prisma/Connection.ts +643 -0
  917. package/src/Prisma/CustomDomain.ts +373 -0
  918. package/src/Prisma/Database.ts +850 -0
  919. package/src/Prisma/Deployment.ts +661 -0
  920. package/src/Prisma/EnvironmentVariable.ts +417 -0
  921. package/src/Prisma/Internal/AppIdentity.ts +19 -0
  922. package/src/Prisma/Internal/AppPromotion.ts +172 -0
  923. package/src/Prisma/Internal/ArchivePlatform.ts +320 -0
  924. package/src/Prisma/Internal/ArtifactFile.ts +242 -0
  925. package/src/Prisma/Internal/ArtifactUpload.ts +86 -0
  926. package/src/Prisma/Internal/BundlePaths.ts +28 -0
  927. package/src/Prisma/Internal/CleanupFailure.ts +17 -0
  928. package/src/Prisma/Internal/DatabaseSecrets.ts +175 -0
  929. package/src/Prisma/Internal/DeploymentActions.ts +59 -0
  930. package/src/Prisma/Internal/DeploymentIdentity.ts +25 -0
  931. package/src/Prisma/Internal/DeploymentObserve.ts +6 -0
  932. package/src/Prisma/Internal/EnvName.ts +24 -0
  933. package/src/Prisma/Internal/HttpClient.ts +30 -0
  934. package/src/Prisma/Operations.ts +259 -0
  935. package/src/Prisma/Postgres.ts +25 -0
  936. package/src/Prisma/PostgresOrigin.ts +52 -0
  937. package/src/Prisma/PrismaDevDatabase.ts +395 -0
  938. package/src/Prisma/PrismaEnvironment.ts +89 -0
  939. package/src/Prisma/PrismaLogs.ts +434 -0
  940. package/src/Prisma/Project.ts +687 -0
  941. package/src/Prisma/Providers.ts +471 -0
  942. package/src/Prisma/Refs.ts +69 -0
  943. package/src/Prisma/SourceRepository.ts +455 -0
  944. package/src/Prisma/Types.ts +611 -0
  945. package/src/Prisma/index.ts +24 -0
  946. package/src/Provider.ts +155 -39
  947. package/src/ProviderMode.ts +123 -0
  948. package/src/Resource.ts +46 -0
  949. package/src/RuntimeContext.ts +7 -0
  950. package/src/Server/Process.ts +33 -0
  951. package/src/State/InMemoryState.ts +2 -0
  952. package/src/State/ResourceState.ts +18 -0
  953. package/src/Sync.ts +14 -2
  954. package/src/Telemetry.ts +736 -0
  955. package/src/Test/Alchemy.ts +3 -1
  956. package/src/Test/Core.ts +1 -8
  957. package/src/Util/Clank.ts +37 -11
  958. package/src/Util/zip.ts +39 -0
  959. package/src/index.ts +3 -0
@@ -1,14 +1,19 @@
1
1
  import * as durableObjectsApi from "@distilled.cloud/cloudflare/durable-objects";
2
+ import * as rulesets from "@distilled.cloud/cloudflare/rulesets";
2
3
  import * as workers from "@distilled.cloud/cloudflare/workers";
3
4
  import * as wfp from "@distilled.cloud/cloudflare/workers-for-platforms";
4
5
  import * as zones from "@distilled.cloud/cloudflare/zones";
6
+ import * as Config from "effect/Config";
5
7
  import * as Data from "effect/Data";
6
8
  import * as Effect from "effect/Effect";
9
+ import * as Layer from "effect/Layer";
10
+ import * as Option from "effect/Option";
7
11
  import * as Path from "effect/Path";
8
12
  import * as Predicate from "effect/Predicate";
9
13
  import * as Redacted from "effect/Redacted";
10
14
  import * as Schedule from "effect/Schedule";
11
15
  import * as Stream from "effect/Stream";
16
+ import { isHttpClientError } from "effect/unstable/http/HttpClientError";
12
17
  import * as crypto from "node:crypto";
13
18
  import { Unowned } from "../../AdoptPolicy.ts";
14
19
  import * as Artifacts from "../../Artifacts.ts";
@@ -19,10 +24,12 @@ import * as ProviderLayer from "../../Local/ProviderLayer.ts";
19
24
  import * as Provider from "../../Provider.ts";
20
25
  import { type ResourceBinding } from "../../Resource.ts";
21
26
  import { Stack } from "../../Stack.ts";
27
+ import { cachedFunction } from "../../Util/cached-function.ts";
22
28
  import { sha256Object } from "../../Util/sha256.ts";
23
29
  import { CloudflareEnvironment } from "../CloudflareEnvironment.ts";
30
+ import { localRuntimeServices } from "../LocalRuntime.ts";
24
31
  import { CloudflareLogs } from "../Logs.ts";
25
- import { listAllZones, resolveZoneId } from "../Zone/lookup.ts";
32
+ import { resolveZoneId } from "../Zone/lookup.ts";
26
33
  import {
27
34
  mergeAssetsConfigFiles,
28
35
  readAssets,
@@ -33,13 +40,23 @@ import { getCompatibility } from "./Compatibility.ts";
33
40
  import { isDurableObjectExport } from "./DurableObject.ts";
34
41
  import { LocalWorkerProvider } from "./LocalWorkerProvider.ts";
35
42
  import {
43
+ isSelfUrl,
36
44
  Worker,
37
45
  type ViteOptions,
38
46
  type WorkerProps,
39
47
  type WorkerRouteConfig,
48
+ type WorkerVersionAffinity,
40
49
  } from "./Worker.ts";
41
- import { getCacheBinding, getCronBindings } from "./WorkerAsyncBindings.ts";
42
- import type { WorkerBinding, WorkerSettingsBinding } from "./WorkerBinding.ts";
50
+ import {
51
+ getCacheBinding,
52
+ getCronBindings,
53
+ isContainerDecl,
54
+ } from "./WorkerAsyncBindings.ts";
55
+ import type {
56
+ WireWorkerBinding,
57
+ WorkerBinding,
58
+ WorkerSettingsBinding,
59
+ } from "./WorkerBinding.ts";
43
60
  import { isPythonMain, readPythonWorkerBundle } from "./PythonWorkerBundle.ts";
44
61
  import { readPrebuiltWorkerBundle, WorkerBundle } from "./WorkerBundle.ts";
45
62
  import { isWorkerLoader } from "./WorkerLoader.ts";
@@ -125,6 +142,433 @@ export const resolveNamespaceName = (
125
142
  return (namespace as { name?: string }).name;
126
143
  };
127
144
 
145
+ /**
146
+ * A Worker's `version` configuration is invalid — a prop that can't be
147
+ * combined with `version.parent` (script-level settings belong to the
148
+ * parent), a locally-hosted Durable Object / Workflow class on a version
149
+ * worker, an out-of-range `traffic`, or a gradual rollout that requires
150
+ * changes the versions API can't carry (assets, DO migrations).
151
+ */
152
+ export class WorkerVersionConfigError extends Data.TaggedError(
153
+ "WorkerVersionConfigError",
154
+ )<{
155
+ message: string;
156
+ }> {}
157
+
158
+ /**
159
+ * Resolve the parent script *name* from a resolved `version.parent` prop or
160
+ * persisted props. The engine resolves a passed {@link Worker} (or
161
+ * `Worker.ref(...)`) to its Attributes object — possibly stables-only during
162
+ * planning, but `workerName` is always a stable — so the value is either the
163
+ * script name string, that attributes object, or `undefined`.
164
+ *
165
+ * @internal
166
+ */
167
+ export const resolveVersionParentName = (
168
+ version: WorkerProps["version"],
169
+ ): string | undefined => {
170
+ const parent = version?.parent;
171
+ if (parent == null) return undefined;
172
+ if (typeof parent === "string") return parent;
173
+ const workerName = (parent as { workerName?: unknown }).workerName;
174
+ return typeof workerName === "string" ? workerName : undefined;
175
+ };
176
+
177
+ /**
178
+ * The traffic percentage a *self-owned* Worker's new version should receive,
179
+ * or `undefined` for the default full cutover. Only a `version` prop without
180
+ * a `parent` participates — version workers handle traffic separately.
181
+ *
182
+ * @internal
183
+ */
184
+ const getSelfRolloutTraffic = (news: WorkerProps): number | undefined => {
185
+ if (!news.version || news.version.parent != null) return undefined;
186
+ const traffic = news.version.traffic;
187
+ return traffic === undefined || traffic >= 100 ? undefined : traffic;
188
+ };
189
+
190
+ const validateTraffic = (traffic: number | undefined) =>
191
+ traffic !== undefined &&
192
+ (!Number.isFinite(traffic) || traffic < 0 || traffic > 100)
193
+ ? Effect.fail(
194
+ new WorkerVersionConfigError({
195
+ message: `version.traffic must be a percentage between 0 and 100, got ${traffic}`,
196
+ }),
197
+ )
198
+ : Effect.void;
199
+
200
+ /** The request header Cloudflare hashes to pin a request to a version. */
201
+ const AFFINITY_HEADER = "Cloudflare-Workers-Version-Key";
202
+
203
+ /**
204
+ * `version.affinity` normalized to a single key source plus the optional
205
+ * IP fallback.
206
+ *
207
+ * @internal exported for unit testing.
208
+ */
209
+ export interface ResolvedVersionAffinity {
210
+ source:
211
+ | { kind: "cookie" | "header"; name: string }
212
+ | { kind: "ip" }
213
+ | { kind: "key"; expression: string };
214
+ ipFallback: boolean;
215
+ }
216
+
217
+ // Cookie / header names are interpolated into a double-quoted Rules-language
218
+ // string literal — restrict them to the token characters real-world names
219
+ // use so a name can never terminate the literal or smuggle expression text.
220
+ const AFFINITY_NAME_PATTERN = /^[A-Za-z0-9_.-]+$/;
221
+
222
+ /**
223
+ * Validate `version.affinity` and normalize it to its key source: exactly
224
+ * one of `cookie` / `header` / `key`, or `ip: true` alone; `ip` combines
225
+ * with `cookie` / `header` as the absent-source fallback.
226
+ *
227
+ * @internal exported for unit testing.
228
+ */
229
+ export const resolveVersionAffinity = (
230
+ affinity: WorkerVersionAffinity,
231
+ ): Effect.Effect<ResolvedVersionAffinity, WorkerVersionConfigError> =>
232
+ Effect.gen(function* () {
233
+ const declared = [
234
+ ...(affinity.cookie !== undefined ? ["cookie"] : []),
235
+ ...(affinity.header !== undefined ? ["header"] : []),
236
+ ...(affinity.key !== undefined ? ["key"] : []),
237
+ ];
238
+ if (declared.length > 1) {
239
+ return yield* Effect.fail(
240
+ new WorkerVersionConfigError({
241
+ message: `version.affinity accepts exactly one key source, got ${declared.join(" and ")}. Combine sources with a raw \`key\` expression instead.`,
242
+ }),
243
+ );
244
+ }
245
+ if (declared.length === 0 && affinity.ip !== true) {
246
+ return yield* Effect.fail(
247
+ new WorkerVersionConfigError({
248
+ message:
249
+ "version.affinity requires a key source: set `cookie`, `header`, `key`, or `ip: true`.",
250
+ }),
251
+ );
252
+ }
253
+ if (affinity.key !== undefined && affinity.ip === true) {
254
+ return yield* Effect.fail(
255
+ new WorkerVersionConfigError({
256
+ message:
257
+ "version.affinity: `ip` is the fallback for an absent `cookie`/`header` — a raw `key` expression has no absence condition to fall back from. Fold `ip.src` into the expression instead.",
258
+ }),
259
+ );
260
+ }
261
+ for (const [prop, name] of [
262
+ ["cookie", affinity.cookie],
263
+ ["header", affinity.header],
264
+ ] as const) {
265
+ if (name !== undefined && !AFFINITY_NAME_PATTERN.test(name)) {
266
+ return yield* Effect.fail(
267
+ new WorkerVersionConfigError({
268
+ message: `version.affinity.${prop} '${name}' is not a valid ${prop} name: expected only letters, digits, '_', '.', and '-'.`,
269
+ }),
270
+ );
271
+ }
272
+ }
273
+ const source: ResolvedVersionAffinity["source"] =
274
+ affinity.cookie !== undefined
275
+ ? { kind: "cookie", name: affinity.cookie }
276
+ : affinity.header !== undefined
277
+ ? // Rules-language header map keys are lowercase.
278
+ { kind: "header", name: affinity.header.toLowerCase() }
279
+ : affinity.key !== undefined
280
+ ? { kind: "key", expression: affinity.key }
281
+ : { kind: "ip" };
282
+ return {
283
+ source,
284
+ ipFallback:
285
+ affinity.ip === true &&
286
+ (source.kind === "cookie" || source.kind === "header"),
287
+ };
288
+ });
289
+
290
+ /** A hostname a Worker serves on within one zone. */
291
+ interface AffinityZoneHost {
292
+ host: string;
293
+ /** `true` when `host` came from a route pattern containing `*`. */
294
+ wildcard: boolean;
295
+ }
296
+
297
+ /**
298
+ * The `http.host` clause scoping a zone's affinity rules to the Worker's
299
+ * own hostnames, so unrelated zone traffic — and other Workers' rollouts
300
+ * on the same zone — never get this Worker's version key.
301
+ *
302
+ * @internal exported for unit testing.
303
+ */
304
+ export const affinityHostExpression = (
305
+ hosts: readonly AffinityZoneHost[],
306
+ ): string => {
307
+ const dedupe = (values: string[]) => [...new Set(values)].sort();
308
+ const exact = dedupe(hosts.filter((h) => !h.wildcard).map((h) => h.host));
309
+ const wild = dedupe(hosts.filter((h) => h.wildcard).map((h) => h.host));
310
+ const clauses = [
311
+ ...(exact.length === 1
312
+ ? [`http.host eq "${exact[0]}"`]
313
+ : exact.length > 1
314
+ ? [`http.host in {${exact.map((h) => `"${h}"`).join(" ")}}`]
315
+ : []),
316
+ ...wild.map((h) => `http.host wildcard "${h}"`),
317
+ ];
318
+ return clauses.length === 1 ? clauses[0] : `(${clauses.join(" or ")})`;
319
+ };
320
+
321
+ interface AffinityRuleSpec {
322
+ description: string;
323
+ expression: string;
324
+ /** Rules-language expression producing the header value. */
325
+ value: string;
326
+ }
327
+
328
+ const affinityRulePrefix = (scriptName: string) =>
329
+ `alchemy:worker:${scriptName}:affinity`;
330
+
331
+ /**
332
+ * The transform rules pinning one zone's traffic: a primary rule filling
333
+ * the version-key header from the configured source, plus — for
334
+ * `cookie`/`header` sources with `ip: true` — a fallback rule keying
335
+ * requests that lack the source by client IP.
336
+ *
337
+ * @internal exported for unit testing.
338
+ */
339
+ export const buildAffinityZoneRules = (
340
+ scriptName: string,
341
+ affinity: ResolvedVersionAffinity,
342
+ hosts: readonly AffinityZoneHost[],
343
+ ): AffinityRuleSpec[] => {
344
+ const prefix = affinityRulePrefix(scriptName);
345
+ const hostExpr = affinityHostExpression(hosts);
346
+ const { source } = affinity;
347
+ if (source.kind === "ip" || source.kind === "key") {
348
+ return [
349
+ {
350
+ description: `${prefix}:key`,
351
+ expression: hostExpr,
352
+ value: source.kind === "ip" ? "to_string(ip.src)" : source.expression,
353
+ },
354
+ ];
355
+ }
356
+ const field =
357
+ source.kind === "cookie"
358
+ ? `http.request.cookies["${source.name}"]`
359
+ : `http.request.headers["${source.name}"]`;
360
+ return [
361
+ {
362
+ description: `${prefix}:key`,
363
+ expression: `${hostExpr} and len(${field}) > 0`,
364
+ value: `${field}[0]`,
365
+ },
366
+ ...(affinity.ipFallback
367
+ ? [
368
+ {
369
+ // An absent cookie/header is a *missing* value in the Rules
370
+ // language, and every comparison on missing evaluates false —
371
+ // `len(field) == 0` can never match. `not (len(field) > 0)`
372
+ // is the complement that does: missing → false → `not` → true.
373
+ description: `${prefix}:ip`,
374
+ expression: `${hostExpr} and not (len(${field}) > 0)`,
375
+ value: "to_string(ip.src)",
376
+ },
377
+ ]
378
+ : []),
379
+ ];
380
+ };
381
+
382
+ /**
383
+ * A Worker's `domain` configuration is invalid — a hostname appears in more
384
+ * than one role (name/aliases/redirects), or a redirect targets itself.
385
+ */
386
+ export class WorkerDomainConfigError extends Data.TaggedError(
387
+ "WorkerDomainConfigError",
388
+ )<{
389
+ message: string;
390
+ }> {}
391
+
392
+ /**
393
+ * The resolved shape of `WorkerProps.workersDev`: `enabled` drives the
394
+ * stable `<name>.<account>.workers.dev` URL, `previewsEnabled` the
395
+ * per-version preview URLs. The two toggles are independent on the
396
+ * Cloudflare API.
397
+ *
398
+ * @internal exported for unit testing.
399
+ */
400
+ export interface ResolvedWorkersDev {
401
+ enabled: boolean;
402
+ previewsEnabled: boolean;
403
+ }
404
+
405
+ /**
406
+ * Resolve the `workersDev` prop to its full shape. `true` / omitted means
407
+ * "default workers.dev behavior" (stable URL + version previews), `false`
408
+ * disables both, and the object form fills unset toggles with `true`.
409
+ *
410
+ * @internal exported for unit testing.
411
+ */
412
+ export const resolveWorkersDev = (
413
+ workersDev: WorkerProps["workersDev"],
414
+ ): ResolvedWorkersDev => {
415
+ if (workersDev === undefined || workersDev === true) {
416
+ return { enabled: true, previewsEnabled: true };
417
+ }
418
+ if (workersDev === false) {
419
+ return { enabled: false, previewsEnabled: false };
420
+ }
421
+ return {
422
+ enabled: workersDev.enabled ?? true,
423
+ previewsEnabled: workersDev.previewsEnabled ?? true,
424
+ };
425
+ };
426
+
427
+ /**
428
+ * The resolved shape of `WorkerProps.domain`: the canonical hostname plus
429
+ * alias and redirect hostname lists, all punycode-normalized and
430
+ * de-duplicated.
431
+ *
432
+ * @internal exported for unit testing.
433
+ */
434
+ export interface ResolvedWorkerDomain {
435
+ name: string;
436
+ aliases: string[];
437
+ redirects: string[];
438
+ }
439
+
440
+ // Convert non-ASCII hostnames (emoji, IDN, etc.) to punycode so the
441
+ // Cloudflare API receives the form it stores domains in. `new URL(...)`
442
+ // does IDNA via WHATWG URL parsing — `📦.alchemy.run` → `xn--5z8h.alchemy.run`.
443
+ const toPunycode = (hostname: string): string => {
444
+ try {
445
+ return new URL(`https://${hostname}`).hostname;
446
+ } catch {
447
+ return hostname;
448
+ }
449
+ };
450
+
451
+ /**
452
+ * Resolve the `domain` prop to its full shape — a bare string is shorthand
453
+ * for `{ name }`. Hostnames are punycode-normalized and de-duplicated;
454
+ * a hostname may only play one role, so aliases/redirects that repeat the
455
+ * canonical name (or each other) fail with a typed error.
456
+ *
457
+ * @internal exported for unit testing.
458
+ */
459
+ export const resolveWorkerDomain = (
460
+ // `string[]` is the pre-redesign prop shape — it can still reach us from
461
+ // persisted `olds` written by older providers (read's classification).
462
+ domain: WorkerProps["domain"] | string[],
463
+ ): Effect.Effect<ResolvedWorkerDomain | undefined, WorkerDomainConfigError> =>
464
+ Effect.gen(function* () {
465
+ if (domain === undefined || domain === null) return undefined;
466
+ // Legacy array form: the first hostname was the primary custom domain
467
+ // (`url = domains[0]` back then), the rest map to aliases. A legacy
468
+ // empty array was the explicit detach-all — no domain.
469
+ const config =
470
+ typeof domain === "string"
471
+ ? { name: domain }
472
+ : Array.isArray(domain)
473
+ ? domain.length > 0
474
+ ? { name: domain[0], aliases: domain.slice(1) }
475
+ : undefined
476
+ : domain;
477
+ if (config === undefined) return undefined;
478
+ const name = toPunycode(config.name);
479
+ const aliases = Array.from(new Set((config.aliases ?? []).map(toPunycode)));
480
+ const redirects = Array.from(
481
+ new Set((config.redirects ?? []).map(toPunycode)),
482
+ );
483
+ const overlap = [
484
+ ...aliases.filter((h) => h === name),
485
+ ...redirects.filter((h) => h === name || aliases.includes(h)),
486
+ ];
487
+ if (overlap.length > 0) {
488
+ return yield* Effect.fail(
489
+ new WorkerDomainConfigError({
490
+ message: `Each hostname may play only one role in a Worker's domain config; ${[...new Set(overlap)].map((h) => `'${h}'`).join(", ")} appears in more than one of name/aliases/redirects.`,
491
+ }),
492
+ );
493
+ }
494
+ return { name, aliases, redirects };
495
+ });
496
+
497
+ const isWorkersDevHostname = (hostname: string) =>
498
+ hostname.endsWith(".workers.dev");
499
+
500
+ // Hostnames that only appear in local-dev state (the dev server's
501
+ // localhost/LAN URLs), never as attachable custom domains.
502
+ const isLocalDevHostname = (hostname: string) =>
503
+ hostname === "localhost" ||
504
+ hostname === "::1" ||
505
+ /^\d+\.\d+\.\d+\.\d+$/.test(hostname);
506
+
507
+ const urlHostname = (url: string): string => {
508
+ try {
509
+ return new URL(url).hostname;
510
+ } catch {
511
+ return url;
512
+ }
513
+ };
514
+
515
+ /** The `rules` payload shape of a zone phase-entrypoint PUT. */
516
+ type PutZoneRedirectRules = rulesets.PutPhasForZoneRequest["rules"];
517
+
518
+ /**
519
+ * The *custom domain* hostnames recorded in a Worker's persisted legacy
520
+ * `domains` state — minus the workers.dev (stable or preview) and local-dev
521
+ * entries that shared the list in older formats.
522
+ *
523
+ * @internal exported for unit testing.
524
+ */
525
+ export const stateCustomDomains = (
526
+ domains: readonly unknown[] | undefined,
527
+ ): string[] =>
528
+ normalizeStateDomains(domains).filter(
529
+ (hostname) =>
530
+ !isWorkersDevHostname(hostname) && !isLocalDevHostname(hostname),
531
+ );
532
+
533
+ /**
534
+ * The Worker's persisted domain configuration: the `domain` attribute for
535
+ * state written by the current format, else re-derived from the legacy
536
+ * `domains` list (first custom hostname = canonical name, rest = aliases;
537
+ * legacy state had no redirects).
538
+ *
539
+ * @internal exported for unit testing.
540
+ */
541
+ export const stateWorkerDomain = (
542
+ output: object | undefined,
543
+ ): ResolvedWorkerDomain | undefined => {
544
+ const state = output as
545
+ | {
546
+ domain?: {
547
+ name?: unknown;
548
+ aliases?: unknown[];
549
+ redirects?: unknown[];
550
+ } | null;
551
+ domains?: unknown[];
552
+ }
553
+ | undefined;
554
+ const domain = state?.domain;
555
+ if (domain && typeof domain.name === "string") {
556
+ return {
557
+ name: domain.name,
558
+ aliases: (domain.aliases ?? []).filter(
559
+ (h): h is string => typeof h === "string",
560
+ ),
561
+ redirects: (domain.redirects ?? []).filter(
562
+ (h): h is string => typeof h === "string",
563
+ ),
564
+ };
565
+ }
566
+ const legacy = stateCustomDomains(state?.domains);
567
+ return legacy.length > 0
568
+ ? { name: legacy[0], aliases: legacy.slice(1), redirects: [] }
569
+ : undefined;
570
+ };
571
+
128
572
  // Workers for Platforms "user workers" live inside a dispatch namespace and
129
573
  // use a parallel family of script endpoints (`/workers/dispatch/namespaces/
130
574
  // :namespace/scripts/...`). The request/response shapes are identical to the
@@ -181,7 +625,8 @@ const getScriptSettings = (
181
625
  const isBindingTargetNotFound = (
182
626
  e:
183
627
  | Effect.Error<ReturnType<typeof workers.putScript>>
184
- | Effect.Error<ReturnType<typeof wfp.putDispatchNamespaceScript>>,
628
+ | Effect.Error<ReturnType<typeof wfp.putDispatchNamespaceScript>>
629
+ | Effect.Error<ReturnType<typeof workers.createScriptVersion>>,
185
630
  ): boolean =>
186
631
  e._tag === "SecretsStoreBindingNotFound" ||
187
632
  e._tag === "KVNamespaceNotFound" ||
@@ -198,6 +643,20 @@ const isBindingTargetNotFound = (
198
643
  const bindingTargetNotFoundRetrySchedule = () =>
199
644
  Schedule.max([Schedule.fixed("2 seconds"), Schedule.recurs(10)]);
200
645
 
646
+ /**
647
+ * Script PUT is an idempotent upsert, so a pure transport failure (the
648
+ * request died before any response — e.g. Cloudflare closing a keep-alive
649
+ * socket that idled while slow upstream resources provisioned earlier in the
650
+ * deploy) is safe to replay. Errors that carry a response are real API
651
+ * verdicts and are NOT retried here.
652
+ */
653
+ const isScriptPutTransportError = (e: { _tag?: string }): boolean =>
654
+ isHttpClientError(e) && e.reason._tag === "TransportError";
655
+
656
+ const retryableScriptPut = (
657
+ e: Parameters<typeof isBindingTargetNotFound>[0],
658
+ ): boolean => isBindingTargetNotFound(e) || isScriptPutTransportError(e);
659
+
201
660
  /**
202
661
  * Upsert a Worker script, routing to the dispatch-namespace endpoint when
203
662
  * `dispatchNamespace` is set. The metadata/files contract is identical, and
@@ -226,7 +685,7 @@ const putWorkerScript = (params: {
226
685
  })
227
686
  .pipe(
228
687
  Effect.retry({
229
- while: isBindingTargetNotFound,
688
+ while: retryableScriptPut,
230
689
  schedule: bindingTargetNotFoundRetrySchedule(),
231
690
  }),
232
691
  );
@@ -240,7 +699,7 @@ const putWorkerScript = (params: {
240
699
  })
241
700
  .pipe(
242
701
  Effect.retry({
243
- while: isBindingTargetNotFound,
702
+ while: retryableScriptPut,
244
703
  schedule: bindingTargetNotFoundRetrySchedule(),
245
704
  }),
246
705
  );
@@ -270,14 +729,14 @@ const deleteWorkerScript = (
270
729
  });
271
730
 
272
731
  /**
273
- * Normalize a Worker's persisted `domains` state to `https://<hostname>`
274
- * strings. Alchemy <= beta.44 stored each custom domain as a
275
- * `{ id, hostname, zoneId }` object; beta.45+ stores `https://<hostname>`
276
- * strings and the diff path calls string methods on each entry. Older state is
277
- * coerced back to strings so `.endsWith` does not throw `u.endsWith is not a
278
- * function` (#546). Entries that are neither a string nor an object with a
279
- * string `hostname` are dropped rather than turned into a bogus `https://`
280
- * value that would skew the diff.
732
+ * Normalize a Worker's persisted *legacy* `domains` state to bare
733
+ * hostnames. Alchemy <= beta.44 stored each custom domain as a
734
+ * `{ id, hostname, zoneId }` object; beta.45+ stored `https://<hostname>`
735
+ * URL strings (with the workers.dev URL mixed in); current state stores the
736
+ * `domain` config object instead of a `domains` list. All legacy
737
+ * generations coerce to hostnames so the diff never throws on older state
738
+ * (#546). Entries that fit no generation are dropped rather than turned
739
+ * into a bogus hostname that would skew the diff.
281
740
  *
282
741
  * @internal exported for unit testing.
283
742
  */
@@ -285,11 +744,79 @@ export const normalizeStateDomains = (
285
744
  domains: readonly unknown[] | undefined,
286
745
  ): string[] =>
287
746
  (domains ?? []).flatMap((u) => {
288
- if (typeof u === "string") return [u];
747
+ if (typeof u === "string") {
748
+ if (u.includes("://")) {
749
+ try {
750
+ return [new URL(u).hostname];
751
+ } catch {
752
+ return [];
753
+ }
754
+ }
755
+ return u.length > 0 ? [u] : [];
756
+ }
289
757
  const hostname = (u as { hostname?: unknown } | null)?.hostname;
290
- return typeof hostname === "string" ? [`https://${hostname}`] : [];
758
+ return typeof hostname === "string" ? [hostname] : [];
291
759
  });
292
760
 
761
+ /**
762
+ * Custom domains Alchemy is responsible for on this Worker — either declared
763
+ * on props (`domain`) or already persisted as non-`workers.dev` URLs in state.
764
+ * Used by `read` to skip `listDomains` when the surface is unmanaged (#926).
765
+ *
766
+ * @internal exported for unit testing.
767
+ */
768
+ export const shouldObserveWorkerDomains = (
769
+ olds: Pick<WorkerProps, "domain"> | undefined,
770
+ output: object | undefined,
771
+ ): boolean =>
772
+ olds?.domain !== undefined || stateWorkerDomain(output) !== undefined;
773
+
774
+ /**
775
+ * Zone routes Alchemy is responsible for on this Worker. Used by `read` to
776
+ * skip account-wide zone/route fan-out when the surface is unmanaged (#926).
777
+ *
778
+ * @internal exported for unit testing.
779
+ */
780
+ export const shouldObserveWorkerRoutes = (
781
+ olds: Pick<WorkerProps, "routes"> | undefined,
782
+ output: Pick<Worker["Attributes"], "routes"> | undefined,
783
+ ): boolean => olds?.routes !== undefined || (output?.routes?.length ?? 0) > 0;
784
+
785
+ /**
786
+ * Cron triggers Alchemy is responsible for on this Worker. Used by `read` to
787
+ * skip `getScriptSchedule` when the surface is unmanaged (#926). Effect-native
788
+ * `cron()` bindings persist into `output.crons` after the first reconcile, so
789
+ * subsequent reads still observe them.
790
+ *
791
+ * @internal exported for unit testing.
792
+ */
793
+ export const shouldObserveWorkerCrons = (
794
+ olds: Pick<WorkerProps, "crons"> | undefined,
795
+ output: Pick<Worker["Attributes"], "crons"> | undefined,
796
+ ): boolean => olds?.crons !== undefined || (output?.crons?.length ?? 0) > 0;
797
+
798
+ /**
799
+ * Optional override for the account's stable `workers.dev` subdomain
800
+ * (`<subdomain>` in `https://<script>.<subdomain>.workers.dev`). When set,
801
+ * Worker URL construction skips `GET /accounts/{id}/workers/subdomain`.
802
+ */
803
+ const CLOUDFLARE_WORKERS_SUBDOMAIN = Config.string(
804
+ "CLOUDFLARE_WORKERS_SUBDOMAIN",
805
+ ).pipe(
806
+ Config.map((value) => value.trim()),
807
+ Config.option,
808
+ );
809
+
810
+ const WORKERS_SUBDOMAIN_PATTERN = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
811
+
812
+ /**
813
+ * Max concurrent `GET /zones/{id}/workers/routes` calls when observing the
814
+ * routes attached to a Worker. Route listing is per-zone, so this fans out
815
+ * with the number of zones the Worker has routes in; unbounded fan-out here
816
+ * is what trips Cloudflare account-level 429 / code 971 throttling (#926).
817
+ */
818
+ const WORKER_ROUTE_LIST_CONCURRENCY = 8;
819
+
293
820
  type MetadataHashValue =
294
821
  | string
295
822
  | number
@@ -379,6 +906,13 @@ interface WorkerMetadataHashInput {
379
906
  readonly bindings: readonly ResourceBinding<Worker["Binding"]>[];
380
907
  readonly accountId: string;
381
908
  readonly stack: { readonly name: string; readonly stage: string };
909
+ /**
910
+ * The resolved URL a `Worker.URL` (`self_url`) binding lowers into.
911
+ * Included in the hash so external URL changes (an account-subdomain
912
+ * rename, a custom-domain reorder) redeploy the Worker with the fresh
913
+ * value even though props and binding data are unchanged.
914
+ */
915
+ readonly selfUrl?: string;
382
916
  }
383
917
 
384
918
  // The asset router config the resource declares (htmlHandling,
@@ -409,9 +943,11 @@ const resolveWorkerMetadataHash = ({
409
943
  bindings,
410
944
  accountId,
411
945
  stack,
946
+ selfUrl,
412
947
  }: WorkerMetadataHashInput): Effect.Effect<string> =>
413
948
  resolveMetadataHashValue({
414
949
  accountId,
950
+ selfUrl,
415
951
  stack: { name: stack.name, stage: stack.stage },
416
952
  compatibility: getCompatibility(props),
417
953
  env: props.env,
@@ -425,15 +961,33 @@ const resolveWorkerMetadataHash = ({
425
961
  logpush: props.logpush,
426
962
  observability: props.observability,
427
963
  placement: props.placement,
428
- subdomain: props.subdomain,
429
964
  tags: props.tags,
430
- url: props.url,
965
+ workersDev: resolveWorkersDev(props.workersDev),
966
+ // Reduce `version.parent` to the parent's script name: the resolved
967
+ // parent is a full attributes object whose *other* fields (hash, url,
968
+ // ...) change on every parent deploy, which would spuriously re-version
969
+ // this Worker even though nothing about the version itself changed.
970
+ version: props.version
971
+ ? {
972
+ parent: resolveVersionParentName(props.version),
973
+ traffic: props.version.traffic,
974
+ alias: props.version.alias,
975
+ affinity: props.version.affinity,
976
+ message: props.version.message,
977
+ tag: props.version.tag,
978
+ }
979
+ : undefined,
431
980
  }).pipe(Effect.flatMap((metadata) => sha256Object({ metadata })));
432
981
 
433
982
  export const WorkerProvider = () =>
434
- ProviderLayer.select({
983
+ ProviderLayer.dual(Worker, {
435
984
  live: () => LiveWorkerProvider(),
436
- local: () => LocalWorkerProvider(),
985
+ // The local runtime deps (workerd, WorkerProxy, LocalRuntimeState)
986
+ // compose INTO the local variant so a live deploy only constructs them
987
+ // if the local provider is actually demanded (e.g. deleting a local
988
+ // dev worker's state row). See ProviderLayer.dual.
989
+ local: () =>
990
+ LocalWorkerProvider().pipe(Layer.provide(localRuntimeServices())),
437
991
  });
438
992
 
439
993
  export const LiveWorkerProvider = () =>
@@ -459,57 +1013,79 @@ export const LiveWorkerProvider = () =>
459
1013
  // const listZones = yield* zones.listZones;
460
1014
  const telemetry = yield* CloudflareLogs;
461
1015
 
462
- const getAccountSubdomain = (accountId: string) =>
463
- workers
464
- .getSubdomain({
465
- accountId,
466
- })
467
- .pipe(Effect.map((result) => result.subdomain));
468
-
469
- // Toggle the workers.dev subdomain via `POST /subdomain` with
470
- // `enabled: true | false`. Mirrors the upstream Alchemy
471
- // implementation in `.vendor/alchemy/.../worker-subdomain.ts`.
472
- // When enabling we also set `previewsEnabled: true` so the
473
- // script is reachable both at its stable workers.dev URL and at
474
- // version-preview URLs; on disable we send just `enabled: false`.
1016
+ // Account subdomain is invariant for the life of a provider layer —
1017
+ // memoize so N Workers (or repeated plan/deploy reads) don't each hit
1018
+ // GET /accounts/{id}/workers/subdomain and trip Cloudflare's 429/971
1019
+ // throttle (#926). `CLOUDFLARE_WORKERS_SUBDOMAIN` bypasses the lookup
1020
+ // entirely when the operator already knows the value.
1021
+ const getAccountSubdomain = yield* cachedFunction((accountId: string) =>
1022
+ Effect.gen(function* () {
1023
+ const configured = yield* CLOUDFLARE_WORKERS_SUBDOMAIN;
1024
+ if (Option.isSome(configured) && configured.value !== "") {
1025
+ if (!WORKERS_SUBDOMAIN_PATTERN.test(configured.value)) {
1026
+ return yield* Effect.die(
1027
+ new Error(
1028
+ `Invalid CLOUDFLARE_WORKERS_SUBDOMAIN "${configured.value}": ` +
1029
+ "expected a workers.dev subdomain label (lowercase letters, digits, hyphens)",
1030
+ ),
1031
+ );
1032
+ }
1033
+ return configured.value;
1034
+ }
1035
+ const result = yield* workers.getSubdomain({ accountId });
1036
+ return result.subdomain;
1037
+ }),
1038
+ );
1039
+
1040
+ // Converge the script's workers.dev settings via `POST /subdomain`.
1041
+ // The two toggles are independent on the Cloudflare API: `enabled`
1042
+ // drives the stable `<name>.<account>.workers.dev` URL and
1043
+ // `previews_enabled` the per-version preview URLs.
475
1044
  const setWorkerSubdomain = Effect.fn(function* (
476
1045
  name: string,
477
- enabled: boolean,
1046
+ desired: ResolvedWorkersDev,
478
1047
  ) {
479
1048
  const { accountId } = yield* yield* CloudflareEnvironment;
480
1049
  return yield* workers.createScriptSubdomain({
481
1050
  accountId,
482
1051
  scriptName: name,
483
- enabled,
484
- previewsEnabled: enabled ? true : undefined,
1052
+ enabled: desired.enabled,
1053
+ previewsEnabled: desired.previewsEnabled,
485
1054
  });
486
1055
  });
487
1056
 
488
- // Convert non-ASCII hostnames (emoji, IDN, etc.) to punycode so the
489
- // Cloudflare API receives the form it stores domains in. `new URL(...)`
490
- // does IDNA via WHATWG URL parsing — `📦.alchemy.run` → `xn--5z8h.alchemy.run`.
491
- const toPunycode = (hostname: string): string => {
492
- try {
493
- return new URL(`https://${hostname}`).hostname;
494
- } catch {
495
- return hostname;
496
- }
497
- };
498
-
499
- const normalizeDomains = (
500
- domain: string | string[] | undefined,
501
- ): string[] =>
502
- domain === undefined
503
- ? []
504
- : Array.from(
505
- new Set(
506
- (Array.isArray(domain) ? domain : [domain]).map(toPunycode),
507
- ),
508
- );
509
-
510
1057
  const normalizeCrons = (crons: string[] | undefined): string[] =>
511
1058
  Array.from(new Set(crons ?? []));
512
1059
 
1060
+ const hasSelfUrlBinding = (
1061
+ bindings: readonly ResourceBinding<Worker["Binding"]>[],
1062
+ ) =>
1063
+ bindings.some((b) =>
1064
+ (b.data.bindings ?? []).some((item) => item.type === "self_url"),
1065
+ );
1066
+
1067
+ // Resolve the URL this Worker will be served at — the same formula that
1068
+ // produces the `url` attribute (first custom domain in user order, else
1069
+ // the workers.dev URL) — usable BEFORE the script upload, so `Worker.URL`
1070
+ // bindings can be lowered into plain_text bindings and VITE_*-prefixed
1071
+ // env entries can be inlined into the client bundle at build time.
1072
+ const resolveSelfUrl = Effect.fn(function* (
1073
+ name: string,
1074
+ props: WorkerProps,
1075
+ accountId: string,
1076
+ ) {
1077
+ const domain = yield* resolveWorkerDomain(props.domain);
1078
+ if (domain) {
1079
+ return `https://${domain.name}`;
1080
+ }
1081
+ if (!resolveWorkersDev(props.workersDev).enabled) {
1082
+ return yield* Effect.die(
1083
+ `Worker "${name}" binds its own URL (Worker.URL) but has none: the stable workers.dev URL is disabled (workersDev) and no custom domain is configured.`,
1084
+ );
1085
+ }
1086
+ return `https://${name}.${yield* getAccountSubdomain(accountId)}.workers.dev`;
1087
+ });
1088
+
513
1089
  const getWorkerCrons = Effect.fn(function* (scriptName: string) {
514
1090
  const { accountId } = yield* yield* CloudflareEnvironment;
515
1091
  return yield* workers
@@ -731,6 +1307,315 @@ export const LiveWorkerProvider = () =>
731
1307
  return applied;
732
1308
  });
733
1309
 
1310
+ // The preview URL of the Worker's *current* version:
1311
+ // `https://<version-prefix>-<name>.<subdomain>.workers.dev`, where the
1312
+ // prefix is the first 8 characters of the version id (read from the
1313
+ // latest deployment's majority version). Resolves to `undefined` when
1314
+ // no deployment is visible yet — callers treat the preview URL as
1315
+ // best-effort. Only consulted in previews-only mode
1316
+ // (`workersDev: { enabled: false, previewsEnabled: true }`), where it
1317
+ // is the Worker's only workers.dev surface.
1318
+ const getPreviewUrl = Effect.fn(function* (
1319
+ scriptName: string,
1320
+ accountSubdomain: string,
1321
+ ) {
1322
+ const { accountId } = yield* yield* CloudflareEnvironment;
1323
+ const deployments = yield* workers
1324
+ .listScriptDeployments({ accountId, scriptName })
1325
+ .pipe(
1326
+ Effect.map((response) => response.deployments ?? []),
1327
+ Effect.catch(() => Effect.succeed([])),
1328
+ );
1329
+ const latest = [...deployments].sort((a, b) =>
1330
+ b.createdOn.localeCompare(a.createdOn),
1331
+ )[0];
1332
+ const version = latest?.versions?.reduce(
1333
+ (max, candidate) =>
1334
+ max === undefined || candidate.percentage > max.percentage
1335
+ ? candidate
1336
+ : max,
1337
+ undefined as { percentage: number; versionId: string } | undefined,
1338
+ );
1339
+ if (!version?.versionId) return undefined;
1340
+ return `https://${version.versionId.slice(0, 8)}-${scriptName}.${accountSubdomain}.workers.dev`;
1341
+ });
1342
+
1343
+ /**
1344
+ * Assemble every URL a deployed, script-owning Worker serves at, most
1345
+ * significant first — the first entry becomes the `url` attribute:
1346
+ *
1347
+ * 1. `https://<domain.name>` — the canonical custom domain
1348
+ * 2. aliases, in declared order
1349
+ * 3. the stable workers.dev URL (`workersDev.enabled`)
1350
+ * 4. preview URLs of the version this deploy uploaded (gradual
1351
+ * rollouts), or — in previews-only mode — the current version's
1352
+ * preview URL, which is then the only workers.dev surface
1353
+ *
1354
+ * Redirect hostnames never appear: they don't serve the Worker.
1355
+ * (Version workers don't use this — their `urls` are their preview
1356
+ * URLs, assembled in `putWorkerVersion`.)
1357
+ */
1358
+ const computeWorkerUrls = Effect.fn(function* (params: {
1359
+ scriptName: string;
1360
+ workersDev: ResolvedWorkersDev;
1361
+ domain: ResolvedWorkerDomain | undefined;
1362
+ /** the version uploaded by this deploy (self-rollout), if any */
1363
+ uploadedVersionId?: string;
1364
+ /** user-provided `version.alias` attached to the uploaded version */
1365
+ uploadedVersionAlias?: string;
1366
+ }) {
1367
+ const { workersDev, domain, scriptName } = params;
1368
+ const urls: string[] = [];
1369
+ if (domain) {
1370
+ urls.push(
1371
+ `https://${domain.name}`,
1372
+ ...domain.aliases.map((hostname) => `https://${hostname}`),
1373
+ );
1374
+ }
1375
+ if (workersDev.enabled || workersDev.previewsEnabled) {
1376
+ const { accountId } = yield* yield* CloudflareEnvironment;
1377
+ const accountSubdomain = yield* getAccountSubdomain(accountId);
1378
+ if (workersDev.enabled) {
1379
+ urls.push(`https://${scriptName}.${accountSubdomain}.workers.dev`);
1380
+ }
1381
+ if (workersDev.previewsEnabled) {
1382
+ if (params.uploadedVersionId !== undefined) {
1383
+ if (params.uploadedVersionAlias !== undefined) {
1384
+ urls.push(
1385
+ `https://${params.uploadedVersionAlias}-${scriptName}.${accountSubdomain}.workers.dev`,
1386
+ );
1387
+ }
1388
+ urls.push(
1389
+ `https://${params.uploadedVersionId.split("-")[0]}-${scriptName}.${accountSubdomain}.workers.dev`,
1390
+ );
1391
+ } else if (!workersDev.enabled) {
1392
+ const previewUrl = yield* getPreviewUrl(
1393
+ scriptName,
1394
+ accountSubdomain,
1395
+ );
1396
+ if (previewUrl) urls.push(previewUrl);
1397
+ }
1398
+ }
1399
+ }
1400
+ return urls;
1401
+ });
1402
+
1403
+ /**
1404
+ * Converge the redirect rules for a Worker's `domain.redirects` in
1405
+ * each affected zone's `http_request_dynamic_redirect` phase
1406
+ * entrypoint. Rules are identified by a
1407
+ * `alchemy:worker:<script>:redirect:<host>` description; only our own
1408
+ * rules are added/removed — every other rule in the shared entrypoint
1409
+ * ruleset passes through untouched. Zones that previously held our
1410
+ * rules but no longer should (removed redirects, removed domain) are
1411
+ * cleaned the same way.
1412
+ */
1413
+ const reconcileRedirectRules = Effect.fn(function* (params: {
1414
+ scriptName: string;
1415
+ domain: ResolvedWorkerDomain | undefined;
1416
+ /** hostname → zoneId for every currently-attached custom domain */
1417
+ zoneIdByHostname: ReadonlyMap<string, string>;
1418
+ /** redirect hostnames from previous state (for zone cleanup) */
1419
+ previousRedirects: readonly string[];
1420
+ }) {
1421
+ const prefix = `alchemy:worker:${params.scriptName}:redirect:`;
1422
+ const desired = params.domain?.redirects ?? [];
1423
+ const targetName = params.domain?.name;
1424
+ // Zones to touch: every zone hosting a desired redirect, plus every
1425
+ // zone that hosted one before (so removals converge). Hostnames
1426
+ // whose zone we can't resolve (e.g. the domain was already
1427
+ // detached) are skipped — their rules become unreachable dead
1428
+ // config at worst, never a failed destroy.
1429
+ const zoneIds = new Set<string>();
1430
+ for (const hostname of [...desired, ...params.previousRedirects]) {
1431
+ const zoneId = params.zoneIdByHostname.get(hostname);
1432
+ if (zoneId) zoneIds.add(zoneId);
1433
+ }
1434
+ if (zoneIds.size === 0) return;
1435
+ for (const zoneId of zoneIds) {
1436
+ const entrypoint = yield* rulesets
1437
+ .getPhasForZone({
1438
+ zoneId,
1439
+ rulesetPhase: "http_request_dynamic_redirect",
1440
+ })
1441
+ .pipe(Effect.catch(() => Effect.succeed(undefined)));
1442
+ const existingRules = entrypoint?.rules ?? [];
1443
+ const ourDesired = desired
1444
+ .filter(
1445
+ (hostname) => params.zoneIdByHostname.get(hostname) === zoneId,
1446
+ )
1447
+ .map((hostname) => ({
1448
+ action: "redirect" as const,
1449
+ expression: `http.host eq "${hostname}"`,
1450
+ description: `${prefix}${hostname}`,
1451
+ enabled: true,
1452
+ actionParameters: {
1453
+ fromValue: {
1454
+ statusCode: 301,
1455
+ preserveQueryString: true,
1456
+ targetUrl: {
1457
+ expression: `concat("https://${targetName}", http.request.uri.path)`,
1458
+ },
1459
+ },
1460
+ },
1461
+ }));
1462
+ const isOurs = (rule: { description?: string | null }) =>
1463
+ (rule.description ?? "").startsWith(prefix);
1464
+ const ourExisting = existingRules.filter(isOurs);
1465
+ const converged =
1466
+ ourExisting.length === ourDesired.length &&
1467
+ ourDesired.every((rule) =>
1468
+ ourExisting.some(
1469
+ (existing) =>
1470
+ existing.description === rule.description &&
1471
+ existing.expression === rule.expression,
1472
+ ),
1473
+ );
1474
+ if (converged) continue;
1475
+ // Pass foreign rules through untouched (minus the read-only
1476
+ // fields the PUT schema doesn't accept) and replace our own set.
1477
+ const foreign = existingRules
1478
+ .filter((rule) => !isOurs(rule))
1479
+ .map((rule) => {
1480
+ const {
1481
+ lastUpdated: _lastUpdated,
1482
+ version: _version,
1483
+ ...rest
1484
+ } = rule as Record<string, unknown> & {
1485
+ lastUpdated?: string;
1486
+ version?: string;
1487
+ };
1488
+ return rest;
1489
+ });
1490
+ yield* rulesets.putPhasForZone({
1491
+ zoneId,
1492
+ rulesetPhase: "http_request_dynamic_redirect",
1493
+ rules: [...foreign, ...ourDesired] as PutZoneRedirectRules,
1494
+ });
1495
+ }
1496
+ });
1497
+
1498
+ /**
1499
+ * Converge the version-affinity transform rules (`version.affinity`)
1500
+ * in each serving zone's `http_request_late_transform` phase
1501
+ * entrypoint: `rewrite` rules that fill the
1502
+ * `Cloudflare-Workers-Version-Key` header from the configured source,
1503
+ * scoped to the Worker's hostnames in that zone. Rules are identified
1504
+ * by an `alchemy:worker:<script>:affinity` description prefix; only
1505
+ * our own rules are added/updated/removed — every other rule in the
1506
+ * shared entrypoint passes through untouched. Zones that previously
1507
+ * held our rules but no longer should (affinity removed, zone no
1508
+ * longer served) are cleaned the same way. Returns the zone ids now
1509
+ * holding rules, for persistence in `affinityZoneIds`.
1510
+ */
1511
+ const reconcileAffinityRules = Effect.fn(function* (params: {
1512
+ scriptName: string;
1513
+ /** `undefined` removes every rule (affinity removed / delete). */
1514
+ desired:
1515
+ | {
1516
+ affinity: ResolvedVersionAffinity;
1517
+ hostsByZone: ReadonlyMap<string, AffinityZoneHost[]>;
1518
+ }
1519
+ | undefined;
1520
+ /** zone ids holding rules per previous state (for cleanup). */
1521
+ previousZoneIds: readonly string[];
1522
+ }) {
1523
+ const prefix = affinityRulePrefix(params.scriptName);
1524
+ const zoneIds = new Set([
1525
+ ...(params.desired?.hostsByZone.keys() ?? []),
1526
+ ...params.previousZoneIds,
1527
+ ]);
1528
+ for (const zoneId of zoneIds) {
1529
+ const hosts = params.desired?.hostsByZone.get(zoneId);
1530
+ const ourDesired =
1531
+ params.desired !== undefined && hosts !== undefined
1532
+ ? buildAffinityZoneRules(
1533
+ params.scriptName,
1534
+ params.desired.affinity,
1535
+ hosts,
1536
+ )
1537
+ : [];
1538
+ const entrypoint = yield* rulesets
1539
+ .getPhasForZone({
1540
+ zoneId,
1541
+ rulesetPhase: "http_request_late_transform",
1542
+ })
1543
+ .pipe(Effect.catch(() => Effect.succeed(undefined)));
1544
+ const existingRules = entrypoint?.rules ?? [];
1545
+ const isOurs = (rule: { description?: string | null }) =>
1546
+ (rule.description ?? "").startsWith(prefix);
1547
+ // The header-value expression of an existing rewrite rule, for
1548
+ // convergence comparison. `headers` is an untyped wire record:
1549
+ // `{ "<Header-Name>": { operation, expression | value } }`.
1550
+ const existingValue = (rule: (typeof existingRules)[number]) => {
1551
+ const headers =
1552
+ "actionParameters" in rule
1553
+ ? (
1554
+ rule.actionParameters as
1555
+ | { headers?: Record<string, unknown> | null }
1556
+ | null
1557
+ | undefined
1558
+ )?.headers
1559
+ : undefined;
1560
+ const header = headers?.[AFFINITY_HEADER] as
1561
+ | { expression?: unknown }
1562
+ | undefined;
1563
+ return typeof header?.expression === "string"
1564
+ ? header.expression
1565
+ : undefined;
1566
+ };
1567
+ const ourExisting = existingRules.filter(isOurs);
1568
+ const converged =
1569
+ ourExisting.length === ourDesired.length &&
1570
+ ourDesired.every((rule) =>
1571
+ ourExisting.some(
1572
+ (existing) =>
1573
+ existing.description === rule.description &&
1574
+ existing.expression === rule.expression &&
1575
+ existingValue(existing) === rule.value,
1576
+ ),
1577
+ );
1578
+ if (converged) continue;
1579
+ // Pass foreign rules through untouched (minus the read-only
1580
+ // fields the PUT schema doesn't accept) and replace our own set.
1581
+ const foreign = existingRules
1582
+ .filter((rule) => !isOurs(rule))
1583
+ .map((rule) => {
1584
+ const {
1585
+ lastUpdated: _lastUpdated,
1586
+ version: _version,
1587
+ ...rest
1588
+ } = rule as Record<string, unknown> & {
1589
+ lastUpdated?: string;
1590
+ version?: string;
1591
+ };
1592
+ return rest;
1593
+ });
1594
+ yield* rulesets.putPhasForZone({
1595
+ zoneId,
1596
+ rulesetPhase: "http_request_late_transform",
1597
+ rules: [
1598
+ ...foreign,
1599
+ ...ourDesired.map((rule) => ({
1600
+ action: "rewrite" as const,
1601
+ description: rule.description,
1602
+ enabled: true,
1603
+ expression: rule.expression,
1604
+ actionParameters: {
1605
+ headers: {
1606
+ [AFFINITY_HEADER]: {
1607
+ operation: "set",
1608
+ expression: rule.value,
1609
+ },
1610
+ },
1611
+ },
1612
+ })),
1613
+ ] as PutZoneRedirectRules,
1614
+ });
1615
+ }
1616
+ return [...(params.desired?.hostsByZone.keys() ?? [])].sort();
1617
+ });
1618
+
734
1619
  type NormalizedWorkerRoute = {
735
1620
  pattern: string;
736
1621
  zoneId: string;
@@ -810,28 +1695,39 @@ export const LiveWorkerProvider = () =>
810
1695
  Effect.catch(() => Effect.succeed([])),
811
1696
  ),
812
1697
  ),
813
- { concurrency: "unbounded" },
1698
+ // Bounded: this issues one request per zone, so a Worker with routes
1699
+ // spread across many zones would otherwise burst the account's whole
1700
+ // API budget in a single tick (#926).
1701
+ { concurrency: WORKER_ROUTE_LIST_CONCURRENCY },
814
1702
  );
815
1703
 
816
1704
  return Effect.map(routesByZone, (routes) => routes.flat());
817
1705
  };
818
1706
 
819
- // Observe every route attached to `scriptName` account-wide. Routes
820
- // are zone-scoped with no account-level enumeration API, so fan out
821
- // over all of the account's zones. Any failure to enumerate zones
822
- // (e.g. a token without zone read scope) degrades to "no routes"
823
- // rather than failing the read.
824
- const readWorkerRoutes = (scriptName: string) =>
825
- Effect.gen(function* () {
826
- const { accountId } = yield* yield* CloudflareEnvironment;
827
- const accountZones = yield* listAllZones(accountId).pipe(
828
- Effect.catch(() => Effect.succeed([])),
829
- );
830
- return yield* listWorkerRoutesInZones(
831
- scriptName,
832
- accountZones.map((zone) => zone.id),
833
- );
834
- });
1707
+ // Observe the routes attached to `scriptName` in the zones Alchemy
1708
+ // already associates with this Worker.
1709
+ //
1710
+ // This used to enumerate every zone on the account and then list routes
1711
+ // in each one. Routes are zone-scoped with no account-level enumeration
1712
+ // API, so that costs O(zones) requests on *every* Worker read — on an
1713
+ // account with a few hundred zones a couple of plan/deploy cycles
1714
+ // exhausts Cloudflare's per-user API budget and unrelated calls start
1715
+ // coming back 429 / code 971 (#926).
1716
+ //
1717
+ // `reconcileRoutes` already scopes itself to the zones implied by
1718
+ // `desired ∪ previous` rather than sweeping the account, so `read` now
1719
+ // uses the same bounded zone set: the zones of the routes already
1720
+ // recorded in state. The blind spot this introduces — a route added out
1721
+ // of band in a zone this Worker has never had a route in — is the one
1722
+ // `reconcileRoutes` already has.
1723
+ const readWorkerRoutes = (
1724
+ scriptName: string,
1725
+ knownRoutes: Worker["Attributes"]["routes"] | undefined,
1726
+ ) =>
1727
+ listWorkerRoutesInZones(
1728
+ scriptName,
1729
+ (knownRoutes ?? []).map((route) => route.zoneId),
1730
+ );
835
1731
 
836
1732
  // Converge the zone routes attached to `scriptName` to `desired`.
837
1733
  // Observed cloud state (not `previous`) is the diff baseline —
@@ -1175,7 +2071,10 @@ export const LiveWorkerProvider = () =>
1175
2071
  crypto.createHash("sha256").update(script).digest("hex"),
1176
2072
  );
1177
2073
 
1178
- const viteBuild = Effect.fn(function* (props: WorkerProps) {
2074
+ const viteBuild = Effect.fn(function* (
2075
+ props: WorkerProps,
2076
+ selfUrl?: string,
2077
+ ) {
1179
2078
  const compatibility = getCompatibility(props);
1180
2079
  // Loaded lazily: `./Vite.ts` pulls in `@distilled.cloud/cloudflare-vite-plugin`
1181
2080
  // (~0.5s), which is only needed for vite-based workers at build time —
@@ -1195,22 +2094,44 @@ export const LiveWorkerProvider = () =>
1195
2094
  : Redacted.isRedacted(value) &&
1196
2095
  typeof Redacted.value(value) === "string"
1197
2096
  ? Redacted.value(value)
1198
- : // A `WorkerLoader` is a real Effect that also carries
1199
- // the `~alchemy/Kind` marker — it is a binding, not a
1200
- // runnable env value. Check it before `Effect.isEffect`
1201
- // so we don't execute it as an inlined env entry.
1202
- isWorkerLoader(value)
1203
- ? undefined
1204
- : Effect.isEffect(value)
1205
- ? yield* value as any as Effect.Effect<any>
1206
- : undefined,
2097
+ : // `Worker.URL` (bare tag or called) — resolved to
2098
+ // this Worker's own URL. The bare tag is
2099
+ // Effect-shaped, so check before `Effect.isEffect`.
2100
+ isSelfUrl(value)
2101
+ ? selfUrl
2102
+ : // A `WorkerLoader` is a real Effect that also carries
2103
+ // the `~alchemy/Kind` marker — it is a binding, not a
2104
+ // runnable env value. Check it before `Effect.isEffect`
2105
+ // so we don't execute it as an inlined env entry.
2106
+ isWorkerLoader(value)
2107
+ ? undefined
2108
+ : // A `Cloudflare.Container` declaration is likewise
2109
+ // Effect-shaped but is a binding (DO namespace +
2110
+ // ContainerApplication) — yielding it would resolve
2111
+ // the started-instance tag, which only exists inside
2112
+ // a Durable Object (#997).
2113
+ isContainerDecl(value)
2114
+ ? undefined
2115
+ : Effect.isEffect(value)
2116
+ ? yield* value as any as Effect.Effect<any>
2117
+ : undefined,
1207
2118
  ];
1208
2119
  }),
1209
2120
  ),
1210
2121
  )).filter(([_, value]) => value !== undefined),
1211
2122
  ),
1212
2123
  {
1213
- main: props.vite?.main,
2124
+ // A relative `vite.main` is documented to resolve from the Vite
2125
+ // root. The rolldown plugin resolves the worker entry with no
2126
+ // importer (i.e. against `process.cwd()`), which breaks when the
2127
+ // deploy runs from a different directory (e.g. a monorepo infra
2128
+ // package) — absolutize before handing it over (#796).
2129
+ main: props.vite?.main
2130
+ ? path.resolve(
2131
+ props.vite.rootDir ?? process.cwd(),
2132
+ props.vite.main,
2133
+ )
2134
+ : undefined,
1214
2135
  compatibilityDate: compatibility.date,
1215
2136
  compatibilityFlags: compatibility.flags,
1216
2137
  viteEnvironments: props.vite?.viteEnvironments,
@@ -1298,7 +2219,7 @@ export const LiveWorkerProvider = () =>
1298
2219
  const prepareAssetsAndBundle = (
1299
2220
  id: string,
1300
2221
  props: WorkerProps,
1301
- opts: { skipAssetsRead?: boolean } = {},
2222
+ opts: { skipAssetsRead?: boolean; selfUrl?: string } = {},
1302
2223
  ) =>
1303
2224
  Effect.gen(function* () {
1304
2225
  if (props.script !== undefined) {
@@ -1322,7 +2243,29 @@ export const LiveWorkerProvider = () =>
1322
2243
  };
1323
2244
  }
1324
2245
  if (props.vite) {
1325
- return yield* viteBuild(props);
2246
+ return yield* viteBuild(props, opts.selfUrl);
2247
+ }
2248
+ // Assets-only Worker: no entry module at all. The script PUT goes
2249
+ // out with no modules and no main_module — Cloudflare's asset
2250
+ // layer serves every request and applies `notFoundHandling`
2251
+ // (including SPA fallback) itself, exactly like Wrangler's
2252
+ // assets-only deploys.
2253
+ if (props.main === undefined) {
2254
+ if (!props.assets) {
2255
+ return yield* Effect.die(
2256
+ new Error(
2257
+ `Worker "${id}" has no main, script, or assets. Provide an entry module (main / script) or an assets directory to deploy an assets-only Worker.`,
2258
+ ),
2259
+ );
2260
+ }
2261
+ return {
2262
+ assets: opts.skipAssetsRead
2263
+ ? undefined
2264
+ : yield* prepareAssets(props.assets),
2265
+ bundle: undefined,
2266
+ input: undefined,
2267
+ additionalWorkspaces: undefined,
2268
+ };
1326
2269
  }
1327
2270
  const [assets, bundle] = yield* Effect.all(
1328
2271
  [
@@ -1369,6 +2312,523 @@ export const LiveWorkerProvider = () =>
1369
2312
  return { directory, config, hash, skip: hash === output?.hash?.assets };
1370
2313
  };
1371
2314
 
2315
+ /**
2316
+ * Append the standard Alchemy runtime bindings plus the user's `env`
2317
+ * entries (routed by shape: `Redacted` → secret_text, string →
2318
+ * plain_text, everything else → json) to a metadata binding list.
2319
+ * Shared between the full script upload and the version upload.
2320
+ */
2321
+ const appendAlchemyAndEnvBindings = (
2322
+ metadataBindings: WorkerBinding[],
2323
+ news: WorkerProps,
2324
+ accountId: string,
2325
+ workerName: string,
2326
+ ) => {
2327
+ metadataBindings.push(
2328
+ {
2329
+ type: "plain_text",
2330
+ name: "ALCHEMY_PHASE",
2331
+ text: "runtime",
2332
+ },
2333
+ {
2334
+ type: "plain_text",
2335
+ name: "ALCHEMY_WORKER_NAME",
2336
+ text: workerName,
2337
+ },
2338
+ {
2339
+ type: "plain_text",
2340
+ name: "ALCHEMY_STACK_NAME",
2341
+ text: stack.name,
2342
+ },
2343
+ {
2344
+ type: "plain_text",
2345
+ name: "ALCHEMY_STAGE",
2346
+ text: stack.stage,
2347
+ },
2348
+ {
2349
+ type: "plain_text",
2350
+ name: "ALCHEMY_CLOUDFLARE_ACCOUNT_ID",
2351
+ text: accountId,
2352
+ },
2353
+ );
2354
+ // Add environment variables as metadata bindings
2355
+ if (news.env) {
2356
+ for (const [key, value] of Object.entries(news.env)) {
2357
+ if (value === undefined) continue;
2358
+ if (metadataBindings.some((b) => b.name === key)) continue;
2359
+ if (Redacted.isRedacted(value)) {
2360
+ const unredacted = Redacted.value(value);
2361
+ metadataBindings.push({
2362
+ type: "secret_text",
2363
+ name: key,
2364
+ text:
2365
+ typeof unredacted === "string"
2366
+ ? unredacted
2367
+ : JSON.stringify(unredacted),
2368
+ });
2369
+ } else if (typeof value === "string") {
2370
+ metadataBindings.push({
2371
+ type: "plain_text",
2372
+ name: key,
2373
+ text: value,
2374
+ });
2375
+ } else {
2376
+ metadataBindings.push({
2377
+ type: "json",
2378
+ name: key,
2379
+ json: value,
2380
+ });
2381
+ }
2382
+ }
2383
+ }
2384
+ };
2385
+
2386
+ /**
2387
+ * Create a deployment routing `traffic`% to `versionId`, with the
2388
+ * remainder staying on the currently-live version (the
2389
+ * highest-percentage version of the script's latest deployment).
2390
+ * `traffic >= 100` — or a script with no other live version to split
2391
+ * against — deploys the new version at 100%.
2392
+ */
2393
+ const deployVersionTraffic = Effect.fn(function* (params: {
2394
+ accountId: string;
2395
+ scriptName: string;
2396
+ versionId: string;
2397
+ traffic: number;
2398
+ message: string | undefined;
2399
+ }) {
2400
+ const { accountId, scriptName, versionId, traffic } = params;
2401
+ const split = yield* Effect.gen(function* () {
2402
+ if (traffic >= 100) {
2403
+ return [{ versionId, percentage: 100 }];
2404
+ }
2405
+ const { deployments } = yield* workers.listScriptDeployments({
2406
+ accountId,
2407
+ scriptName,
2408
+ });
2409
+ // Deployments are returned newest-first; the live version is the
2410
+ // highest-percentage version of the most recent deployment.
2411
+ const stable = deployments[0]?.versions
2412
+ .filter((v) => v.versionId !== versionId)
2413
+ .sort((a, b) => b.percentage - a.percentage)[0];
2414
+ if (!stable) {
2415
+ // Nothing to split against (first deployment of this script).
2416
+ return [{ versionId, percentage: 100 }];
2417
+ }
2418
+ return [
2419
+ { versionId, percentage: traffic },
2420
+ { versionId: stable.versionId, percentage: 100 - traffic },
2421
+ ];
2422
+ });
2423
+ const deployment = yield* workers.createScriptDeployment({
2424
+ accountId,
2425
+ scriptName,
2426
+ strategy: "percentage",
2427
+ versions: split,
2428
+ annotations: params.message
2429
+ ? { workersMessage: params.message }
2430
+ : undefined,
2431
+ });
2432
+ return deployment.id;
2433
+ });
2434
+
2435
+ /**
2436
+ * Resolve the preview-URL alias for a version worker: the
2437
+ * user-provided `version.alias` (validated against Cloudflare's
2438
+ * naming rules), or an alias derived from the stack, stage, and
2439
+ * logical id — stable across deploys, so the aliased preview URL
2440
+ * (`<alias>-<script>.<subdomain>.workers.dev`) is a durable link
2441
+ * that always points at the latest uploaded version. Returns
2442
+ * `undefined` when the parent's script name leaves no room in the
2443
+ * 63-character DNS label and no explicit alias was given.
2444
+ */
2445
+ const resolveVersionAlias = Effect.fn(function* (
2446
+ id: string,
2447
+ news: WorkerProps,
2448
+ parentName: string,
2449
+ ) {
2450
+ // `<alias>-<script name>` is a single DNS label (≤ 63 chars).
2451
+ const budget = 63 - parentName.length - 1;
2452
+ const userAlias = news.version?.alias;
2453
+ if (userAlias !== undefined) {
2454
+ if (!/^[a-z][a-z0-9-]*$/.test(userAlias)) {
2455
+ return yield* Effect.fail(
2456
+ new WorkerVersionConfigError({
2457
+ message: `version.alias '${userAlias}' is invalid: aliases must start with a lowercase letter and contain only lowercase letters, digits, and dashes.`,
2458
+ }),
2459
+ );
2460
+ }
2461
+ if (userAlias.length > budget) {
2462
+ return yield* Effect.fail(
2463
+ new WorkerVersionConfigError({
2464
+ message: `version.alias '${userAlias}' is too long: '<alias>-${parentName}' must fit in a 63-character DNS label, leaving ${Math.max(budget, 0)} characters for the alias.`,
2465
+ }),
2466
+ );
2467
+ }
2468
+ return userAlias;
2469
+ }
2470
+ if (budget < 4) {
2471
+ return undefined;
2472
+ }
2473
+ // Deterministic per (stack, stage, id) — survives updates AND
2474
+ // replacements so the aliased preview URL never moves. A short
2475
+ // hash disambiguates different stacks/stages whose readable
2476
+ // prefixes collide after truncation.
2477
+ const hash = yield* Effect.sync(() =>
2478
+ crypto
2479
+ .createHash("sha256")
2480
+ .update(`${stack.name}/${stack.stage}/${id}`)
2481
+ .digest("hex")
2482
+ .slice(0, 6),
2483
+ );
2484
+ const readable = `${stack.stage}-${id}`
2485
+ .toLowerCase()
2486
+ .replace(/[^a-z0-9-]+/g, "-")
2487
+ .replace(/^-+|-+$/g, "");
2488
+ const alias = [readable, hash]
2489
+ .filter((part) => part.length > 0)
2490
+ .join("-")
2491
+ .slice(0, budget)
2492
+ .replace(/-+$/, "");
2493
+ return /^[a-z]/.test(alias) ? alias : `v${alias}`.slice(0, budget);
2494
+ });
2495
+
2496
+ /**
2497
+ * The parent's workers.dev preview settings + account subdomain,
2498
+ * fetched once per version reconcile. Preview URLs (aliased and
2499
+ * versioned) only serve when the parent has previews enabled.
2500
+ */
2501
+ const resolveVersionPreviewContext = Effect.fn(function* (
2502
+ accountId: string,
2503
+ scriptName: string,
2504
+ ) {
2505
+ const subdomain = yield* workers
2506
+ .getScriptSubdomain({ accountId, scriptName })
2507
+ .pipe(
2508
+ Effect.orElseSucceed<workers.GetScriptSubdomainResponse>(() => ({
2509
+ enabled: false,
2510
+ previewsEnabled: false,
2511
+ })),
2512
+ );
2513
+ const accountSubdomain = yield* getAccountSubdomain(accountId);
2514
+ return {
2515
+ previewsEnabled: subdomain.previewsEnabled === true,
2516
+ accountSubdomain,
2517
+ };
2518
+ });
2519
+
2520
+ /**
2521
+ * Reject props that can't ride along on a *version worker*
2522
+ * (`version.parent` set). A version carries only code, bindings, and
2523
+ * compatibility settings; everything script-level belongs to the
2524
+ * parent and must not be mutated from a version resource. Locally
2525
+ * hosted Durable Object / Workflow classes are rejected because their
2526
+ * migrations would apply to the parent script when the version
2527
+ * deploys.
2528
+ */
2529
+ const validateVersionWorkerProps = Effect.fn(function* (
2530
+ news: WorkerProps,
2531
+ bindings: ResourceBinding<Worker["Binding"]>[],
2532
+ parentName: string,
2533
+ ) {
2534
+ const forbidden = (
2535
+ [
2536
+ ["name", news.name],
2537
+ ["assets", news.assets],
2538
+ ["namespace", news.namespace],
2539
+ ["crons", news.crons],
2540
+ ["domain", news.domain],
2541
+ ["routes", news.routes],
2542
+ ["tags", news.tags],
2543
+ ["logpush", news.logpush],
2544
+ ["observability", news.observability],
2545
+ ["placement", news.placement],
2546
+ ["limits", news.limits],
2547
+ ["workersDev", news.workersDev],
2548
+ ["vite", news.vite],
2549
+ ] as const
2550
+ ).flatMap(([key, value]) => (value !== undefined ? [key] : []));
2551
+ if (forbidden.length > 0) {
2552
+ return yield* Effect.fail(
2553
+ new WorkerVersionConfigError({
2554
+ message:
2555
+ `version.parent uploads a version of '${parentName}' — script-level settings belong to the parent Worker and cannot be set here: ${forbidden.join(", ")}. ` +
2556
+ `Remove ${forbidden.length === 1 ? "this prop" : "these props"} or configure ${forbidden.length === 1 ? "it" : "them"} on the parent.`,
2557
+ }),
2558
+ );
2559
+ }
2560
+ yield* validateTraffic(news.version?.traffic);
2561
+ const cronBindings = getCronBindings(bindings);
2562
+ if (cronBindings.length > 0) {
2563
+ return yield* Effect.fail(
2564
+ new WorkerVersionConfigError({
2565
+ message: `Cron Triggers are script-level settings and cannot be registered from a version of '${parentName}'. Configure crons on the parent Worker.`,
2566
+ }),
2567
+ );
2568
+ }
2569
+ // Any locally-hosted DO class (a durable_object_namespace binding
2570
+ // without a foreign scriptName) or DO/Workflow export would require
2571
+ // migrations, which the versions API can't carry — and which would
2572
+ // mutate the parent's namespaces.
2573
+ const hostedClasses = getDurableObjectBindings(bindings, parentName);
2574
+ const exportedClasses = Object.keys(news.exports ?? {});
2575
+ if (hostedClasses.length > 0 || exportedClasses.length > 0) {
2576
+ return yield* Effect.fail(
2577
+ new WorkerVersionConfigError({
2578
+ message: `A version of '${parentName}' cannot host Durable Object or Workflow classes (${[
2579
+ ...new Set([
2580
+ ...hostedClasses.map((c) => c.className),
2581
+ ...exportedClasses,
2582
+ ]),
2583
+ ].join(
2584
+ ", ",
2585
+ )}): class migrations apply to the parent script. Host the classes on the parent Worker and reference them cross-script instead.`,
2586
+ }),
2587
+ );
2588
+ }
2589
+ });
2590
+
2591
+ /**
2592
+ * Reconcile a *version worker*: upload this Worker's code + bindings
2593
+ * as an immutable version of the parent's script (no script of its
2594
+ * own), then optionally shift `version.traffic`% of the parent's
2595
+ * traffic to it. Idempotent in the reconciler sense: every run
2596
+ * converges cloud state to "the parent's script has a version with
2597
+ * exactly this content, receiving exactly this traffic" — re-running
2598
+ * with changed content simply uploads the next immutable version.
2599
+ */
2600
+ const putWorkerVersion = Effect.fn(function* (
2601
+ id: string,
2602
+ news: WorkerProps,
2603
+ bindings: ResourceBinding<Worker["Binding"]>[],
2604
+ session: ScopedPlanStatusSession,
2605
+ output: Worker["Attributes"] | undefined,
2606
+ ) {
2607
+ const { accountId } = yield* yield* CloudflareEnvironment;
2608
+ const version = news.version!;
2609
+ const parentName = resolveVersionParentName(version);
2610
+ if (parentName === undefined) {
2611
+ return yield* Effect.fail(
2612
+ new WorkerVersionConfigError({
2613
+ message: `version.parent did not resolve to a Worker script name. Pass a Worker (e.g. \`yield* Cloudflare.Worker.ref(id, { stage })\`) or a literal script name.`,
2614
+ }),
2615
+ );
2616
+ }
2617
+ yield* validateVersionWorkerProps(news, bindings, parentName);
2618
+ const traffic = version.traffic ?? 0;
2619
+ // Resolve the alias and preview context BEFORE the upload: the
2620
+ // aliased preview URL (`<alias>-<script>.<subdomain>.workers.dev`)
2621
+ // is deterministic ahead of time — unlike the versioned URL, whose
2622
+ // prefix comes from the server-assigned version id — which is what
2623
+ // lets a `Worker.URL` (self_url) binding be baked into the
2624
+ // version's own bindings.
2625
+ const alias = yield* resolveVersionAlias(id, news, parentName);
2626
+ const { previewsEnabled, accountSubdomain } =
2627
+ yield* resolveVersionPreviewContext(accountId, parentName);
2628
+ const aliasedUrl =
2629
+ alias !== undefined && previewsEnabled
2630
+ ? `https://${alias}-${parentName}.${accountSubdomain}.workers.dev`
2631
+ : undefined;
2632
+ const selfUrl = hasSelfUrlBinding(bindings) ? aliasedUrl : undefined;
2633
+ if (hasSelfUrlBinding(bindings) && selfUrl === undefined) {
2634
+ return yield* Effect.fail(
2635
+ new WorkerVersionConfigError({
2636
+ message: previewsEnabled
2637
+ ? `A version of '${parentName}' binds its own URL (Worker.URL), but no preview alias fits: '<alias>-${parentName}' must stay within a 63-character DNS label. Set a short version.alias or shorten the parent's name.`
2638
+ : `A version of '${parentName}' binds its own URL (Worker.URL), but the parent's workers.dev previews are disabled — a version's URL is its aliased preview URL. Enable the parent's workers.dev subdomain (url: true, the default).`,
2639
+ }),
2640
+ );
2641
+ }
2642
+ yield* Effect.logInfo(
2643
+ `Cloudflare Worker version: preparing bundle for ${parentName} (from ${id})`,
2644
+ );
2645
+ const { bundle, hash: preparedHash } = yield* prepareAssetsAndBundle(
2646
+ id,
2647
+ news,
2648
+ { skipAssetsRead: true },
2649
+ );
2650
+ const metadataHash = yield* resolveWorkerMetadataHash({
2651
+ props: news,
2652
+ bindings,
2653
+ accountId,
2654
+ stack: { name: stack.name, stage: stack.stage },
2655
+ selfUrl,
2656
+ });
2657
+ const hash = {
2658
+ ...preparedHash,
2659
+ metadata: metadataHash,
2660
+ } satisfies Worker["Attributes"]["hash"];
2661
+ // Lower the `Worker.URL` sentinel into the aliased preview URL —
2662
+ // same lowering `putWorker` performs, with the alias standing in
2663
+ // for the script's own URL.
2664
+ const metadataBindings = bindings.flatMap((b) =>
2665
+ (b.data.bindings ?? []).map((item) =>
2666
+ item.type === "self_url"
2667
+ ? { type: "plain_text" as const, name: item.name, text: selfUrl! }
2668
+ : item,
2669
+ ),
2670
+ );
2671
+ appendAlchemyAndEnvBindings(
2672
+ metadataBindings,
2673
+ news,
2674
+ accountId,
2675
+ parentName,
2676
+ );
2677
+ const compatibility = getCompatibility(news);
2678
+ yield* session.note(`Uploading version of ${parentName} ...`);
2679
+ const created = yield* workers
2680
+ .createScriptVersion({
2681
+ accountId,
2682
+ scriptName: parentName,
2683
+ metadata: {
2684
+ mainModule: bundle.main!,
2685
+ bindings:
2686
+ metadataBindings as unknown as workers.CreateScriptVersionRequest["metadata"]["bindings"],
2687
+ compatibilityDate: compatibility.date,
2688
+ compatibilityFlags: compatibility.flags,
2689
+ cache: news.cache ?? getCacheBinding(bindings),
2690
+ annotations:
2691
+ alias !== undefined ||
2692
+ version.message !== undefined ||
2693
+ version.tag !== undefined
2694
+ ? {
2695
+ workersAlias: alias,
2696
+ workersMessage: version.message,
2697
+ workersTag: version.tag,
2698
+ }
2699
+ : undefined,
2700
+ },
2701
+ files: bundle.files,
2702
+ })
2703
+ .pipe(
2704
+ Effect.retry({
2705
+ while: isBindingTargetNotFound,
2706
+ schedule: bindingTargetNotFoundRetrySchedule(),
2707
+ }),
2708
+ Effect.catchTag("WorkerNotFound", () =>
2709
+ Effect.fail(
2710
+ new WorkerVersionConfigError({
2711
+ message: `version.parent script '${parentName}' does not exist. Deploy the parent Worker first (or check the referenced stage/stack).`,
2712
+ }),
2713
+ ),
2714
+ ),
2715
+ );
2716
+ const versionId = created.id ?? undefined;
2717
+ if (versionId === undefined) {
2718
+ return yield* Effect.fail(
2719
+ new WorkerVersionConfigError({
2720
+ message: `Cloudflare did not return a version id for the uploaded version of '${parentName}'.`,
2721
+ }),
2722
+ );
2723
+ }
2724
+ let deploymentId: string | undefined;
2725
+ if (traffic > 0) {
2726
+ yield* session.note(
2727
+ `Deploying version at ${traffic}% of ${parentName}'s traffic ...`,
2728
+ );
2729
+ deploymentId = yield* deployVersionTraffic({
2730
+ accountId,
2731
+ scriptName: parentName,
2732
+ versionId,
2733
+ traffic,
2734
+ message: version.message,
2735
+ });
2736
+ }
2737
+ // Version-affinity transform rules (`version.affinity`): a canary
2738
+ // rides the *parent's* zone traffic, so the rules land on the
2739
+ // parent's zones — observed custom-domain attachments plus, when
2740
+ // `parent` was passed as a Worker resource, its recorded routes.
2741
+ const previousAffinityZoneIds = output?.affinityZoneIds ?? [];
2742
+ let affinityZoneIds: string[] | undefined;
2743
+ if (version.affinity !== undefined || previousAffinityZoneIds.length) {
2744
+ const resolvedAffinity =
2745
+ version.affinity !== undefined
2746
+ ? yield* resolveVersionAffinity(version.affinity)
2747
+ : undefined;
2748
+ let hostsByZone: Map<string, AffinityZoneHost[]> | undefined;
2749
+ if (resolvedAffinity !== undefined) {
2750
+ hostsByZone = new Map();
2751
+ const addHost = (zoneId: string, host: AffinityZoneHost) => {
2752
+ const hosts = hostsByZone!.get(zoneId) ?? [];
2753
+ hosts.push(host);
2754
+ hostsByZone!.set(zoneId, hosts);
2755
+ };
2756
+ // A `version.parent` passed as a Worker resource resolves to
2757
+ // the parent's Attributes at reconcile time (see
2758
+ // resolveVersionParentName) — its recorded routes and domain
2759
+ // tell us the zones the parent serves on.
2760
+ const parentAttrs =
2761
+ typeof version.parent === "object" && version.parent !== null
2762
+ ? (version.parent as unknown as Partial<Worker["Attributes"]>)
2763
+ : undefined;
2764
+ const redirectHosts = new Set(parentAttrs?.domain?.redirects ?? []);
2765
+ const attached = yield* workers
2766
+ .listDomains({ accountId, service: parentName })
2767
+ .pipe(Effect.map((r) => r.result ?? []));
2768
+ for (const d of attached) {
2769
+ if (!d.hostname || !d.zoneId || redirectHosts.has(d.hostname)) {
2770
+ continue;
2771
+ }
2772
+ addHost(d.zoneId, { host: d.hostname, wildcard: false });
2773
+ }
2774
+ for (const route of parentAttrs?.routes ?? []) {
2775
+ const host = route.pattern.split("/")[0];
2776
+ if (!host || host.includes('"')) continue;
2777
+ addHost(route.zoneId, { host, wildcard: host.includes("*") });
2778
+ }
2779
+ if (hostsByZone.size === 0) {
2780
+ return yield* Effect.fail(
2781
+ new WorkerVersionConfigError({
2782
+ message:
2783
+ `version.affinity pins users via a zone Transform Rule setting the ${AFFINITY_HEADER} header, which only sees zone traffic — the parent '${parentName}' has no custom domains (or recorded zone routes) to place the rule on. ` +
2784
+ `Give the parent a \`domain\` or zone \`routes\`, or pass the parent as a Worker resource so its routes are visible here.`,
2785
+ }),
2786
+ );
2787
+ }
2788
+ yield* session.note("Reconciling version-affinity rules ...");
2789
+ }
2790
+ const placed = yield* reconcileAffinityRules({
2791
+ scriptName: parentName,
2792
+ desired:
2793
+ resolvedAffinity !== undefined && hostsByZone !== undefined
2794
+ ? { affinity: resolvedAffinity, hostsByZone }
2795
+ : undefined,
2796
+ previousZoneIds: previousAffinityZoneIds,
2797
+ });
2798
+ affinityZoneIds = placed.length > 0 ? placed : undefined;
2799
+ }
2800
+ // The aliased URL is primary (`url`): it is stable across deploys,
2801
+ // re-pointing at each newly uploaded version. The per-version URL
2802
+ // (prefixed by the version id) rides along in `urls`.
2803
+ const versionedUrl = previewsEnabled
2804
+ ? `https://${versionId.split("-")[0]}-${parentName}.${accountSubdomain}.workers.dev`
2805
+ : undefined;
2806
+ const urls = [
2807
+ ...(aliasedUrl ? [aliasedUrl] : []),
2808
+ ...(versionedUrl ? [versionedUrl] : []),
2809
+ ];
2810
+ return {
2811
+ workerId: parentName,
2812
+ workerName: parentName,
2813
+ namespace: undefined,
2814
+ logpush: undefined,
2815
+ url: urls[0],
2816
+ urls,
2817
+ domain: undefined,
2818
+ tags: undefined,
2819
+ durableObjectNamespaces: {},
2820
+ accountId,
2821
+ routes: [],
2822
+ crons: [],
2823
+ versionOf: parentName,
2824
+ versionId,
2825
+ versionAlias: alias,
2826
+ deploymentId,
2827
+ affinityZoneIds,
2828
+ hash,
2829
+ } satisfies Worker["Attributes"];
2830
+ });
2831
+
1372
2832
  const putWorker = Effect.fn(function* (
1373
2833
  id: string,
1374
2834
  news: WorkerProps,
@@ -1388,6 +2848,26 @@ export const LiveWorkerProvider = () =>
1388
2848
  // account-level script. The put/settings calls switch endpoints and
1389
2849
  // the subdomain / custom-domain / cron reconciliation is skipped.
1390
2850
  const dispatchNamespace = resolveNamespaceName(news?.namespace);
2851
+ yield* validateTraffic(news.version?.traffic);
2852
+ if (news.version !== undefined && dispatchNamespace) {
2853
+ return yield* Effect.fail(
2854
+ new WorkerVersionConfigError({
2855
+ message: `Workers for Platforms user workers do not support versions or gradual deployments — remove the version prop from '${name}'.`,
2856
+ }),
2857
+ );
2858
+ }
2859
+ // Resolve the Worker's own URL up front when a `Worker.URL` binding
2860
+ // is present: the value must exist before the bundle is built (Vite
2861
+ // inlines VITE_*-prefixed env into the client bundle) and before the
2862
+ // metadata bindings are assembled (the `self_url` sentinel lowers
2863
+ // into a plain_text binding below).
2864
+ const selfUrl = hasSelfUrlBinding(bindings)
2865
+ ? dispatchNamespace
2866
+ ? yield* Effect.die(
2867
+ `Worker "${name}" binds its own URL (Worker.URL), but a dispatch-namespace user worker is invoked via dynamic dispatch and has no URL of its own.`,
2868
+ )
2869
+ : yield* resolveSelfUrl(name, news, accountId)
2870
+ : undefined;
1391
2871
  yield* Effect.logInfo(
1392
2872
  `Cloudflare Worker ${olds ? "update" : "create"}: preparing bundle for ${name}`,
1393
2873
  );
@@ -1403,6 +2883,7 @@ export const LiveWorkerProvider = () =>
1403
2883
  hash: preparedHash,
1404
2884
  } = yield* prepareAssetsAndBundle(id, news, {
1405
2885
  skipAssetsRead: prebuiltAssets?.skip,
2886
+ selfUrl,
1406
2887
  });
1407
2888
  // When the caller supplied a precomputed hash (e.g. via
1408
2889
  // `Command.Build`), store *that* hash in output state so the
@@ -1415,6 +2896,7 @@ export const LiveWorkerProvider = () =>
1415
2896
  bindings,
1416
2897
  accountId,
1417
2898
  stack: { name: stack.name, stage: stack.stage },
2899
+ selfUrl,
1418
2900
  });
1419
2901
  const hash = {
1420
2902
  ...preparedHash,
@@ -1426,7 +2908,12 @@ export const LiveWorkerProvider = () =>
1426
2908
  // `transferred_classes` migration below and must be stripped from the
1427
2909
  // wire-shape binding before upload.
1428
2910
  const metadataBindings = bindings.flatMap((b) =>
1429
- (b.data.bindings ?? []).map((item): WorkerBinding => {
2911
+ (b.data.bindings ?? []).map((item): WireWorkerBinding => {
2912
+ // Lower the `Worker.URL` sentinel into the resolved URL —
2913
+ // Cloudflare has no native binding for it.
2914
+ if (item.type === "self_url") {
2915
+ return { type: "plain_text", name: item.name, text: selfUrl! };
2916
+ }
1430
2917
  if (
1431
2918
  item.type === "durable_object_namespace" &&
1432
2919
  item.transferredFrom !== undefined
@@ -1434,6 +2921,16 @@ export const LiveWorkerProvider = () =>
1434
2921
  const { transferredFrom: _, ...rest } = item;
1435
2922
  return rest;
1436
2923
  }
2924
+ // `queueId` (mode discrimination) and `shim` (dev-mode remote
2925
+ // producer) are alchemy-only metadata on queue bindings — strip
2926
+ // them from the wire shape.
2927
+ if (
2928
+ item.type === "queue" &&
2929
+ (item.queueId !== undefined || item.shim !== undefined)
2930
+ ) {
2931
+ const { queueId: _, shim: __, ...rest } = item;
2932
+ return rest;
2933
+ }
1437
2934
  return item;
1438
2935
  }),
1439
2936
  );
@@ -1498,58 +2995,7 @@ export const LiveWorkerProvider = () =>
1498
2995
  name: "ASSETS",
1499
2996
  });
1500
2997
  }
1501
- metadataBindings.push(
1502
- {
1503
- type: "plain_text",
1504
- name: "ALCHEMY_PHASE",
1505
- text: "runtime",
1506
- },
1507
- {
1508
- type: "plain_text",
1509
- name: "ALCHEMY_STACK_NAME",
1510
- text: stack.name,
1511
- },
1512
- {
1513
- type: "plain_text",
1514
- name: "ALCHEMY_STAGE",
1515
- text: stack.stage,
1516
- },
1517
- {
1518
- type: "plain_text",
1519
- name: "ALCHEMY_CLOUDFLARE_ACCOUNT_ID",
1520
- text: accountId,
1521
- },
1522
- );
1523
- // Add environment variables as metadata bindings
1524
- if (news.env) {
1525
- for (const [key, value] of Object.entries(news.env)) {
1526
- if (value === undefined) continue;
1527
- if (metadataBindings.some((b) => b.name === key)) continue;
1528
- if (Redacted.isRedacted(value)) {
1529
- const unredacted = Redacted.value(value);
1530
- metadataBindings.push({
1531
- type: "secret_text",
1532
- name: key,
1533
- text:
1534
- typeof unredacted === "string"
1535
- ? unredacted
1536
- : JSON.stringify(unredacted),
1537
- });
1538
- } else if (typeof value === "string") {
1539
- metadataBindings.push({
1540
- type: "plain_text",
1541
- name: key,
1542
- text: value,
1543
- });
1544
- } else {
1545
- metadataBindings.push({
1546
- type: "json",
1547
- name: key,
1548
- json: value,
1549
- });
1550
- }
1551
- }
1552
- }
2998
+ appendAlchemyAndEnvBindings(metadataBindings, news, accountId, name);
1553
2999
  yield* Effect.logInfo(
1554
3000
  `Cloudflare Worker ${olds ? "update" : "create"}: uploading script for ${name}`,
1555
3001
  );
@@ -1896,47 +3342,155 @@ export const LiveWorkerProvider = () =>
1896
3342
  tailConsumers: undefined,
1897
3343
  usageModel: undefined,
1898
3344
  };
1899
- const worker = yield* putWorkerScript({
1900
- accountId,
1901
- scriptName: name,
1902
- dispatchNamespace,
1903
- metadata,
1904
- files: bundle.files,
1905
- }).pipe(
1906
- Effect.catch((err) => {
1907
- // When adopting a Worker managed by Wrangler (or after a previous
1908
- // deploy with mismatched migrations), the old_tag precondition
1909
- // fails. The only way to discover the actual tag is through the
1910
- // error message — getScriptSettings is meant to return it but
1911
- // doesn't at runtime.
1912
- const msg = String(
1913
- typeof err === "object" && err !== null && "message" in err
1914
- ? err.message
1915
- : err,
3345
+ const rolloutTraffic = getSelfRolloutTraffic(news);
3346
+ let versionId: string | undefined;
3347
+ let deploymentId: string | undefined;
3348
+ let worker: { id?: string | null; logpush?: boolean | null };
3349
+ // A gradual rollout (`version.traffic` < 100) deploys through the
3350
+ // versions API instead of the full-cutover script PUT. That's only
3351
+ // possible when the script already has a live deployment to split
3352
+ // traffic against (otherwise the first deploy takes 100%), and only
3353
+ // for changes a version can carry — code, bindings, compatibility
3354
+ // settings, cache. Script-level settings in the upload metadata
3355
+ // (tags, observability, limits, placement, logpush) keep their live
3356
+ // values until the next full deploy, matching
3357
+ // `wrangler versions upload` semantics.
3358
+ if (
3359
+ rolloutTraffic !== undefined &&
3360
+ existingSettings !== undefined &&
3361
+ output?.hash !== undefined &&
3362
+ !dispatchNamespace
3363
+ ) {
3364
+ if (metadataAssets !== undefined) {
3365
+ return yield* Effect.fail(
3366
+ new WorkerVersionConfigError({
3367
+ message: `Worker '${name}' has static assets, which the versions API cannot carry — gradual rollouts (version.traffic) are not supported for Workers with assets.`,
3368
+ }),
1916
3369
  );
1917
- const expectedTag = msg.match(
1918
- /when expected tag is ['"]?([^'"]+)['"]?/,
1919
- )?.[1];
1920
- if (expectedTag) {
1921
- return putWorkerScript({
1922
- accountId,
1923
- scriptName: name,
1924
- dispatchNamespace,
1925
- metadata: {
1926
- ...metadata,
1927
- migrations: {
1928
- ...migrations,
1929
- oldTag: expectedTag,
1930
- newTag: bumpMigrationTagVersion(expectedTag),
3370
+ }
3371
+ const migratedClasses = [
3372
+ ...migrations.newClasses,
3373
+ ...migrations.newSqliteClasses,
3374
+ ...migrations.deletedClasses,
3375
+ ...migrations.renamedClasses.map((r) => r.to),
3376
+ ...migrations.transferredClasses.map((t) => t.to),
3377
+ ];
3378
+ if (migratedClasses.length > 0) {
3379
+ return yield* Effect.fail(
3380
+ new WorkerVersionConfigError({
3381
+ message: `This deploy of '${name}' changes Durable Object classes (${migratedClasses.join(", ")}), which requires a migration — migrations cannot ride a gradual rollout. Deploy at 100% (remove version.traffic) first, then resume gradual rollouts.`,
3382
+ }),
3383
+ );
3384
+ }
3385
+ yield* session.note(
3386
+ `Uploading version of ${name} (${bundleSize}) ...`,
3387
+ );
3388
+ const created = yield* workers
3389
+ .createScriptVersion({
3390
+ accountId,
3391
+ scriptName: name,
3392
+ metadata: {
3393
+ mainModule: metadata.mainModule!,
3394
+ bindings:
3395
+ metadata.bindings as unknown as workers.CreateScriptVersionRequest["metadata"]["bindings"],
3396
+ compatibilityDate: metadata.compatibilityDate,
3397
+ compatibilityFlags: metadata.compatibilityFlags,
3398
+ cache: metadata.cache,
3399
+ annotations:
3400
+ news.version?.alias !== undefined ||
3401
+ news.version?.message !== undefined ||
3402
+ news.version?.tag !== undefined
3403
+ ? {
3404
+ workersAlias: news.version?.alias,
3405
+ workersMessage: news.version?.message,
3406
+ workersTag: news.version?.tag,
3407
+ }
3408
+ : undefined,
3409
+ },
3410
+ files: bundle.files,
3411
+ })
3412
+ .pipe(
3413
+ Effect.retry({
3414
+ while: isBindingTargetNotFound,
3415
+ schedule: bindingTargetNotFoundRetrySchedule(),
3416
+ }),
3417
+ );
3418
+ versionId = created.id ?? undefined;
3419
+ if (versionId === undefined) {
3420
+ return yield* Effect.fail(
3421
+ new WorkerVersionConfigError({
3422
+ message: `Cloudflare did not return a version id for the uploaded version of '${name}'.`,
3423
+ }),
3424
+ );
3425
+ }
3426
+ if (rolloutTraffic > 0) {
3427
+ yield* session.note(
3428
+ `Deploying version at ${rolloutTraffic}% of traffic ...`,
3429
+ );
3430
+ deploymentId = yield* deployVersionTraffic({
3431
+ accountId,
3432
+ scriptName: name,
3433
+ versionId,
3434
+ traffic: rolloutTraffic,
3435
+ message: news.version?.message,
3436
+ });
3437
+ }
3438
+ worker = { id: name, logpush: existingSettings.logpush };
3439
+ } else {
3440
+ if (rolloutTraffic !== undefined) {
3441
+ // First deploy of this script (or a pre-create placeholder is
3442
+ // the only thing live) — there is no previous version worth
3443
+ // splitting traffic with, so this deploy takes 100%.
3444
+ yield* Effect.logInfo(
3445
+ `Cloudflare Worker ${name}: no previous live version to split traffic with; deploying at 100%`,
3446
+ );
3447
+ }
3448
+ worker = yield* putWorkerScriptWithMigrationRecovery();
3449
+ }
3450
+
3451
+ function putWorkerScriptWithMigrationRecovery() {
3452
+ return putWorkerScript({
3453
+ accountId,
3454
+ scriptName: name,
3455
+ dispatchNamespace,
3456
+ metadata,
3457
+ files: bundle.files,
3458
+ }).pipe(
3459
+ Effect.catch((err) => {
3460
+ // When adopting a Worker managed by Wrangler (or after a previous
3461
+ // deploy with mismatched migrations), the old_tag precondition
3462
+ // fails. The only way to discover the actual tag is through the
3463
+ // error message — getScriptSettings is meant to return it but
3464
+ // doesn't at runtime.
3465
+ const msg = String(
3466
+ typeof err === "object" && err !== null && "message" in err
3467
+ ? err.message
3468
+ : err,
3469
+ );
3470
+ const expectedTag = msg.match(
3471
+ /when expected tag is ['"]?([^'"]+)['"]?/,
3472
+ )?.[1];
3473
+ if (expectedTag) {
3474
+ return putWorkerScript({
3475
+ accountId,
3476
+ scriptName: name,
3477
+ dispatchNamespace,
3478
+ metadata: {
3479
+ ...metadata,
3480
+ migrations: {
3481
+ ...migrations,
3482
+ oldTag: expectedTag,
3483
+ newTag: bumpMigrationTagVersion(expectedTag),
3484
+ },
1931
3485
  },
1932
- },
1933
- files: bundle.files,
1934
- });
1935
- }
1936
- // @effect-diagnostics-next-line anyUnknownInErrorContext:off
1937
- return Effect.fail(err as any);
1938
- }),
1939
- );
3486
+ files: bundle.files,
3487
+ });
3488
+ }
3489
+ // @effect-diagnostics-next-line anyUnknownInErrorContext:off
3490
+ return Effect.fail(err as any);
3491
+ }),
3492
+ );
3493
+ }
1940
3494
  const { settings, durableObjectNamespaces } =
1941
3495
  yield* getWorkerSettingsWithDurableObjects(
1942
3496
  name,
@@ -1957,20 +3511,21 @@ export const LiveWorkerProvider = () =>
1957
3511
  tags: settings.tags ?? metadata.tags,
1958
3512
  durableObjectNamespaces,
1959
3513
  accountId,
1960
- domains: [],
3514
+ urls: [],
3515
+ domain: undefined,
1961
3516
  routes: [],
1962
3517
  crons: [],
1963
3518
  hash,
1964
3519
  } satisfies Worker["Attributes"];
1965
3520
  }
1966
- // Reconcile workers.dev subdomain against observed cloud state.
1967
- // We can't diff `news.url` against `olds.url` here because both
1968
- // default to `undefined` (meaning "enable") — that comparison
1969
- // would skip the API call on every deploy where the user never
1970
- // explicitly set `url`, leaving the subdomain in whatever state
3521
+ // Reconcile the workers.dev settings against observed cloud state.
3522
+ // We can't diff `news.workersDev` against `olds.workersDev` here
3523
+ // because both default to `undefined` (meaning "enable") — that
3524
+ // comparison would skip the API call on every deploy where the user
3525
+ // never set the prop, leaving the subdomain in whatever state
1971
3526
  // Cloudflare currently has it (disabled by default, or whatever
1972
3527
  // a previous failed/external action left it as).
1973
- const desiredSubdomainEnabled = news.url !== false;
3528
+ const workersDev = resolveWorkersDev(news.workersDev);
1974
3529
  const observedSubdomain = yield* workers
1975
3530
  .getScriptSubdomain({
1976
3531
  accountId,
@@ -1983,11 +3538,11 @@ export const LiveWorkerProvider = () =>
1983
3538
  })),
1984
3539
  );
1985
3540
  if (
1986
- desiredSubdomainEnabled !== observedSubdomain.enabled ||
1987
- desiredSubdomainEnabled !== observedSubdomain.previewsEnabled
3541
+ workersDev.enabled !== observedSubdomain.enabled ||
3542
+ workersDev.previewsEnabled !== observedSubdomain.previewsEnabled
1988
3543
  ) {
1989
3544
  yield* session.note(
1990
- `${desiredSubdomainEnabled ? "Enabling" : "Disabling"} workers.dev subdomain...`,
3545
+ `${workersDev.enabled || workersDev.previewsEnabled ? "Enabling" : "Disabling"} workers.dev subdomain...`,
1991
3546
  );
1992
3547
  // Cloudflare's script registry is eventually consistent — for the
1993
3548
  // first few hundred ms after `putScript` returns, POST /subdomain
@@ -1997,7 +3552,7 @@ export const LiveWorkerProvider = () =>
1997
3552
  // Retry the subdomain toggle on those transient tags with a short
1998
3553
  // exponential backoff; same pattern we use elsewhere in this
1999
3554
  // provider for DO-namespace propagation and for `putScript` itself.
2000
- yield* setWorkerSubdomain(name, desiredSubdomainEnabled).pipe(
3555
+ yield* setWorkerSubdomain(name, workersDev).pipe(
2001
3556
  Effect.retry({
2002
3557
  while: (error) =>
2003
3558
  error._tag === "WorkerNotFound" ||
@@ -2010,22 +3565,99 @@ export const LiveWorkerProvider = () =>
2010
3565
  }),
2011
3566
  );
2012
3567
  }
2013
- const desiredDomains = normalizeDomains(news.domain);
2014
- const previousDomains = output?.domains ?? [];
2015
- if (desiredDomains.length > 0 || previousDomains.length > 0) {
3568
+ // Custom domains are managed only when `domain` is declared on props
3569
+ // (#942): an omitted `domain` leaves live attachments alone — and
3570
+ // spares a plain workers.dev Worker the listDomains call on every
3571
+ // deploy (#926) — while `domain: null` explicitly detaches
3572
+ // everything. When managed, the canonical name + aliases + redirect
3573
+ // hostnames are all attached (DNS + edge certificate); redirect
3574
+ // hostnames additionally get a redirect rule, which runs before the
3575
+ // Worker so those requests never invoke it. Unmanaged domains
3576
+ // persisted in state carry forward so read keeps observing them.
3577
+ const manageCustomDomains = news.domain !== undefined;
3578
+ const domainConfig = yield* resolveWorkerDomain(news.domain);
3579
+ const previousDomain = stateWorkerDomain(output);
3580
+ let effectiveDomain = manageCustomDomains
3581
+ ? domainConfig
3582
+ : previousDomain;
3583
+ if (!manageCustomDomains && previousDomain !== undefined) {
3584
+ // Unmanaged, but state remembers domains: observe which of them
3585
+ // are actually still attached and carry only those forward —
3586
+ // stale state entries (e.g. legacy records whose attach never
3587
+ // happened, or hostnames detached out-of-band) must not
3588
+ // resurface in `urls`. Plain workers.dev Workers (no persisted
3589
+ // domains) never pay this listDomains call (#926).
3590
+ const live = new Set(
3591
+ yield* workers.listDomains({ accountId, service: name }).pipe(
3592
+ Effect.map((r) =>
3593
+ (r.result ?? []).flatMap((d) =>
3594
+ d.hostname ? [d.hostname] : [],
3595
+ ),
3596
+ ),
3597
+ Effect.catch(() => Effect.succeed([] as string[])),
3598
+ ),
3599
+ );
3600
+ const serving = [
3601
+ ...(live.has(previousDomain.name) ? [previousDomain.name] : []),
3602
+ ...previousDomain.aliases.filter((h) => live.has(h)),
3603
+ ];
3604
+ effectiveDomain =
3605
+ serving.length > 0
3606
+ ? {
3607
+ name: serving[0],
3608
+ aliases: serving.slice(1),
3609
+ redirects: previousDomain.redirects.filter((h) =>
3610
+ live.has(h),
3611
+ ),
3612
+ }
3613
+ : undefined;
3614
+ }
3615
+ if (manageCustomDomains) {
3616
+ const desiredHostnames = domainConfig
3617
+ ? [
3618
+ domainConfig.name,
3619
+ ...domainConfig.aliases,
3620
+ ...domainConfig.redirects,
3621
+ ]
3622
+ : [];
2016
3623
  yield* session.note(
2017
- `Reconciling custom domains (${desiredDomains.length}) ...`,
3624
+ `Reconciling custom domains (${desiredHostnames.length}) ...`,
2018
3625
  );
3626
+ // Capture hostname → zone for *currently attached* domains before
3627
+ // reconcile detaches removed ones — a removed redirect hostname's
3628
+ // zone is otherwise unresolvable and its rule couldn't be cleaned.
3629
+ const liveBeforeReconcile = yield* workers
3630
+ .listDomains({ accountId, service: name })
3631
+ .pipe(
3632
+ Effect.map((r) =>
3633
+ (r.result ?? []).flatMap((d) =>
3634
+ d.hostname && d.zoneId
3635
+ ? [[d.hostname, d.zoneId] as const]
3636
+ : [],
3637
+ ),
3638
+ ),
3639
+ Effect.catch(() => Effect.succeed([])),
3640
+ );
3641
+ const reconciled = yield* reconcileDomains(name, desiredHostnames);
3642
+ const zoneIdByHostname = new Map([
3643
+ ...liveBeforeReconcile,
3644
+ ...reconciled.map((d) => [d.hostname, d.zoneId] as const),
3645
+ ]);
3646
+ yield* reconcileRedirectRules({
3647
+ scriptName: name,
3648
+ domain: domainConfig,
3649
+ zoneIdByHostname,
3650
+ previousRedirects: previousDomain?.redirects ?? [],
3651
+ });
3652
+ effectiveDomain = domainConfig;
2019
3653
  }
2020
- const reconciled = yield* reconcileDomains(name, desiredDomains);
2021
- const workersDevUrl =
2022
- news.url !== false
2023
- ? `https://${name}.${yield* getAccountSubdomain(accountId)}.workers.dev`
2024
- : undefined;
2025
- const domains = [
2026
- ...reconciled.map((d) => `https://${d.hostname}`),
2027
- ...(workersDevUrl ? [workersDevUrl] : []),
2028
- ];
3654
+ const urls = yield* computeWorkerUrls({
3655
+ scriptName: name,
3656
+ workersDev,
3657
+ domain: effectiveDomain,
3658
+ uploadedVersionId: versionId,
3659
+ uploadedVersionAlias: news.version?.alias,
3660
+ });
2029
3661
  const desiredRoutes = yield* normalizeRoutes(news.routes);
2030
3662
  const previousRoutes = output?.routes ?? [];
2031
3663
  if (desiredRoutes.length > 0 || previousRoutes.length > 0) {
@@ -2038,24 +3670,96 @@ export const LiveWorkerProvider = () =>
2038
3670
  desiredRoutes,
2039
3671
  previousRoutes,
2040
3672
  );
2041
- const crons = yield* reconcileCrons(
2042
- name,
2043
- normalizeCrons([...getCronBindings(bindings), ...(news.crons ?? [])]),
2044
- output?.crons ?? [],
2045
- session,
2046
- );
3673
+ // Version-affinity transform rules (`version.affinity`): pin each
3674
+ // user to one version during gradual rollouts by filling the
3675
+ // version-key header from a stable request property, on every zone
3676
+ // this Worker serves on. Runs after the domain/route reconciles so
3677
+ // freshly attached surfaces resolve their zones, and also when only
3678
+ // *previous* state holds rules, so removing affinity converges.
3679
+ const affinity =
3680
+ news.version?.parent == null ? news.version?.affinity : undefined;
3681
+ const previousAffinityZoneIds = output?.affinityZoneIds ?? [];
3682
+ let affinityZoneIds: string[] | undefined;
3683
+ if (affinity !== undefined || previousAffinityZoneIds.length > 0) {
3684
+ const resolvedAffinity =
3685
+ affinity !== undefined
3686
+ ? yield* resolveVersionAffinity(affinity)
3687
+ : undefined;
3688
+ let hostsByZone: Map<string, AffinityZoneHost[]> | undefined;
3689
+ if (resolvedAffinity !== undefined) {
3690
+ hostsByZone = new Map();
3691
+ const addHost = (zoneId: string, host: AffinityZoneHost) => {
3692
+ const hosts = hostsByZone!.get(zoneId) ?? [];
3693
+ hosts.push(host);
3694
+ hostsByZone!.set(zoneId, hosts);
3695
+ };
3696
+ // Serving hostnames from *observed* domain attachments (covers
3697
+ // managed, unmanaged, and adopted domains alike); redirect
3698
+ // hostnames 301 before the Worker and never need a key.
3699
+ const redirectHosts = new Set(effectiveDomain?.redirects ?? []);
3700
+ const attached = yield* workers
3701
+ .listDomains({ accountId, service: name })
3702
+ .pipe(Effect.map((r) => r.result ?? []));
3703
+ for (const d of attached) {
3704
+ if (!d.hostname || !d.zoneId || redirectHosts.has(d.hostname)) {
3705
+ continue;
3706
+ }
3707
+ addHost(d.zoneId, { host: d.hostname, wildcard: false });
3708
+ }
3709
+ for (const route of routes) {
3710
+ const host = route.pattern.split("/")[0];
3711
+ if (!host || host.includes('"')) continue;
3712
+ addHost(route.zoneId, { host, wildcard: host.includes("*") });
3713
+ }
3714
+ if (hostsByZone.size === 0) {
3715
+ return yield* Effect.fail(
3716
+ new WorkerVersionConfigError({
3717
+ message:
3718
+ `version.affinity pins users via a zone Transform Rule setting the ${AFFINITY_HEADER} header, which only sees zone traffic — give '${name}' a custom domain (\`domain\`) or zone \`routes\`. ` +
3719
+ `On the bare workers.dev URL, clients must send the header themselves.`,
3720
+ }),
3721
+ );
3722
+ }
3723
+ yield* session.note("Reconciling version-affinity rules ...");
3724
+ }
3725
+ const placed = yield* reconcileAffinityRules({
3726
+ scriptName: name,
3727
+ desired:
3728
+ resolvedAffinity !== undefined && hostsByZone !== undefined
3729
+ ? { affinity: resolvedAffinity, hostsByZone }
3730
+ : undefined,
3731
+ previousZoneIds: previousAffinityZoneIds,
3732
+ });
3733
+ affinityZoneIds = placed.length > 0 ? placed : undefined;
3734
+ }
3735
+ const desiredCrons = normalizeCrons([
3736
+ ...getCronBindings(bindings),
3737
+ ...(news.crons ?? []),
3738
+ ]);
3739
+ const previousCrons = output?.crons ?? [];
3740
+ // Same gating as read: skip getScriptSchedule when neither props nor
3741
+ // prior state indicate cron management (#926).
3742
+ const crons =
3743
+ desiredCrons.length > 0 || previousCrons.length > 0
3744
+ ? yield* reconcileCrons(name, desiredCrons, previousCrons, session)
3745
+ : [];
2047
3746
  return {
2048
3747
  workerId: worker.id ?? name,
2049
3748
  workerName: name,
2050
3749
  namespace: undefined,
2051
3750
  logpush: worker.logpush ?? undefined,
2052
- url: domains[0],
3751
+ url: urls[0],
3752
+ urls,
3753
+ domain: effectiveDomain,
2053
3754
  tags: settings.tags ?? metadata.tags,
2054
3755
  durableObjectNamespaces,
2055
3756
  accountId,
2056
- domains,
2057
3757
  routes,
2058
3758
  crons,
3759
+ versionOf: undefined,
3760
+ versionId,
3761
+ deploymentId,
3762
+ affinityZoneIds,
2059
3763
  hash,
2060
3764
  } satisfies Worker["Attributes"];
2061
3765
  });
@@ -2074,11 +3778,34 @@ export const LiveWorkerProvider = () =>
2074
3778
  // unresolved: the hash can't be computed deterministically here, and
2075
3779
  // the eventual apply stores it once bindings resolve.
2076
3780
  if (bindings) {
3781
+ // For a version worker, `Worker.URL` resolves to the *aliased
3782
+ // preview URL* (see `putWorkerVersion`) — recompute the same
3783
+ // value here so the metadata hash matches what the apply stored
3784
+ // and unchanged deploys stay noops.
3785
+ const versionParent =
3786
+ props.version?.parent != null
3787
+ ? (resolveVersionParentName(props.version) ?? output.versionOf)
3788
+ : undefined;
3789
+ const selfUrl = hasSelfUrlBinding(bindings)
3790
+ ? versionParent !== undefined
3791
+ ? yield* Effect.gen(function* () {
3792
+ const alias = yield* resolveVersionAlias(
3793
+ id,
3794
+ props,
3795
+ versionParent,
3796
+ );
3797
+ return alias !== undefined
3798
+ ? `https://${alias}-${versionParent}.${yield* getAccountSubdomain(accountId)}.workers.dev`
3799
+ : undefined;
3800
+ })
3801
+ : yield* resolveSelfUrl(output.workerName, props, accountId)
3802
+ : undefined;
2077
3803
  const metadataHash = yield* resolveWorkerMetadataHash({
2078
3804
  props,
2079
3805
  bindings,
2080
3806
  accountId,
2081
3807
  stack: { name: stack.name, stage: stack.stage },
3808
+ selfUrl,
2082
3809
  });
2083
3810
  if (metadataHash !== output.hash?.metadata) {
2084
3811
  return true;
@@ -2108,6 +3835,25 @@ export const LiveWorkerProvider = () =>
2108
3835
  );
2109
3836
  return hash !== output.hash?.input;
2110
3837
  }
3838
+ // Assets-only Worker — there is no bundle to hash. A stored bundle
3839
+ // hash means the Worker previously had a script and is being
3840
+ // converted to assets-only, which must deploy.
3841
+ if (props.main === undefined) {
3842
+ if (output.hash?.bundle !== undefined) {
3843
+ return true;
3844
+ }
3845
+ const assetsHash =
3846
+ props.assets && Predicate.hasProperty(props.assets, "hash")
3847
+ ? props.assets.hash
3848
+ : undefined;
3849
+ if (assetsHash === undefined) {
3850
+ // Same conservative rule as the directory-shaped `assets` below:
3851
+ // don't read the directory during diff; `putWorker` reads once
3852
+ // and keeps the existing manifest if nothing actually changed.
3853
+ return true;
3854
+ }
3855
+ return assetsHash !== output.hash?.assets;
3856
+ }
2111
3857
  const bundleHash = yield* prepareBundle(id, props).pipe(
2112
3858
  Effect.map((b) => b.hash),
2113
3859
  );
@@ -2185,7 +3931,8 @@ export const LiveWorkerProvider = () =>
2185
3931
  url: undefined,
2186
3932
  tags: script.tags ?? undefined,
2187
3933
  durableObjectNamespaces: {},
2188
- domains: [],
3934
+ urls: [],
3935
+ domain: undefined,
2189
3936
  routes: [],
2190
3937
  crons: [],
2191
3938
  },
@@ -2227,6 +3974,27 @@ export const LiveWorkerProvider = () =>
2227
3974
  if (newNamespace !== oldNamespace) {
2228
3975
  return { action: "replace" };
2229
3976
  }
3977
+ // A version worker (version.parent) and a script-owning Worker are
3978
+ // distinct cloud resources, and a version belongs to exactly one
3979
+ // parent script — switching mode or parent is a replacement.
3980
+ const newIsVersion = news.version?.parent != null;
3981
+ const oldIsVersion =
3982
+ output?.versionOf !== undefined || olds?.version?.parent != null;
3983
+ if (newIsVersion !== oldIsVersion) {
3984
+ return { action: "replace" };
3985
+ }
3986
+ if (newIsVersion) {
3987
+ const newParent = resolveVersionParentName(news.version);
3988
+ const oldParent =
3989
+ output?.versionOf ?? resolveVersionParentName(olds?.version);
3990
+ if (
3991
+ newParent !== undefined &&
3992
+ oldParent !== undefined &&
3993
+ newParent !== oldParent
3994
+ ) {
3995
+ return { action: "replace" };
3996
+ }
3997
+ }
2230
3998
  const oldWorkerName =
2231
3999
  output?.workerName ?? (yield* createWorkerName(id, olds?.name));
2232
4000
  // Auto-generated names are engine-owned: the deployed name stays
@@ -2241,15 +4009,23 @@ export const LiveWorkerProvider = () =>
2241
4009
  if (!output) {
2242
4010
  return;
2243
4011
  }
2244
- const newDomains = normalizeDomains(news.domain)
2245
- .map((h) => `https://${h}`)
2246
- .sort();
2247
- const oldDomains = normalizeStateDomains(output?.domains)
2248
- .filter((u) => !u.endsWith(".workers.dev"))
2249
- .sort();
4012
+ // Compare the full domain config — name, aliases (ordered: they
4013
+ // drive `urls` order), and redirects — against persisted state
4014
+ // (with legacy `domains`-list state migrated on the fly).
4015
+ const newDomainConfig = yield* resolveWorkerDomain(news.domain);
4016
+ const oldDomainConfig = stateWorkerDomain(output);
4017
+ const domainKey = (d: ResolvedWorkerDomain | undefined) =>
4018
+ d === undefined
4019
+ ? ""
4020
+ : JSON.stringify([d.name, d.aliases, [...d.redirects].sort()]);
4021
+ // An omitted `domain` unmanages the surface (#942): reconcile
4022
+ // carries previously-observed custom domains forward in state, so
4023
+ // comparing the empty desired config against those would report a
4024
+ // dirty plan on every deploy, forever. Only a declared `domain`
4025
+ // (including `null`, the explicit detach-all) participates.
2250
4026
  const domainsChanged =
2251
- newDomains.length !== oldDomains.length ||
2252
- newDomains.some((d, i) => d !== oldDomains[i]);
4027
+ news.domain !== undefined &&
4028
+ domainKey(newDomainConfig) !== domainKey(oldDomainConfig);
2253
4029
  const newCrons = normalizeCrons([
2254
4030
  ...(Array.isArray(newBindings)
2255
4031
  ? getCronBindings(
@@ -2269,25 +4045,30 @@ export const LiveWorkerProvider = () =>
2269
4045
  const routesChanged =
2270
4046
  newRouteKeys.length !== oldRouteKeys.length ||
2271
4047
  newRouteKeys.some((key, index) => key !== oldRouteKeys[index]);
2272
- // `url` is `domains[0]`: the first custom domain in user order if
2273
- // any, otherwise the workers.dev URL (derived from the stable
2274
- // worker name + account subdomain). It's stable across this update
2275
- // exactly when that first domain is unchanged — which is NOT the
2276
- // same as "the domain set is unchanged": adding a second custom
2277
- // domain leaves `url` put, while reordering changes it even though
2278
- // the set is equal. Compute the resulting `url` and carry it
2279
- // forward as a stable only when it matches the old one, so
2280
- // downstream resources that reference `worker.url` (e.g. a GitHub
2281
- // Webhook delivery URL built via `Output.interpolate`) resolve it
2282
- // to a concrete value during planning instead of an unresolved
2283
- // Output — otherwise every worker update spuriously re-updates them.
2284
- const newCustomDomains = normalizeDomains(news.domain);
2285
- const newUrl =
2286
- newCustomDomains.length > 0
2287
- ? `https://${newCustomDomains[0]}`
2288
- : news.url !== false
2289
- ? normalizeStateDomains(output.domains).find((u) =>
2290
- u.endsWith(".workers.dev"),
4048
+ // `url` is `urls[0]`: the canonical custom domain when one is
4049
+ // configured (or carried forward from state when the surface is
4050
+ // unmanaged, #942/#975), else the stable workers.dev URL. It's
4051
+ // stable across this update exactly when the recomputed value
4052
+ // matches what's deployed. Carry it forward as a stable only
4053
+ // then, so downstream resources that reference `worker.url`
4054
+ // (e.g. a GitHub Webhook delivery URL built via
4055
+ // `Output.interpolate`) resolve it to a concrete value during
4056
+ // planning instead of an unresolved Output — otherwise every
4057
+ // worker update spuriously re-updates them. Previews-only mode
4058
+ // and version workers derive `url` from version preview URLs —
4059
+ // never assumed stable here.
4060
+ const newWorkersDev = resolveWorkersDev(news.workersDev);
4061
+ const effectiveDomainConfig =
4062
+ news.domain !== undefined ? newDomainConfig : oldDomainConfig;
4063
+ const newUrl = newIsVersion
4064
+ ? undefined
4065
+ : effectiveDomainConfig !== undefined
4066
+ ? `https://${effectiveDomainConfig.name}`
4067
+ : newWorkersDev.enabled
4068
+ ? (output.urls ?? []).find(
4069
+ (u) =>
4070
+ isWorkersDevHostname(urlHostname(u)) &&
4071
+ urlHostname(u).startsWith(`${workerName}.`),
2291
4072
  )
2292
4073
  : undefined;
2293
4074
  const urlStable = newUrl !== undefined && newUrl === output.url;
@@ -2356,6 +4137,32 @@ export const LiveWorkerProvider = () =>
2356
4137
  precreate: Effect.fn(function* ({ id, news, session, bindings }) {
2357
4138
  const { accountId } = yield* yield* CloudflareEnvironment;
2358
4139
  const name = yield* createWorkerName(id, news.name);
4140
+ // A version worker uploads to its parent's script during
4141
+ // reconcile; pre-creating a placeholder script under this
4142
+ // resource's own generated name would leave a stray script behind.
4143
+ // Version workers can't host Durable Object / Workflow classes, so
4144
+ // no placeholder is needed for circular bindings either. (`news`
4145
+ // is raw here — `version.parent` may be an unresolved ref, but its
4146
+ // *presence* is statically known.)
4147
+ if (news.version?.parent != null) {
4148
+ yield* Effect.logInfo(
4149
+ `Cloudflare Worker precreate: skipping stub for version worker ${id}`,
4150
+ );
4151
+ return {
4152
+ workerId: name,
4153
+ workerName: name,
4154
+ namespace: undefined,
4155
+ logpush: undefined,
4156
+ url: undefined,
4157
+ tags: undefined,
4158
+ durableObjectNamespaces: {},
4159
+ accountId,
4160
+ urls: [],
4161
+ domain: undefined,
4162
+ routes: [],
4163
+ crons: [],
4164
+ } satisfies Worker["Attributes"];
4165
+ }
2359
4166
  // A Workers for Platforms user worker can't be pre-created: precreate
2360
4167
  // runs on raw, *unresolved* props (so resources in a dependency cycle
2361
4168
  // can signal early), meaning a `namespace` that references the
@@ -2378,7 +4185,8 @@ export const LiveWorkerProvider = () =>
2378
4185
  tags: undefined,
2379
4186
  durableObjectNamespaces: {},
2380
4187
  accountId,
2381
- domains: [],
4188
+ urls: [],
4189
+ domain: undefined,
2382
4190
  routes: [],
2383
4191
  crons: [],
2384
4192
  } satisfies Worker["Attributes"];
@@ -2473,6 +4281,7 @@ export const LiveWorkerProvider = () =>
2473
4281
  );
2474
4282
  } else {
2475
4283
  yield* session.note("Pre-creating worker...");
4284
+ const compatibility = getCompatibility(news);
2476
4285
  const mainModule = "main.js";
2477
4286
  const placeholderScript = `${doClasses.length > 0 ? 'import { DurableObject } from "cloudflare:workers";\n\n' : ""}export default { fetch() { return new Response("Alchemy worker is being deployed...") } };\n${doClasses
2478
4287
  .map(
@@ -2494,7 +4303,11 @@ export const LiveWorkerProvider = () =>
2494
4303
  className,
2495
4304
  }))
2496
4305
  : undefined,
2497
- ...getCompatibility(news),
4306
+ // Spreading `getCompatibility` here would set `date`/`flags`,
4307
+ // which are not metadata keys — the placeholder would upload
4308
+ // with no compatibility date or flags at all.
4309
+ compatibilityDate: compatibility.date,
4310
+ compatibilityFlags: compatibility.flags,
2498
4311
  containers,
2499
4312
  migrations:
2500
4313
  doClasses.length > 0
@@ -2568,7 +4381,8 @@ export const LiveWorkerProvider = () =>
2568
4381
  tags: existingSettings?.tags ?? tags,
2569
4382
  durableObjectNamespaces,
2570
4383
  accountId,
2571
- domains: [],
4384
+ urls: [],
4385
+ domain: undefined,
2572
4386
  routes: [],
2573
4387
  crons: [],
2574
4388
  } satisfies Worker["Attributes"];
@@ -2576,6 +4390,34 @@ export const LiveWorkerProvider = () =>
2576
4390
  read: Effect.fn(
2577
4391
  function* ({ id, output, olds }) {
2578
4392
  const { accountId } = yield* yield* CloudflareEnvironment;
4393
+ // Version workers don't own a script — their `workerName` is the
4394
+ // *parent's* script, so the normal read below would hydrate (and
4395
+ // potentially brand as adoptable) a resource we don't own. The
4396
+ // version itself is immutable; all we verify is that it still
4397
+ // exists on the parent.
4398
+ if (
4399
+ output?.versionOf !== undefined ||
4400
+ olds?.version?.parent != null
4401
+ ) {
4402
+ if (output?.versionOf === undefined || !output.versionId) {
4403
+ // No recorded version (e.g. state was written before the
4404
+ // upload completed) — nothing to observe; reconcile will
4405
+ // upload a fresh version.
4406
+ return undefined;
4407
+ }
4408
+ return yield* workers
4409
+ .getScriptVersion({
4410
+ accountId,
4411
+ scriptName: output.versionOf,
4412
+ versionId: output.versionId,
4413
+ })
4414
+ .pipe(
4415
+ Effect.map(() => output),
4416
+ Effect.catchTag(["WorkerNotFound", "VersionNotFound"], () =>
4417
+ Effect.succeed(undefined),
4418
+ ),
4419
+ );
4420
+ }
2579
4421
  const workerName =
2580
4422
  output?.workerName ?? (yield* createWorkerName(id, olds?.name));
2581
4423
  const dispatchNamespace =
@@ -2605,7 +4447,8 @@ export const LiveWorkerProvider = () =>
2605
4447
  url: undefined,
2606
4448
  tags: settings.tags ?? undefined,
2607
4449
  durableObjectNamespaces: getDurableObjects(settings.bindings),
2608
- domains: [],
4450
+ urls: [],
4451
+ domain: undefined,
2609
4452
  routes: [],
2610
4453
  crons: [],
2611
4454
  } satisfies Worker["Attributes"];
@@ -2622,6 +4465,18 @@ export const LiveWorkerProvider = () =>
2622
4465
  // `WorkerNotFound` if the script doesn't exist, which the
2623
4466
  // surrounding `Effect.catchTag` turns into `undefined` — that's
2624
4467
  // all the existence check we need.
4468
+ //
4469
+ // Domain / route / cron observation is gated on whether Alchemy
4470
+ // manages that surface for this Worker (#926). A plain workers.dev
4471
+ // Worker otherwise pays for listDomains + zone route listings +
4472
+ // getScriptSchedule on every plan/deploy read. Route observation
4473
+ // is additionally scoped to the zones state already associates
4474
+ // with this Worker — see readWorkerRoutes. Empty-array props
4475
+ // (`domain: []`, `routes: []`, `crons: []`) still observe so we
4476
+ // can detect drift and converge deletions.
4477
+ const observeDomains = shouldObserveWorkerDomains(olds, output);
4478
+ const observeRoutes = shouldObserveWorkerRoutes(olds, output);
4479
+ const observeCrons = shouldObserveWorkerCrons(olds, output);
2625
4480
  const [subdomain, settings, domainsList, routesList] =
2626
4481
  yield* Effect.all(
2627
4482
  [
@@ -2633,41 +4488,73 @@ export const LiveWorkerProvider = () =>
2633
4488
  accountId,
2634
4489
  scriptName: workerName,
2635
4490
  }),
2636
- workers
2637
- .listDomains({
2638
- accountId,
2639
- service: workerName,
2640
- })
2641
- .pipe(Effect.map((r) => r.result ?? [])),
2642
- readWorkerRoutes(workerName),
4491
+ observeDomains
4492
+ ? workers
4493
+ .listDomains({
4494
+ accountId,
4495
+ service: workerName,
4496
+ })
4497
+ .pipe(Effect.map((r) => r.result ?? []))
4498
+ : Effect.succeed(
4499
+ [] as workers.ListDomainsResponse["result"],
4500
+ ),
4501
+ observeRoutes
4502
+ ? readWorkerRoutes(workerName, output?.routes)
4503
+ : Effect.succeed([] as Worker["Attributes"]["routes"]),
2643
4504
  ],
2644
- { concurrency: "unbounded" },
4505
+ // Bound concurrency so a single Worker read doesn't stampede
4506
+ // the account alongside every other Worker's lifecycle calls.
4507
+ { concurrency: 1 },
2645
4508
  );
2646
- // Preserve the order the user provided in `olds.domain`. The
2647
- // Cloudflare API returns domains in non-deterministic order,
2648
- // which would cause downstream `worker.domains[0]` reads to flip
2649
- // between deploys. Drift (domains we don't know about) is
2650
- // appended after the user-ordered ones.
2651
- const userOrder = normalizeDomains(olds?.domain);
2652
- const orderedHostnames = [
2653
- ...userOrder.flatMap(
2654
- (h) =>
2655
- domainsList.find((d) => d.hostname === h)?.hostname ?? [],
2656
- ),
2657
- ...domainsList.flatMap((d) =>
2658
- d.hostname && !userOrder.includes(d.hostname)
2659
- ? [d.hostname]
2660
- : [],
2661
- ),
4509
+ // Classify the observed hostnames using the declared config
4510
+ // (`olds.domain`): the canonical name and each alias/redirect
4511
+ // keep their declared role while still attached; drift (attached
4512
+ // hostnames we don't know about) is appended to the aliases. The
4513
+ // Cloudflare API returns domains in non-deterministic order, so
4514
+ // the declared order is what keeps `url`/`urls` from flipping
4515
+ // between reads.
4516
+ const declared = yield* resolveWorkerDomain(olds?.domain).pipe(
4517
+ Effect.orElseSucceed(() => undefined),
4518
+ );
4519
+ const observed = new Set(
4520
+ domainsList.flatMap((d) => (d.hostname ? [d.hostname] : [])),
4521
+ );
4522
+ const keptName =
4523
+ declared !== undefined && observed.has(declared.name)
4524
+ ? declared.name
4525
+ : undefined;
4526
+ const keptAliases =
4527
+ declared?.aliases.filter((h) => observed.has(h)) ?? [];
4528
+ const keptRedirects =
4529
+ declared?.redirects.filter((h) => observed.has(h)) ?? [];
4530
+ const classified = new Set([
4531
+ ...(keptName ? [keptName] : []),
4532
+ ...keptAliases,
4533
+ ...keptRedirects,
4534
+ ]);
4535
+ const drift = [...observed].filter((h) => !classified.has(h));
4536
+ const serving = [
4537
+ ...(keptName ? [keptName] : []),
4538
+ ...keptAliases,
4539
+ ...drift,
2662
4540
  ];
2663
- const workersDevUrl = subdomain.enabled
2664
- ? `https://${workerName}.${yield* getAccountSubdomain(accountId)}.workers.dev`
2665
- : undefined;
2666
- const domains = [
2667
- ...orderedHostnames.map((h) => `https://${h}`),
2668
- ...(workersDevUrl ? [workersDevUrl] : []),
4541
+ const observedDomain =
4542
+ serving.length > 0
4543
+ ? {
4544
+ name: serving[0],
4545
+ aliases: serving.slice(1),
4546
+ redirects: keptRedirects,
4547
+ }
4548
+ : undefined;
4549
+ const urls = [
4550
+ ...serving.map((h) => `https://${h}`),
4551
+ ...(subdomain.enabled
4552
+ ? [
4553
+ `https://${workerName}.${yield* getAccountSubdomain(accountId)}.workers.dev`,
4554
+ ]
4555
+ : []),
2669
4556
  ];
2670
- const crons = yield* getWorkerCrons(workerName);
4557
+ const crons = observeCrons ? yield* getWorkerCrons(workerName) : [];
2671
4558
  yield* Effect.logInfo(
2672
4559
  `Cloudflare Worker read: found ${workerName}`,
2673
4560
  );
@@ -2677,12 +4564,17 @@ export const LiveWorkerProvider = () =>
2677
4564
  workerName,
2678
4565
  namespace: undefined,
2679
4566
  logpush: settings.logpush ?? undefined,
2680
- url: domains[0],
4567
+ url: urls[0],
4568
+ urls,
4569
+ domain: observedDomain,
2681
4570
  tags: settings.tags ?? undefined,
2682
4571
  durableObjectNamespaces: getDurableObjects(settings.bindings),
2683
- domains,
2684
4572
  routes: routesList,
2685
4573
  crons,
4574
+ // Rule placement is provider-managed state, not observed here
4575
+ // (a getPhas call per known zone on every read); carry the
4576
+ // cleanup list forward like any other stable cache.
4577
+ affinityZoneIds: output?.affinityZoneIds,
2686
4578
  } satisfies Worker["Attributes"];
2687
4579
 
2688
4580
  // Centralized ownership decision: the engine routes `read`'s
@@ -2723,6 +4615,12 @@ export const LiveWorkerProvider = () =>
2723
4615
  output,
2724
4616
  session,
2725
4617
  }) {
4618
+ // A version worker uploads an immutable version to its parent's
4619
+ // script instead of owning a script of its own — none of the
4620
+ // script-level observation below applies.
4621
+ if (news.version?.parent != null) {
4622
+ return yield* putWorkerVersion(id, news, bindings, session, output);
4623
+ }
2726
4624
  const { accountId } = yield* yield* CloudflareEnvironment;
2727
4625
  const name =
2728
4626
  output?.workerName ?? (yield* createWorkerName(id, news.name));
@@ -2791,6 +4689,70 @@ export const LiveWorkerProvider = () =>
2791
4689
  );
2792
4690
  }),
2793
4691
  delete: Effect.fn(function* ({ output }) {
4692
+ // Version workers own a version, not the script — `workerName` is
4693
+ // the *parent's* script and must never be deleted here. Versions
4694
+ // can't be deleted through the API (they age out of Cloudflare's
4695
+ // retention window); all we clean up is live traffic: if the
4696
+ // current deployment routes traffic to our version, restore 100%
4697
+ // to the other version in the split.
4698
+ if (output.versionOf !== undefined) {
4699
+ // Remove our affinity rules from the parent's zones — the
4700
+ // canary owned them, and with the canary gone there is no
4701
+ // split left to pin. Best-effort: a zone we can no longer
4702
+ // touch must not fail the destroy.
4703
+ if (output.affinityZoneIds?.length) {
4704
+ yield* reconcileAffinityRules({
4705
+ scriptName: output.versionOf,
4706
+ desired: undefined,
4707
+ previousZoneIds: output.affinityZoneIds,
4708
+ }).pipe(Effect.catch(() => Effect.void));
4709
+ }
4710
+ if (!output.versionId) return;
4711
+ yield* Effect.logInfo(
4712
+ `Cloudflare Worker delete: releasing version ${output.versionId} of ${output.versionOf}`,
4713
+ );
4714
+ const { deployments } = yield* workers
4715
+ .listScriptDeployments({
4716
+ accountId: output.accountId,
4717
+ scriptName: output.versionOf,
4718
+ })
4719
+ .pipe(
4720
+ Effect.catchTag("WorkerNotFound", () =>
4721
+ Effect.succeed({
4722
+ deployments:
4723
+ [] as workers.ListScriptDeploymentsResponse["deployments"],
4724
+ }),
4725
+ ),
4726
+ );
4727
+ const latest = deployments[0];
4728
+ if (
4729
+ !latest?.versions.some((v) => v.versionId === output.versionId)
4730
+ ) {
4731
+ // Not part of the live deployment — the version just ages out.
4732
+ return;
4733
+ }
4734
+ const stable = latest.versions
4735
+ .filter((v) => v.versionId !== output.versionId)
4736
+ .sort((a, b) => b.percentage - a.percentage)[0];
4737
+ if (!stable) {
4738
+ // Our version holds 100% of traffic; there is no other version
4739
+ // to restore. Deleting the resource must not take the parent's
4740
+ // script down — leave the deployment in place.
4741
+ yield* Effect.logWarning(
4742
+ `Cloudflare Worker delete: version ${output.versionId} serves 100% of '${output.versionOf}' traffic; leaving the deployment as-is. Re-deploy the parent to move off it.`,
4743
+ );
4744
+ return;
4745
+ }
4746
+ yield* workers
4747
+ .createScriptDeployment({
4748
+ accountId: output.accountId,
4749
+ scriptName: output.versionOf,
4750
+ strategy: "percentage",
4751
+ versions: [{ versionId: stable.versionId, percentage: 100 }],
4752
+ })
4753
+ .pipe(Effect.catchTag("WorkerNotFound", () => Effect.void));
4754
+ return;
4755
+ }
2794
4756
  yield* Effect.logInfo(
2795
4757
  `Cloudflare Worker delete: deleting ${output.workerName}`,
2796
4758
  );
@@ -2825,6 +4787,36 @@ export const LiveWorkerProvider = () =>
2825
4787
  Effect.map((r) => r.result ?? []),
2826
4788
  Effect.catch(() => Effect.succeed([])),
2827
4789
  );
4790
+ // Remove our redirect rules from each affected zone's dynamic
4791
+ // redirect entrypoint *before* detaching the domains — the live
4792
+ // domain list is what resolves each redirect hostname's zone.
4793
+ // Reconciling with an empty domain config removes exactly the
4794
+ // rules tagged `alchemy:worker:<script>:redirect:*`, leaving
4795
+ // everything else in the shared entrypoint untouched.
4796
+ // Remove our version-affinity rules from every zone state says
4797
+ // holds them. Best-effort, like the redirect rules below.
4798
+ if (output.affinityZoneIds?.length) {
4799
+ yield* reconcileAffinityRules({
4800
+ scriptName: output.workerName,
4801
+ desired: undefined,
4802
+ previousZoneIds: output.affinityZoneIds,
4803
+ }).pipe(Effect.catch(() => Effect.void));
4804
+ }
4805
+ const redirects = stateWorkerDomain(output)?.redirects ?? [];
4806
+ if (redirects.length > 0) {
4807
+ yield* reconcileRedirectRules({
4808
+ scriptName: output.workerName,
4809
+ domain: undefined,
4810
+ zoneIdByHostname: new Map(
4811
+ liveDomains.flatMap((d) =>
4812
+ d.hostname && d.zoneId
4813
+ ? [[d.hostname, d.zoneId] as const]
4814
+ : [],
4815
+ ),
4816
+ ),
4817
+ previousRedirects: redirects,
4818
+ }).pipe(Effect.catch(() => Effect.void));
4819
+ }
2828
4820
  if (liveDomains.length) {
2829
4821
  yield* Effect.all(
2830
4822
  liveDomains.flatMap((d) =>