alchemy 2.0.0-beta.44 → 2.0.0-beta.46

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 (443) hide show
  1. package/bin/exec.js +67 -8
  2. package/bin/exec.js.map +1 -1
  3. package/lib/AWS/Lambda/Function.d.ts +17 -0
  4. package/lib/AWS/Lambda/Function.d.ts.map +1 -1
  5. package/lib/AWS/Lambda/Function.js +61 -16
  6. package/lib/AWS/Lambda/Function.js.map +1 -1
  7. package/lib/AWS/Providers.d.ts +2 -1
  8. package/lib/AWS/Providers.d.ts.map +1 -1
  9. package/lib/AWS/Providers.js +3 -1
  10. package/lib/AWS/Providers.js.map +1 -1
  11. package/lib/AlchemyContext.d.ts +0 -1
  12. package/lib/AlchemyContext.d.ts.map +1 -1
  13. package/lib/AlchemyContext.js.map +1 -1
  14. package/lib/Apply.js +17 -1
  15. package/lib/Apply.js.map +1 -1
  16. package/lib/Auth/AuthProvider.d.ts.map +1 -1
  17. package/lib/Auth/AuthProvider.js +24 -6
  18. package/lib/Auth/AuthProvider.js.map +1 -1
  19. package/lib/Auth/Credentials.d.ts.map +1 -1
  20. package/lib/Auth/Credentials.js +3 -3
  21. package/lib/Auth/Credentials.js.map +1 -1
  22. package/lib/Cli/commands/_shared.d.ts +13 -1
  23. package/lib/Cli/commands/_shared.d.ts.map +1 -1
  24. package/lib/Cli/commands/_shared.js +33 -2
  25. package/lib/Cli/commands/_shared.js.map +1 -1
  26. package/lib/Cli/commands/cloudflare.d.ts.map +1 -1
  27. package/lib/Cli/commands/cloudflare.js +5 -1
  28. package/lib/Cli/commands/cloudflare.js.map +1 -1
  29. package/lib/Cli/commands/dev.d.ts.map +1 -1
  30. package/lib/Cli/commands/dev.js +10 -1
  31. package/lib/Cli/commands/dev.js.map +1 -1
  32. package/lib/Cli/commands/login.d.ts.map +1 -1
  33. package/lib/Cli/commands/login.js +19 -5
  34. package/lib/Cli/commands/login.js.map +1 -1
  35. package/lib/Cli/commands/profile.d.ts.map +1 -1
  36. package/lib/Cli/commands/profile.js +7 -27
  37. package/lib/Cli/commands/profile.js.map +1 -1
  38. package/lib/Cli/main.d.ts.map +1 -1
  39. package/lib/Cli/main.js +0 -2
  40. package/lib/Cli/main.js.map +1 -1
  41. package/lib/Cloudflare/AiGateway/AiGateway.d.ts +0 -2
  42. package/lib/Cloudflare/AiGateway/AiGateway.d.ts.map +1 -1
  43. package/lib/Cloudflare/AiGateway/AiGateway.js +2 -4
  44. package/lib/Cloudflare/AiGateway/AiGateway.js.map +1 -1
  45. package/lib/Cloudflare/BrowserRendering/BrowserRendering.d.ts +116 -0
  46. package/lib/Cloudflare/BrowserRendering/BrowserRendering.d.ts.map +1 -0
  47. package/lib/Cloudflare/BrowserRendering/BrowserRendering.js +92 -0
  48. package/lib/Cloudflare/BrowserRendering/BrowserRendering.js.map +1 -0
  49. package/lib/Cloudflare/BrowserRendering/BrowserRenderingBinding.d.ts +55 -0
  50. package/lib/Cloudflare/BrowserRendering/BrowserRenderingBinding.d.ts.map +1 -0
  51. package/lib/Cloudflare/BrowserRendering/BrowserRenderingBinding.js +55 -0
  52. package/lib/Cloudflare/BrowserRendering/BrowserRenderingBinding.js.map +1 -0
  53. package/lib/Cloudflare/BrowserRendering/index.d.ts +3 -0
  54. package/lib/Cloudflare/BrowserRendering/index.d.ts.map +1 -0
  55. package/lib/Cloudflare/BrowserRendering/index.js +3 -0
  56. package/lib/Cloudflare/BrowserRendering/index.js.map +1 -0
  57. package/lib/Cloudflare/Container/Container.d.ts +3 -3
  58. package/lib/Cloudflare/Container/Container.js +3 -3
  59. package/lib/Cloudflare/Container/StartContainer.d.ts.map +1 -1
  60. package/lib/Cloudflare/Container/StartContainer.js.map +1 -1
  61. package/lib/Cloudflare/D1/D1Database.d.ts +1 -0
  62. package/lib/Cloudflare/D1/D1Database.d.ts.map +1 -1
  63. package/lib/Cloudflare/D1/D1Database.js +2 -1
  64. package/lib/Cloudflare/D1/D1Database.js.map +1 -1
  65. package/lib/Cloudflare/D1/D1DatabaseBinding.js +1 -1
  66. package/lib/Cloudflare/D1/D1DatabaseBinding.js.map +1 -1
  67. package/lib/Cloudflare/Fetcher.d.ts.map +1 -1
  68. package/lib/Cloudflare/Fetcher.js +0 -2
  69. package/lib/Cloudflare/Fetcher.js.map +1 -1
  70. package/lib/Cloudflare/Hyperdrive/Hyperdrive.d.ts +1 -0
  71. package/lib/Cloudflare/Hyperdrive/Hyperdrive.d.ts.map +1 -1
  72. package/lib/Cloudflare/Hyperdrive/Hyperdrive.js +3 -0
  73. package/lib/Cloudflare/Hyperdrive/Hyperdrive.js.map +1 -1
  74. package/lib/Cloudflare/Hyperdrive/HyperdriveBinding.d.ts +3 -2
  75. package/lib/Cloudflare/Hyperdrive/HyperdriveBinding.d.ts.map +1 -1
  76. package/lib/Cloudflare/Hyperdrive/HyperdriveBinding.js +32 -33
  77. package/lib/Cloudflare/Hyperdrive/HyperdriveBinding.js.map +1 -1
  78. package/lib/Cloudflare/KV/KVNamespace.d.ts +1 -0
  79. package/lib/Cloudflare/KV/KVNamespace.d.ts.map +1 -1
  80. package/lib/Cloudflare/KV/KVNamespace.js +2 -0
  81. package/lib/Cloudflare/KV/KVNamespace.js.map +1 -1
  82. package/lib/Cloudflare/Local.js +1 -1
  83. package/lib/Cloudflare/Local.js.map +1 -1
  84. package/lib/Cloudflare/Providers.d.ts +2 -1
  85. package/lib/Cloudflare/Providers.d.ts.map +1 -1
  86. package/lib/Cloudflare/Providers.js +25 -17
  87. package/lib/Cloudflare/Providers.js.map +1 -1
  88. package/lib/Cloudflare/Queue/Queue.d.ts +1 -0
  89. package/lib/Cloudflare/Queue/Queue.d.ts.map +1 -1
  90. package/lib/Cloudflare/Queue/Queue.js +1 -0
  91. package/lib/Cloudflare/Queue/Queue.js.map +1 -1
  92. package/lib/Cloudflare/Queue/QueueConsumer.d.ts +2 -1
  93. package/lib/Cloudflare/Queue/QueueConsumer.d.ts.map +1 -1
  94. package/lib/Cloudflare/Queue/QueueConsumer.js +10 -3
  95. package/lib/Cloudflare/Queue/QueueConsumer.js.map +1 -1
  96. package/lib/Cloudflare/Queue/QueueEventSource.d.ts +30 -4
  97. package/lib/Cloudflare/Queue/QueueEventSource.d.ts.map +1 -1
  98. package/lib/Cloudflare/Queue/QueueEventSource.js +22 -1
  99. package/lib/Cloudflare/Queue/QueueEventSource.js.map +1 -1
  100. package/lib/Cloudflare/R2/R2Bucket.d.ts +1 -0
  101. package/lib/Cloudflare/R2/R2Bucket.d.ts.map +1 -1
  102. package/lib/Cloudflare/R2/R2Bucket.js +18 -0
  103. package/lib/Cloudflare/R2/R2Bucket.js.map +1 -1
  104. package/lib/Cloudflare/SecretsStore/Secret.d.ts.map +1 -1
  105. package/lib/Cloudflare/SecretsStore/Secret.js +13 -34
  106. package/lib/Cloudflare/SecretsStore/Secret.js.map +1 -1
  107. package/lib/Cloudflare/SecretsStore/SecretBinding.d.ts +3 -2
  108. package/lib/Cloudflare/SecretsStore/SecretBinding.d.ts.map +1 -1
  109. package/lib/Cloudflare/SecretsStore/SecretBinding.js +3 -2
  110. package/lib/Cloudflare/SecretsStore/SecretBinding.js.map +1 -1
  111. package/lib/Cloudflare/StateStore/Api.d.ts +5 -13
  112. package/lib/Cloudflare/StateStore/Api.d.ts.map +1 -1
  113. package/lib/Cloudflare/StateStore/Api.js +14 -8
  114. package/lib/Cloudflare/StateStore/Api.js.map +1 -1
  115. package/lib/Cloudflare/StateStore/State.d.ts +11 -65
  116. package/lib/Cloudflare/StateStore/State.d.ts.map +1 -1
  117. package/lib/Cloudflare/StateStore/State.js +320 -343
  118. package/lib/Cloudflare/StateStore/State.js.map +1 -1
  119. package/lib/Cloudflare/StateStore/Store.d.ts.map +1 -1
  120. package/lib/Cloudflare/StateStore/Store.js +21 -31
  121. package/lib/Cloudflare/StateStore/Store.js.map +1 -1
  122. package/lib/Cloudflare/Vectorize/VectorizeIndex.d.ts +114 -0
  123. package/lib/Cloudflare/Vectorize/VectorizeIndex.d.ts.map +1 -0
  124. package/lib/Cloudflare/Vectorize/VectorizeIndex.js +157 -0
  125. package/lib/Cloudflare/Vectorize/VectorizeIndex.js.map +1 -0
  126. package/lib/Cloudflare/Vectorize/VectorizeIndexBinding.d.ts +38 -0
  127. package/lib/Cloudflare/Vectorize/VectorizeIndexBinding.d.ts.map +1 -0
  128. package/lib/Cloudflare/Vectorize/VectorizeIndexBinding.js +44 -0
  129. package/lib/Cloudflare/Vectorize/VectorizeIndexBinding.js.map +1 -0
  130. package/lib/Cloudflare/Vectorize/VectorizeMetadataIndex.d.ts +72 -0
  131. package/lib/Cloudflare/Vectorize/VectorizeMetadataIndex.d.ts.map +1 -0
  132. package/lib/Cloudflare/Vectorize/VectorizeMetadataIndex.js +131 -0
  133. package/lib/Cloudflare/Vectorize/VectorizeMetadataIndex.js.map +1 -0
  134. package/lib/Cloudflare/Vectorize/index.d.ts +4 -0
  135. package/lib/Cloudflare/Vectorize/index.d.ts.map +1 -0
  136. package/lib/Cloudflare/Vectorize/index.js +4 -0
  137. package/lib/Cloudflare/Vectorize/index.js.map +1 -0
  138. package/lib/Cloudflare/VpcService/VpcService.d.ts +2 -2
  139. package/lib/Cloudflare/VpcService/VpcService.d.ts.map +1 -1
  140. package/lib/Cloudflare/Website/StaticSite.d.ts +5 -5
  141. package/lib/Cloudflare/Website/StaticSite.d.ts.map +1 -1
  142. package/lib/Cloudflare/Website/StaticSite.js +3 -0
  143. package/lib/Cloudflare/Website/StaticSite.js.map +1 -1
  144. package/lib/Cloudflare/Website/Vite.d.ts +5 -5
  145. package/lib/Cloudflare/Website/Vite.d.ts.map +1 -1
  146. package/lib/Cloudflare/Website/Vite.js.map +1 -1
  147. package/lib/Cloudflare/Workers/Assets.d.ts.map +1 -1
  148. package/lib/Cloudflare/Workers/Assets.js +1 -1
  149. package/lib/Cloudflare/Workers/Assets.js.map +1 -1
  150. package/lib/Cloudflare/Workers/DurableObjectBridge.d.ts.map +1 -1
  151. package/lib/Cloudflare/Workers/DurableObjectBridge.js +4 -2
  152. package/lib/Cloudflare/Workers/DurableObjectBridge.js.map +1 -1
  153. package/lib/Cloudflare/Workers/DurableObjectNamespace.d.ts +183 -6
  154. package/lib/Cloudflare/Workers/DurableObjectNamespace.d.ts.map +1 -1
  155. package/lib/Cloudflare/Workers/DurableObjectNamespace.js +279 -76
  156. package/lib/Cloudflare/Workers/DurableObjectNamespace.js.map +1 -1
  157. package/lib/Cloudflare/Workers/HttpServer.d.ts +6 -4
  158. package/lib/Cloudflare/Workers/HttpServer.d.ts.map +1 -1
  159. package/lib/Cloudflare/Workers/HttpServer.js +20 -15
  160. package/lib/Cloudflare/Workers/HttpServer.js.map +1 -1
  161. package/lib/Cloudflare/Workers/InferEnv.d.ts +3 -2
  162. package/lib/Cloudflare/Workers/InferEnv.d.ts.map +1 -1
  163. package/lib/Cloudflare/Workers/LocalWorkerProvider.d.ts +4 -7
  164. package/lib/Cloudflare/Workers/LocalWorkerProvider.d.ts.map +1 -1
  165. package/lib/Cloudflare/Workers/LocalWorkerProvider.js +111 -66
  166. package/lib/Cloudflare/Workers/LocalWorkerProvider.js.map +1 -1
  167. package/lib/Cloudflare/Workers/Rpc.d.ts +16 -0
  168. package/lib/Cloudflare/Workers/Rpc.d.ts.map +1 -1
  169. package/lib/Cloudflare/Workers/Rpc.js +22 -0
  170. package/lib/Cloudflare/Workers/Rpc.js.map +1 -1
  171. package/lib/Cloudflare/Workers/RpcDurableObjectNamespace.d.ts +285 -0
  172. package/lib/Cloudflare/Workers/RpcDurableObjectNamespace.d.ts.map +1 -0
  173. package/lib/Cloudflare/Workers/RpcDurableObjectNamespace.js +288 -0
  174. package/lib/Cloudflare/Workers/RpcDurableObjectNamespace.js.map +1 -0
  175. package/lib/Cloudflare/Workers/RpcWorker.d.ts +305 -0
  176. package/lib/Cloudflare/Workers/RpcWorker.d.ts.map +1 -0
  177. package/lib/Cloudflare/Workers/RpcWorker.js +350 -0
  178. package/lib/Cloudflare/Workers/RpcWorker.js.map +1 -0
  179. package/lib/Cloudflare/Workers/Vite.d.ts +1 -1
  180. package/lib/Cloudflare/Workers/Vite.d.ts.map +1 -1
  181. package/lib/Cloudflare/Workers/Vite.js +2 -1
  182. package/lib/Cloudflare/Workers/Vite.js.map +1 -1
  183. package/lib/Cloudflare/Workers/Worker.d.ts +69 -24
  184. package/lib/Cloudflare/Workers/Worker.d.ts.map +1 -1
  185. package/lib/Cloudflare/Workers/Worker.js +168 -47
  186. package/lib/Cloudflare/Workers/Worker.js.map +1 -1
  187. package/lib/Cloudflare/Workers/WorkerAsyncBindings.d.ts +1 -1
  188. package/lib/Cloudflare/Workers/WorkerAsyncBindings.d.ts.map +1 -1
  189. package/lib/Cloudflare/Workers/WorkerAsyncBindings.js +166 -97
  190. package/lib/Cloudflare/Workers/WorkerAsyncBindings.js.map +1 -1
  191. package/lib/Cloudflare/Workers/WorkerBinding.d.ts +12 -23
  192. package/lib/Cloudflare/Workers/WorkerBinding.d.ts.map +1 -1
  193. package/lib/Cloudflare/Workers/WorkerBinding.js +3 -0
  194. package/lib/Cloudflare/Workers/WorkerBinding.js.map +1 -1
  195. package/lib/Cloudflare/Workers/WorkerBridge.d.ts.map +1 -1
  196. package/lib/Cloudflare/Workers/WorkerBridge.js +5 -2
  197. package/lib/Cloudflare/Workers/WorkerBridge.js.map +1 -1
  198. package/lib/Cloudflare/Workers/WorkerBundle.d.ts.map +1 -1
  199. package/lib/Cloudflare/Workers/WorkerBundle.js +4 -1
  200. package/lib/Cloudflare/Workers/WorkerBundle.js.map +1 -1
  201. package/lib/Cloudflare/Workers/WorkerRuntimeContext.d.ts.map +1 -1
  202. package/lib/Cloudflare/Workers/WorkerRuntimeContext.js +9 -10
  203. package/lib/Cloudflare/Workers/WorkerRuntimeContext.js.map +1 -1
  204. package/lib/Cloudflare/Workers/Workflow.d.ts +5 -3
  205. package/lib/Cloudflare/Workers/Workflow.d.ts.map +1 -1
  206. package/lib/Cloudflare/Workers/Workflow.js +19 -1
  207. package/lib/Cloudflare/Workers/Workflow.js.map +1 -1
  208. package/lib/Cloudflare/Workers/index.d.ts +2 -0
  209. package/lib/Cloudflare/Workers/index.d.ts.map +1 -1
  210. package/lib/Cloudflare/Workers/index.js +2 -0
  211. package/lib/Cloudflare/Workers/index.js.map +1 -1
  212. package/lib/Cloudflare/index.d.ts +2 -0
  213. package/lib/Cloudflare/index.d.ts.map +1 -1
  214. package/lib/Cloudflare/index.js +2 -0
  215. package/lib/Cloudflare/index.js.map +1 -1
  216. package/lib/Dependencies.d.ts +10 -0
  217. package/lib/Dependencies.d.ts.map +1 -0
  218. package/lib/Dependencies.js +1 -0
  219. package/lib/Dependencies.js.map +1 -0
  220. package/lib/Deploy.d.ts +2 -1
  221. package/lib/Deploy.d.ts.map +1 -1
  222. package/lib/Deploy.js +2 -2
  223. package/lib/Deploy.js.map +1 -1
  224. package/lib/Diff.d.ts +11 -0
  225. package/lib/Diff.d.ts.map +1 -1
  226. package/lib/Diff.js +11 -0
  227. package/lib/Diff.js.map +1 -1
  228. package/lib/Input.d.ts +2 -1
  229. package/lib/Input.d.ts.map +1 -1
  230. package/lib/KeyPair.d.ts +77 -0
  231. package/lib/KeyPair.d.ts.map +1 -0
  232. package/lib/KeyPair.js +102 -0
  233. package/lib/KeyPair.js.map +1 -0
  234. package/lib/Local/RpcProvider.d.ts +3 -1
  235. package/lib/Local/RpcProvider.d.ts.map +1 -1
  236. package/lib/Local/RpcProvider.js +28 -2
  237. package/lib/Local/RpcProvider.js.map +1 -1
  238. package/lib/Local/RpcServerEnvironment.js +1 -1
  239. package/lib/Local/RpcServerEnvironment.js.map +1 -1
  240. package/lib/Neon/Project.d.ts.map +1 -1
  241. package/lib/Neon/Project.js +14 -3
  242. package/lib/Neon/Project.js.map +1 -1
  243. package/lib/Output.d.ts.map +1 -1
  244. package/lib/Output.js +9 -2
  245. package/lib/Output.js.map +1 -1
  246. package/lib/Phase.d.ts +2 -1
  247. package/lib/Phase.d.ts.map +1 -1
  248. package/lib/Phase.js +6 -1
  249. package/lib/Phase.js.map +1 -1
  250. package/lib/Plan.d.ts.map +1 -1
  251. package/lib/Plan.js +14 -3
  252. package/lib/Plan.js.map +1 -1
  253. package/lib/Platform.d.ts +8 -6
  254. package/lib/Platform.d.ts.map +1 -1
  255. package/lib/Platform.js +33 -3
  256. package/lib/Platform.js.map +1 -1
  257. package/lib/Random.d.ts.map +1 -1
  258. package/lib/Random.js +9 -14
  259. package/lib/Random.js.map +1 -1
  260. package/lib/RuntimeContext.d.ts +21 -1
  261. package/lib/RuntimeContext.d.ts.map +1 -1
  262. package/lib/RuntimeContext.js +12 -0
  263. package/lib/RuntimeContext.js.map +1 -1
  264. package/lib/Stack.d.ts +3 -3
  265. package/lib/Stack.d.ts.map +1 -1
  266. package/lib/Stack.js.map +1 -1
  267. package/lib/State/HttpStateApi.d.ts +2 -2
  268. package/lib/State/HttpStateStore.d.ts +4 -0
  269. package/lib/State/HttpStateStore.d.ts.map +1 -1
  270. package/lib/State/HttpStateStore.js +13 -0
  271. package/lib/State/HttpStateStore.js.map +1 -1
  272. package/lib/State/StateEncoding.d.ts +8 -0
  273. package/lib/State/StateEncoding.d.ts.map +1 -1
  274. package/lib/State/StateEncoding.js +55 -5
  275. package/lib/State/StateEncoding.js.map +1 -1
  276. package/lib/Test/Bun.d.ts.map +1 -1
  277. package/lib/Test/Bun.js +0 -2
  278. package/lib/Test/Bun.js.map +1 -1
  279. package/lib/Test/Vitest.d.ts.map +1 -1
  280. package/lib/Test/Vitest.js +0 -2
  281. package/lib/Test/Vitest.js.map +1 -1
  282. package/lib/Util/Duration.d.ts +14 -0
  283. package/lib/Util/Duration.d.ts.map +1 -0
  284. package/lib/Util/Duration.js +18 -0
  285. package/lib/Util/Duration.js.map +1 -0
  286. package/lib/Util/index.d.ts +0 -6
  287. package/lib/Util/index.d.ts.map +1 -1
  288. package/lib/Util/index.js +0 -6
  289. package/lib/Util/index.js.map +1 -1
  290. package/lib/Util/poll.d.ts +30 -0
  291. package/lib/Util/poll.d.ts.map +1 -0
  292. package/lib/Util/poll.js +26 -0
  293. package/lib/Util/poll.js.map +1 -0
  294. package/lib/Util/types.d.ts +3 -1
  295. package/lib/Util/types.d.ts.map +1 -1
  296. package/lib/Util/types.js +5 -1
  297. package/lib/Util/types.js.map +1 -1
  298. package/lib/index.d.ts +2 -3
  299. package/lib/index.d.ts.map +1 -1
  300. package/lib/index.js +1 -3
  301. package/lib/index.js.map +1 -1
  302. package/lib/tsconfig.test.tsbuildinfo +1 -1
  303. package/package.json +20 -19
  304. package/src/AWS/Lambda/Function.ts +87 -34
  305. package/src/AWS/Providers.ts +3 -0
  306. package/src/AlchemyContext.ts +0 -1
  307. package/src/Apply.ts +18 -1
  308. package/src/Auth/AuthProvider.ts +25 -5
  309. package/src/Auth/Credentials.ts +8 -7
  310. package/src/Cli/commands/_shared.ts +47 -4
  311. package/src/Cli/commands/cloudflare.ts +5 -3
  312. package/src/Cli/commands/dev.ts +13 -1
  313. package/src/Cli/commands/login.ts +20 -4
  314. package/src/Cli/commands/profile.ts +24 -34
  315. package/src/Cli/main.ts +0 -3
  316. package/src/Cloudflare/AiGateway/AiGateway.ts +2 -6
  317. package/src/Cloudflare/BrowserRendering/BrowserRendering.ts +132 -0
  318. package/src/Cloudflare/BrowserRendering/BrowserRenderingBinding.ts +137 -0
  319. package/src/Cloudflare/BrowserRendering/index.ts +11 -0
  320. package/src/Cloudflare/Container/Container.ts +3 -3
  321. package/src/Cloudflare/Container/StartContainer.ts +1 -2
  322. package/src/Cloudflare/D1/D1Database.ts +4 -1
  323. package/src/Cloudflare/D1/D1DatabaseBinding.ts +1 -1
  324. package/src/Cloudflare/Fetcher.ts +0 -2
  325. package/src/Cloudflare/Hyperdrive/Hyperdrive.ts +5 -0
  326. package/src/Cloudflare/Hyperdrive/HyperdriveBinding.ts +41 -43
  327. package/src/Cloudflare/KV/KVNamespace.ts +4 -0
  328. package/src/Cloudflare/Local.ts +1 -1
  329. package/src/Cloudflare/Providers.ts +32 -16
  330. package/src/Cloudflare/Queue/Queue.ts +3 -0
  331. package/src/Cloudflare/Queue/QueueConsumer.ts +21 -8
  332. package/src/Cloudflare/Queue/QueueEventSource.ts +33 -5
  333. package/src/Cloudflare/R2/R2Bucket.ts +26 -0
  334. package/src/Cloudflare/SecretsStore/Secret.ts +14 -41
  335. package/src/Cloudflare/SecretsStore/SecretBinding.ts +11 -11
  336. package/src/Cloudflare/StateStore/Api.ts +15 -15
  337. package/src/Cloudflare/StateStore/State.ts +468 -468
  338. package/src/Cloudflare/StateStore/Store.ts +27 -37
  339. package/src/Cloudflare/Vectorize/VectorizeIndex.ts +261 -0
  340. package/src/Cloudflare/Vectorize/VectorizeIndexBinding.ts +103 -0
  341. package/src/Cloudflare/Vectorize/VectorizeMetadataIndex.ts +208 -0
  342. package/src/Cloudflare/Vectorize/index.ts +3 -0
  343. package/src/Cloudflare/VpcService/VpcService.ts +2 -2
  344. package/src/Cloudflare/Website/StaticSite.ts +8 -10
  345. package/src/Cloudflare/Website/Vite.ts +4 -7
  346. package/src/Cloudflare/Workers/Assets.ts +1 -1
  347. package/src/Cloudflare/Workers/DurableObjectBridge.ts +7 -4
  348. package/src/Cloudflare/Workers/DurableObjectNamespace.ts +380 -121
  349. package/src/Cloudflare/Workers/HttpServer.ts +31 -25
  350. package/src/Cloudflare/Workers/InferEnv.ts +15 -8
  351. package/src/Cloudflare/Workers/LocalWorkerProvider.ts +139 -78
  352. package/src/Cloudflare/Workers/Rpc.ts +50 -0
  353. package/src/Cloudflare/Workers/RpcDurableObjectNamespace.ts +502 -0
  354. package/src/Cloudflare/Workers/RpcWorker.ts +562 -0
  355. package/src/Cloudflare/Workers/Vite.ts +2 -0
  356. package/src/Cloudflare/Workers/Worker.ts +274 -93
  357. package/src/Cloudflare/Workers/WorkerAsyncBindings.ts +165 -103
  358. package/src/Cloudflare/Workers/WorkerBinding.ts +12 -0
  359. package/src/Cloudflare/Workers/WorkerBridge.ts +7 -4
  360. package/src/Cloudflare/Workers/WorkerBundle.ts +4 -1
  361. package/src/Cloudflare/Workers/WorkerRuntimeContext.ts +8 -15
  362. package/src/Cloudflare/Workers/Workflow.ts +22 -3
  363. package/src/Cloudflare/Workers/index.ts +2 -0
  364. package/src/Cloudflare/index.ts +2 -0
  365. package/src/Dependencies.ts +10 -0
  366. package/src/Deploy.ts +3 -1
  367. package/src/Diff.ts +13 -0
  368. package/src/Input.ts +2 -0
  369. package/src/KeyPair.ts +146 -0
  370. package/src/Local/RpcProvider.ts +45 -1
  371. package/src/Local/RpcServerEnvironment.ts +1 -1
  372. package/src/Neon/Project.ts +16 -3
  373. package/src/Output.ts +8 -2
  374. package/src/Phase.ts +8 -0
  375. package/src/Plan.ts +16 -3
  376. package/src/Platform.ts +74 -27
  377. package/src/Random.ts +22 -27
  378. package/src/RuntimeContext.ts +23 -1
  379. package/src/Stack.ts +3 -3
  380. package/src/State/HttpStateStore.ts +28 -0
  381. package/src/State/StateEncoding.ts +58 -7
  382. package/src/Test/Bun.ts +0 -3
  383. package/src/Test/Vitest.ts +0 -3
  384. package/src/Util/Duration.ts +21 -0
  385. package/src/Util/index.ts +0 -6
  386. package/src/Util/poll.ts +47 -0
  387. package/src/Util/types.ts +9 -2
  388. package/src/index.ts +2 -3
  389. package/lib/Secret.d.ts +0 -50
  390. package/lib/Secret.d.ts.map +0 -1
  391. package/lib/Secret.js +0 -23
  392. package/lib/Secret.js.map +0 -1
  393. package/lib/Trait.d.ts +0 -43
  394. package/lib/Trait.d.ts.map +0 -1
  395. package/lib/Trait.js +0 -17
  396. package/lib/Trait.js.map +0 -1
  397. package/lib/Util/LocalhostDns.d.ts +0 -23
  398. package/lib/Util/LocalhostDns.d.ts.map +0 -1
  399. package/lib/Util/LocalhostDns.js +0 -46
  400. package/lib/Util/LocalhostDns.js.map +0 -1
  401. package/lib/Util/assert-never.d.ts +0 -12
  402. package/lib/Util/assert-never.d.ts.map +0 -1
  403. package/lib/Util/assert-never.js +0 -11
  404. package/lib/Util/assert-never.js.map +0 -1
  405. package/lib/Util/class.d.ts +0 -3
  406. package/lib/Util/class.d.ts.map +0 -1
  407. package/lib/Util/class.js +0 -1
  408. package/lib/Util/class.js.map +0 -1
  409. package/lib/Util/ignore.d.ts +0 -40
  410. package/lib/Util/ignore.d.ts.map +0 -1
  411. package/lib/Util/ignore.js +0 -591
  412. package/lib/Util/ignore.js.map +0 -1
  413. package/lib/Util/instance.d.ts +0 -4
  414. package/lib/Util/instance.d.ts.map +0 -1
  415. package/lib/Util/instance.js +0 -1
  416. package/lib/Util/instance.js.map +0 -1
  417. package/lib/Util/pointer.d.ts +0 -5
  418. package/lib/Util/pointer.d.ts.map +0 -1
  419. package/lib/Util/pointer.js +0 -1
  420. package/lib/Util/pointer.js.map +0 -1
  421. package/lib/Util/schema-to-type.d.ts +0 -42
  422. package/lib/Util/schema-to-type.d.ts.map +0 -1
  423. package/lib/Util/schema-to-type.js +0 -364
  424. package/lib/Util/schema-to-type.js.map +0 -1
  425. package/lib/Util/todo.d.ts +0 -3
  426. package/lib/Util/todo.d.ts.map +0 -1
  427. package/lib/Util/todo.js +0 -3
  428. package/lib/Util/todo.js.map +0 -1
  429. package/lib/Variable.d.ts +0 -29
  430. package/lib/Variable.d.ts.map +0 -1
  431. package/lib/Variable.js +0 -9
  432. package/lib/Variable.js.map +0 -1
  433. package/src/Secret.ts +0 -87
  434. package/src/Trait.ts +0 -105
  435. package/src/Util/LocalhostDns.ts +0 -52
  436. package/src/Util/assert-never.ts +0 -18
  437. package/src/Util/class.ts +0 -7
  438. package/src/Util/ignore.ts +0 -815
  439. package/src/Util/instance.ts +0 -9
  440. package/src/Util/pointer.ts +0 -5
  441. package/src/Util/schema-to-type.ts +0 -456
  442. package/src/Util/todo.ts +0 -4
  443. package/src/Variable.ts +0 -44
@@ -8,9 +8,9 @@ import * as HttpClient from "effect/unstable/http/HttpClient";
8
8
  import * as HttpClientRequest from "effect/unstable/http/HttpClientRequest";
9
9
  import * as HttpApiClient from "effect/unstable/httpapi/HttpApiClient";
10
10
  import crypto from "node:crypto";
11
- import * as Output from "../../Output.ts";
12
11
 
13
12
  import * as Config from "effect/Config";
13
+ import { isHttpClientError } from "effect/unstable/http/HttpClientError";
14
14
  import { adopt } from "../../AdoptPolicy.ts";
15
15
  import { AuthError } from "../../Auth/AuthProvider.ts";
16
16
  import {
@@ -20,16 +20,14 @@ import {
20
20
  import { ALCHEMY_PROFILE, ProfileLive } from "../../Auth/Profile.ts";
21
21
  import * as Cloudflare from "../../Cloudflare/Providers.ts";
22
22
  import { deploy } from "../../Deploy.ts";
23
+ import * as Output from "../../Output.ts";
23
24
  import * as Alchemy from "../../Stack.ts";
24
25
  import { StateApi } from "../../State/HttpStateApi.ts";
25
26
  import {
27
+ checkHttpStateStoreAuth,
26
28
  makeHttpStateStore,
27
29
  type HttpStateStoreCredentials,
28
30
  } from "../../State/HttpStateStore.ts";
29
- import * as CloudflareEnvironment from "../CloudflareEnvironment.ts";
30
- import * as Credentials from "../Credentials.ts";
31
-
32
- import { AlchemyContext } from "../../AlchemyContext.ts";
33
31
  import { makeLocalState } from "../../State/LocalState.ts";
34
32
  import { State, type StateService } from "../../State/State.ts";
35
33
  import {
@@ -39,94 +37,170 @@ import {
39
37
  import * as Clank from "../../Util/Clank.ts";
40
38
  import * as Access from "../Access.ts";
41
39
  import { CloudflareAuth } from "../Auth/AuthProvider.ts";
40
+ import * as CloudflareEnvironment from "../CloudflareEnvironment.ts";
41
+ import * as Credentials from "../Credentials.ts";
42
42
  import { EdgeSessionError, createEdgeSession } from "../EdgeSession.ts";
43
43
  import Api, { STATE_STORE_SCRIPT_NAME, STATE_STORE_VERSION } from "./Api.ts";
44
- import { AuthTokenSecretName, TokenValue } from "./Token.ts";
44
+ import { AuthToken, AuthTokenSecretName, TokenValue } from "./Token.ts";
45
+
46
+ const CI = Config.boolean("CI").pipe(Config.withDefault(false));
45
47
 
46
48
  /** Filename used for stored credentials under the profile directory. */
47
49
  const CREDENTIALS_FILE = "cloudflare-state-store";
48
50
 
49
- /**
50
- * SHA-256 hex digest of the Cloudflare account ID. Used as a stable
51
- * pseudonymous identifier on telemetry spans so the dashboard can
52
- * count distinct state-store deployments without leaking the raw
53
- * accountId. Mirrors the `alchemy.git.origin_hash` pattern in
54
- * `Telemetry/Attributes.ts`.
55
- */
56
- const hashAccountId = (accountId: string) =>
57
- Effect.sync(() =>
58
- crypto.createHash("sha256").update(accountId).digest("hex"),
59
- );
51
+ export const state = () =>
52
+ Layer.effect(
53
+ State,
54
+ Effect.gen(function* () {
55
+ const isCI = yield* CI;
56
+ const scriptName = STATE_STORE_SCRIPT_NAME;
57
+ const profileName = yield* ALCHEMY_PROFILE;
58
+ const localStage = `${profileName}_${scriptName}`;
59
+ const credStore = yield* CredentialsStore;
60
+ if (yield* hasLocalStack(localStage)) {
61
+ // if there's still a local stack, then we need to finish the bootstrap
62
+ // TODO(sam): what if the local stack was
63
+ return yield* deployWithLocalState({
64
+ scriptName,
65
+ profileName,
66
+ isCI,
67
+ force: false,
68
+ });
69
+ }
60
70
 
61
- /**
62
- * Best-effort Cloudflare-account-hash annotation on the current span.
63
- * Resolves the accountId from {@link CloudflareEnvironment} and
64
- * attaches `alchemy.cloudflare.account_hash` to whichever span is
65
- * active. Silently no-ops if the environment isn't resolvable so
66
- * State-store layer construction still succeeds in degraded paths.
67
- *
68
- * `noTrack` controls whether the hash is attached:
69
- * - `true` — never annotate (caller-level opt-out).
70
- * - `false` — always annotate, regardless of env.
71
- * - `undefined` — fall back to the `NO_TRACK` env var; default off.
72
- */
73
- const annotateAccountHash = (noTrack?: boolean) =>
74
- Effect.gen(function* () {
75
- if (noTrack === true) return;
76
- if (noTrack === undefined) {
77
- const fromEnv = yield* Config.boolean("NO_TRACK").pipe(
78
- Config.withDefault(false),
71
+ const ensureLatest = ({
72
+ url,
73
+ authToken,
74
+ }: {
75
+ url: string;
76
+ authToken: string;
77
+ }) =>
78
+ Effect.gen(function* () {
79
+ const { matches, expected, observed } =
80
+ yield* checkStateStoreVersion(url);
81
+
82
+ if (observed === undefined) {
83
+ const shouldDeploy = yield* Clank.confirm({
84
+ message: `Cloudflare State Store '${scriptName}' is not available. Do you want to deploy it?`,
85
+ });
86
+ if (shouldDeploy) {
87
+ return yield* bootstrap({
88
+ workerName: scriptName,
89
+ profile: profileName,
90
+ });
91
+ } else {
92
+ return yield* Effect.die(new Clank.PromptCancelled());
93
+ }
94
+ }
95
+
96
+ const httpState = yield* ensureAccess({ url, authToken });
97
+ if (matches) {
98
+ return httpState;
99
+ } else if (isCI) {
100
+ return yield* Effect.die(
101
+ new AuthError({
102
+ message: `Cloudflare State store not found. Run 'alchemy bootstrap cloudflare --profile <your-ci-profile>' to deploy it first.`,
103
+ }),
104
+ );
105
+ } else {
106
+ const shouldDeploy = yield* Clank.confirm({
107
+ message:
108
+ `Cloudflare State Store '${scriptName}' is out of date ` +
109
+ `(expected v${expected}, observed v${observed ?? "unknown"})`,
110
+ });
111
+ if (shouldDeploy) {
112
+ const stateStoreOptions = yield* deployStateStore({
113
+ stage: scriptName,
114
+ state: httpState,
115
+ force: false,
116
+ });
117
+ return yield* makeCloudflareStateStore(stateStoreOptions);
118
+ } else {
119
+ return yield* Effect.die(new Clank.PromptCancelled());
120
+ }
121
+ }
122
+ });
123
+
124
+ const ensureAccess = (credentials: HttpStateStoreCredentials) =>
125
+ Effect.gen(function* () {
126
+ const isAuth = yield* checkHttpStateStoreAuth(credentials);
127
+ if (!isAuth) {
128
+ // our token is wrong, force a refresh
129
+ yield* Clank.info(
130
+ `Cloudflare State store authentication failed, refreshing credentials...`,
131
+ );
132
+ const credentials = yield* loginWithCloudflare(profileName, true);
133
+ if (!(yield* checkHttpStateStoreAuth(credentials))) {
134
+ return yield* Effect.die(
135
+ new AuthError({
136
+ message: `Cloudflare State store authentication failed, after refreshing credentials.`,
137
+ }),
138
+ );
139
+ }
140
+ return yield* makeCloudflareStateStore(credentials);
141
+ }
142
+ return yield* makeCloudflareStateStore(credentials);
143
+ });
144
+
145
+ const credentials = yield* credStore.read<HttpStateStoreCredentials>(
146
+ profileName,
147
+ CREDENTIALS_FILE,
79
148
  );
80
- if (fromEnv) return;
81
- }
82
- const env = yield* Effect.serviceOption(
83
- CloudflareEnvironment.CloudflareEnvironment,
84
- );
85
- if (env._tag !== "Some") return;
86
- const hash = yield* hashAccountId(env.value.accountId);
87
- yield* Effect.annotateCurrentSpan("alchemy.cloudflare.account_hash", hash);
88
- }).pipe(Effect.catch(() => Effect.void));
149
+ if (credentials) {
150
+ return yield* ensureLatest(credentials);
151
+ }
152
+ const workerExists = yield* isStateStoreAvailable(scriptName);
153
+ if (workerExists) {
154
+ return yield* ensureLatest(
155
+ yield* loginWithCloudflare(profileName, false),
156
+ );
157
+ } else if (isCI) {
158
+ // TODO(sam): do we want to support bootstrapping the state store from CI?
159
+ // for now - just die here
160
+ return yield* Effect.die(
161
+ new AuthError({
162
+ message: `Cloudflare State store not found. Run 'alchemy bootstrap cloudflare --profile <your-ci-profile>' to deploy it first.`,
163
+ }),
164
+ );
165
+ } else {
166
+ return yield* Clank.confirm({
167
+ message:
168
+ "Cloudflare State Store not found. Do you want to deploy it?",
169
+ }).pipe(
170
+ Effect.flatMap((shouldDeploy) =>
171
+ shouldDeploy
172
+ ? bootstrap()
173
+ : Effect.die(new Clank.PromptCancelled()),
174
+ ),
175
+ );
176
+ }
177
+ }).pipe(recordStateStoreInit, Effect.orDie),
178
+ ).pipe(
179
+ Layer.provideMerge(Credentials.fromAuthProvider()),
180
+ Layer.provideMerge(CloudflareEnvironment.fromProfile()),
181
+ Layer.provideMerge(CloudflareAuth),
182
+ Layer.provideMerge(Access.AccessLive),
183
+ Layer.provideMerge(ProfileLive),
184
+ Layer.provideMerge(CredentialsStoreLive),
185
+ Layer.orDie,
186
+ );
89
187
 
90
188
  export interface BootstrapOptions {
91
- /**
92
- * The name of the script to use for the state store.
93
- * @default "alchemy-state-store"
94
- */
189
+ /** @default "alchemy-state-store" */
95
190
  workerName?: string;
96
- /**
97
- * Re-run the deploy even if the worker already exists in Cloudflare.
98
- *
99
- * Without this flag, an already-deployed worker is **adopted**: the
100
- * auth-token secret is re-fetched via an edge probe and the local
101
- * credentials file is refreshed, but no new code is uploaded. With
102
- * `force: true` the full deploy runs again, redeploying the worker
103
- * code and reconciling every resource in place.
104
- */
191
+ /** @default false */
105
192
  force?: boolean;
193
+ /** @default "default" */
194
+ profile?: string;
106
195
  }
107
196
 
108
- /**
109
- * Manually bootstrap (or repair) the Cloudflare-hosted HTTP State
110
- * Store. Used by `alchemy bootstrap cloudflare`.
111
- *
112
- * The flow mirrors what {@link state} does on first use, but is
113
- * exposed as a standalone effect so users can trigger it (or recover
114
- * from a partially-failed previous deploy) explicitly:
115
- *
116
- * 1. Resume any in-progress local `CloudflareStateStore` stack.
117
- * 2. If the worker already exists in Cloudflare and `force` is not
118
- * set, adopt it: read the auth token via an edge-preview probe
119
- * and persist credentials.
120
- * 3. Otherwise run the full deploy with adoption enabled, sync local
121
- * state into the deployed store, persist credentials, and delete
122
- * the local stack.
123
- */
124
197
  export const bootstrap = (options: BootstrapOptions = {}) =>
125
198
  Effect.gen(function* () {
126
- const isCI = yield* Config.boolean("CI").pipe(Config.withDefault(false));
127
- const profileName = yield* ALCHEMY_PROFILE;
199
+ const isCI = yield* CI;
200
+ const profileName = options.profile ?? (yield* ALCHEMY_PROFILE);
128
201
  const scriptName = options.workerName ?? STATE_STORE_SCRIPT_NAME;
129
202
  const force = options.force ?? false;
203
+ const localStage = `${profileName}_${scriptName}`;
130
204
  yield* Effect.annotateCurrentSpan({
131
205
  "alchemy.state_store.script_name": scriptName,
132
206
  "alchemy.state_store.profile": profileName,
@@ -135,36 +209,41 @@ export const bootstrap = (options: BootstrapOptions = {}) =>
135
209
  });
136
210
  yield* annotateAccountHash();
137
211
 
138
- const localState = yield* makeLocalState();
139
- const hasLocalStack = yield* Effect.map(
140
- localState.listStages("CloudflareStateStore"),
141
- (stages) => stages.includes(scriptName),
142
- );
143
-
144
- if (hasLocalStack) {
212
+ if (yield* hasLocalStack(localStage)) {
213
+ // if there's a local stack still, we can assume we did not finish hoisting it, so finish that
145
214
  yield* Clank.info(
146
215
  `Resuming Cloudflare State Store '${scriptName}' deployment...`,
147
216
  );
148
- yield* finishBootstrap({ scriptName, profileName, localState, isCI });
149
- yield* Clank.success(`Cloudflare State Store '${scriptName}' is ready.`);
150
- return;
151
- }
152
-
153
- const { accountId } = yield* CloudflareEnvironment.CloudflareEnvironment;
154
- const workerExists = yield* workers
155
- .getScriptSetting({ accountId, scriptName })
156
- .pipe(
157
- Effect.map((setting) => setting !== undefined),
158
- Effect.catchTag("WorkerNotFound", () => Effect.succeed(false)),
217
+ // resume deployment
218
+ return yield* deployWithLocalState({
219
+ scriptName,
220
+ profileName,
221
+ isCI,
222
+ force,
223
+ }).pipe(
224
+ Effect.tap(() =>
225
+ Clank.success(`Cloudflare State Store '${scriptName}' is ready.`),
226
+ ),
159
227
  );
228
+ }
160
229
 
161
- if (workerExists && !force) {
162
- yield* Clank.info(
163
- `Worker '${scriptName}' already exists; adopting and refreshing credentials. ` +
164
- `Use --force to redeploy.`,
230
+ const workerExists = yield* isStateStoreAvailable(scriptName);
231
+ if (workerExists) {
232
+ // this is a regular update, let's check if it needs an update and refresh credentials
233
+ if (!force) {
234
+ yield* Clank.info(
235
+ `Worker '${scriptName}' already exists; adopting and refreshing credentials. ` +
236
+ `Use --force to redeploy.`,
237
+ );
238
+ }
239
+ const credentials = yield* loginWithCloudflare(
240
+ profileName,
241
+ // force refresh during
242
+ true,
165
243
  );
166
- const credentials = yield* loginWithCloudflare();
244
+ const { url, authToken } = credentials;
167
245
  if (!isCI) {
246
+ // we don't write credentials in CI because the file system is ephemeral
168
247
  const store = yield* CredentialsStore;
169
248
  yield* store.write<HttpStateStoreCredentials>(
170
249
  profileName,
@@ -172,27 +251,44 @@ export const bootstrap = (options: BootstrapOptions = {}) =>
172
251
  credentials,
173
252
  );
174
253
  }
175
- yield* redeployIfStale({
254
+ const { matches, expected, observed } =
255
+ yield* checkStateStoreVersion(url);
256
+ const httpState = yield* makeCloudflareStateStore({ url, authToken });
257
+ if (!matches || force) {
258
+ if (matches && force) {
259
+ yield* Clank.info(
260
+ `Cloudflare State Store '${scriptName}' is up to date; force redeploying...`,
261
+ );
262
+ } else {
263
+ yield* Clank.info(
264
+ `Cloudflare State Store '${scriptName}' is out of date ` +
265
+ `(expected v${expected}, observed v${observed ?? "unknown"}); redeploying...`,
266
+ );
267
+ }
268
+ return yield* makeCloudflareStateStore(
269
+ yield* deployStateStore({
270
+ stage: scriptName,
271
+ state: httpState,
272
+ force,
273
+ }),
274
+ );
275
+ } else {
276
+ return httpState;
277
+ }
278
+ } else {
279
+ // fresh deploy - deploy from local for the first time
280
+ yield* Clank.info(`Deploying Cloudflare State Store '${scriptName}'...`);
281
+ return yield* deployWithLocalState({
176
282
  scriptName,
177
283
  profileName,
178
- localState,
179
284
  isCI,
180
- url: credentials.url,
181
- });
182
- yield* Clank.success(`Cloudflare State Store '${scriptName}' is ready.`);
183
- return;
184
- }
185
-
186
- if (workerExists) {
187
- yield* Clank.info(
188
- `Forcing redeploy of Cloudflare State Store '${scriptName}'...`,
285
+ force,
286
+ }).pipe(
287
+ Effect.tap(() =>
288
+ Clank.success(`Cloudflare State Store '${scriptName}' is ready.`),
289
+ ),
189
290
  );
190
- } else {
191
- yield* Clank.info(`Deploying Cloudflare State Store '${scriptName}'...`);
192
291
  }
193
-
194
- yield* finishBootstrap({ scriptName, profileName, localState, isCI });
195
- yield* Clank.success(`Cloudflare State Store '${scriptName}' is ready.`);
196
292
  }).pipe(
197
293
  Effect.withSpan("state_store.bootstrap", {
198
294
  attributes: {
@@ -203,288 +299,23 @@ export const bootstrap = (options: BootstrapOptions = {}) =>
203
299
  }),
204
300
  );
205
301
 
206
- export const state = () =>
207
- Layer.effect(
208
- State,
209
- Effect.gen(function* () {
210
- const isCI = yield* Config.boolean("CI").pipe(Config.withDefault(false));
211
- const alchemyContext = yield* AlchemyContext;
212
- const profileName = yield* ALCHEMY_PROFILE;
213
- const store = yield* CredentialsStore;
214
- const credentials = yield* store.read<HttpStateStoreCredentials>(
215
- profileName,
216
- CREDENTIALS_FILE,
217
- );
218
-
219
- const scriptName = STATE_STORE_SCRIPT_NAME;
220
- yield* Effect.annotateCurrentSpan({
221
- "alchemy.state_store.script_name": scriptName,
222
- "alchemy.state_store.profile": profileName,
223
- "alchemy.state_store.ci": isCI,
224
- });
225
- yield* annotateAccountHash(
226
- yield* Config.boolean("NO_TRACK").pipe(Config.withDefault(false)),
227
- );
228
-
229
- // The bootstrap of the Cloudflare State Store is only considered
230
- // successful once two invariants hold:
231
- // 1. the worker has been deployed and the credentials persisted
232
- // 2. the local CloudflareStateStore stack has been synced into
233
- // the HTTP store and removed from disk
234
- //
235
- // If a previous run failed anywhere between deploying the worker
236
- // and deleting the local stack (e.g. the worker got created but
237
- // sync/credential-write/delete never ran) we will detect the
238
- // leftover local stack here and resume the bootstrap. This loop
239
- // re-runs deploy (idempotent over already-applied resources) and
240
- // finishes the sync/credential/cleanup steps until both
241
- // invariants are satisfied.
242
- const localState = yield* makeLocalState();
243
-
244
- const hasLocalStack = yield* Effect.map(
245
- localState.listStages("CloudflareStateStore"),
246
- (stages) => stages.includes(scriptName),
247
- );
248
-
249
- if (hasLocalStack) {
250
- yield* Clank.info(
251
- `Resuming Cloudflare State Store '${scriptName}' deployment...`,
252
- );
253
- return yield* finishBootstrap({
254
- scriptName,
255
- profileName,
256
- localState,
257
- isCI,
258
- });
259
- }
260
-
261
- if (credentials) {
262
- // We have local credentials. Before trusting them, verify
263
- // the deployed worker is on the version this CLI was built
264
- // against. If it isn't, `redeployIfStale` runs the idempotent
265
- // bootstrap flow and short-circuits with the freshly-deployed
266
- // store.
267
- const fresh = yield* redeployIfStale({
268
- scriptName,
269
- profileName,
270
- localState,
271
- isCI,
272
- url: credentials.url,
273
- });
274
- if (fresh) return fresh;
275
-
276
- const httpState = yield* makeCloudflareStateStore(credentials);
277
-
278
- if (alchemyContext.updateStateStore) {
279
- yield* deployStateStore(scriptName, httpState);
280
- }
281
-
282
- return httpState;
283
- }
284
-
285
- // our profile does not contain a reference to the state store, let's try and resolve it
286
- const { accountId } = yield* CloudflareEnvironment.CloudflareEnvironment;
287
- const workerExists = yield* workers
288
- .getScriptSetting({
289
- accountId,
290
- scriptName,
291
- })
292
- .pipe(
293
- Effect.map((setting) => setting !== undefined),
294
- Effect.catchTag("WorkerNotFound", () => Effect.succeed(false)),
295
- );
296
-
297
- if (workerExists) {
298
- // it exists, so fetch the secret token
299
- const credentials = yield* loginWithCloudflare();
300
- if (!isCI) {
301
- yield* store.write<HttpStateStoreCredentials>(
302
- profileName,
303
- CREDENTIALS_FILE,
304
- credentials,
305
- );
306
- }
307
-
308
- const fresh = yield* redeployIfStale({
309
- scriptName,
310
- profileName,
311
- localState,
312
- isCI,
313
- url: credentials.url,
314
- });
315
- if (fresh) return fresh;
316
-
317
- const httpState = yield* makeCloudflareStateStore(credentials);
318
-
319
- if (alchemyContext.updateStateStore) {
320
- yield* deployStateStore(scriptName, httpState);
321
- }
322
-
323
- return httpState;
324
- }
325
-
326
- if (isCI) {
327
- // TODO(sam): do we want to support bootstrapping the state store from CI?
328
- // for now - just die here
329
- return yield* Effect.die(
330
- new AuthError({
331
- message: `State store not found for script ${scriptName}. Deploy the state store first.`,
332
- }),
333
- );
334
- }
335
-
336
- const shouldDeploy = yield* Clank.confirm({
337
- message: "Cloudflare State Store not found. Do you want to deploy it?",
338
- });
339
-
340
- if (!shouldDeploy) {
341
- return yield* Effect.die(new Clank.PromptCancelled());
342
- }
343
-
344
- return yield* finishBootstrap({
345
- scriptName,
346
- profileName,
347
- localState,
348
- isCI,
349
- });
350
- }).pipe(recordStateStoreInit, Effect.orDie),
351
- ).pipe(
352
- Layer.provideMerge(Credentials.fromAuthProvider()),
353
- Layer.provideMerge(CloudflareEnvironment.fromProfile()),
354
- Layer.provideMerge(CloudflareAuth),
355
- Layer.provideMerge(Access.AccessLive),
356
- Layer.provideMerge(ProfileLive),
357
- Layer.provideMerge(CredentialsStoreLive),
358
- Layer.orDie,
359
- );
360
-
361
- const makeCloudflareStateStore = Effect.fnUntraced(function* ({
362
- url,
363
- authToken,
302
+ const deployStateStore = ({
303
+ stage,
304
+ state,
305
+ force,
364
306
  }: {
365
- url: string;
366
- authToken: string;
367
- }) {
368
- const access = yield* Access.Access;
369
- const accessHeaders = yield* access.getAccessHeaders(new URL(url).host);
370
- return yield* makeHttpStateStore({
371
- url,
372
- authToken,
373
- transformClient: HttpClientRequest.setHeaders(accessHeaders),
374
- id: "cloudflare-http",
375
- });
376
- });
377
-
378
- /**
379
- * Non-destructively copy every resource in the
380
- * `CloudflareStateStore/<scriptName>` stack from `source` into
381
- * `destination`, leaving every other stack in `destination` untouched.
382
- *
383
- * This intentionally does not delete anything from `destination`: at
384
- * bootstrap time the destination is the user's live remote state
385
- * store, and removing entries that happen to be missing locally would
386
- * be catastrophic.
387
- */
388
- const hoistBootstrapStack = Effect.fnUntraced(function* (
389
- source: StateService,
390
- destination: StateService,
391
- scriptName: string,
392
- ) {
393
- const stack = "CloudflareStateStore";
394
- const stage = scriptName;
395
- const fqns = yield* source.list({ stack, stage });
396
- yield* Effect.annotateCurrentSpan({
397
- "alchemy.state_store.stack": stack,
398
- "alchemy.state_store.stage": stage,
399
- "alchemy.state_store.resources.count": fqns.length,
400
- });
401
- yield* Effect.forEach(
402
- fqns,
403
- Effect.fnUntraced(function* (fqn) {
404
- const value = yield* source.get({ stack, stage, fqn });
405
- if (value) {
406
- yield* destination.set({ stack, stage, fqn, value });
407
- }
408
- }),
409
- { concurrency: "unbounded" },
410
- );
411
- }, Effect.withSpan("state_store.hoist_bootstrap_stack"));
412
-
413
- /**
414
- * Finish (or resume) the bootstrap of the Cloudflare State Store using
415
- * the provided local state as the source of truth. This is idempotent
416
- * and safe to re-run: any resources already applied during a previous
417
- * partial run will be reconciled, the local stack will be synced into
418
- * the deployed HTTP store, credentials persisted, and finally the
419
- * local stack removed - which is what marks the bootstrap as complete.
420
- */
421
- const finishBootstrap = ({
422
- scriptName,
423
- profileName,
424
- localState,
425
- isCI,
426
- }: {
427
- scriptName: string;
428
- profileName: string;
429
- localState: StateService;
430
- isCI: boolean;
307
+ stage: string;
308
+ state: StateService;
309
+ force?: boolean;
431
310
  }) =>
432
- Effect.gen(function* () {
433
- yield* deployStateStore(scriptName, localState);
434
-
435
- // Don't trust the `authToken` returned by `deploy(...)`: when
436
- // adoption kicks in (the Secrets Store secret already existed),
437
- // the locally-generated `Random` value won't match the value
438
- // actually persisted in Cloudflare, and any HTTP call to the
439
- // worker would 401. Re-read the live token from the deployed
440
- // worker via the same edge-preview probe `loginWithCloudflare`
441
- // uses, so the credentials we persist always reflect what is
442
- // actually deployed. `loginWithCloudflare` also persists the
443
- // credentials file (skipping the write in CI), so we don't need
444
- // to do that explicitly here.
445
- const { url, authToken } = yield* loginWithCloudflare();
446
- const httpState = yield* makeCloudflareStateStore({ url, authToken });
447
- // `profileName` is intentionally unused here — `loginWithCloudflare`
448
- // resolves it itself. Reference it to keep the surrounding API
449
- // explicit and to avoid an unused-parameter lint.
450
- void profileName;
451
- void isCI;
452
-
453
- // Copy the freshly-deployed CloudflareStateStore stack into the
454
- // remote store. We deliberately do NOT call `syncState` here:
455
- // syncState is a *mirror* that deletes everything in the
456
- // destination that is not in the source, which would wipe every
457
- // user stack in the remote store on any subsequent bootstrap
458
- // (resume / --force / partial-failure recovery), since by that
459
- // point local state only contains the bootstrap stack itself.
460
- yield* hoistBootstrapStack(localState, httpState, scriptName);
461
-
462
- yield* localState.deleteStack({
463
- stack: "CloudflareStateStore",
464
- stage: scriptName,
465
- });
466
-
467
- return httpState;
468
- }).pipe(
469
- Effect.withSpan("state_store.finish_bootstrap", {
470
- attributes: {
471
- "alchemy.state_store.op": "finish_bootstrap",
472
- "alchemy.state_store.script_name": scriptName,
473
- "alchemy.state_store.profile": profileName,
474
- "alchemy.state_store.ci": isCI,
475
- },
476
- }),
477
- );
478
-
479
- const deployStateStore = (scriptName: string, state?: StateService) =>
480
311
  Effect.gen(function* () {
481
312
  yield* annotateAccountHash();
482
- const localState = state ?? (yield* makeLocalState());
483
313
  // deploy it with local state (which we will then hoist into the Cloudflare state store)
484
- const stateLayer = Layer.succeed(State, localState);
314
+ const stateLayer = Layer.succeed(State, state);
485
315
  const { url, authToken } = yield* deploy({
486
316
  // use the script name as the stage name (so the user can have multiple state stores)
487
- stage: scriptName,
317
+ stage,
318
+ force,
488
319
  stack: Alchemy.Stack(
489
320
  "CloudflareStateStore",
490
321
  {
@@ -494,6 +325,7 @@ const deployStateStore = (scriptName: string, state?: StateService) =>
494
325
  Effect.gen(function* () {
495
326
  const token = yield* TokenValue;
496
327
  const api = yield* Api;
328
+ yield* AuthToken; // make sure it's in the Secrets Store
497
329
 
498
330
  // Surface the bearer token so tests and clients can authenticate
499
331
  // after deploy. The underlying value lives in the Cloudflare
@@ -514,6 +346,9 @@ const deployStateStore = (scriptName: string, state?: StateService) =>
514
346
  // TODO(sam): we should not need to do this, but types do complain. fix deploy
515
347
  Effect.provide(stateLayer),
516
348
  );
349
+
350
+ yield* writeCredentials(url, authToken);
351
+
517
352
  // Cloudflare's worker upload is eventually consistent: the deploy
518
353
  // call returns as soon as the script upload is accepted, but the
519
354
  // edge can keep serving the previous version for several seconds
@@ -524,17 +359,138 @@ const deployStateStore = (scriptName: string, state?: StateService) =>
524
359
  // observe stale data or trip the staleness check and recurse into
525
360
  // another redeploy.
526
361
  yield* waitForStateStoreVersion(url);
527
- return { url, authToken, localState };
362
+ return { url, authToken };
528
363
  }).pipe(
529
364
  Effect.withSpan("state_store.deploy", {
530
365
  attributes: {
531
- "alchemy.state_store.script_name": scriptName,
532
366
  "alchemy.state_store.op": "deploy",
533
367
  },
534
368
  }),
535
369
  recordStateStoreOp("deploy"),
536
370
  );
537
371
 
372
+ const deployWithLocalState = ({
373
+ scriptName,
374
+ isCI,
375
+ force,
376
+ profileName,
377
+ }: {
378
+ scriptName: string;
379
+ isCI: boolean;
380
+ force: boolean;
381
+ profileName: string;
382
+ }) =>
383
+ Effect.gen(function* () {
384
+ const localState = yield* makeLocalState();
385
+ const localStage = `${profileName}_${scriptName}`;
386
+ const remoteStage = scriptName;
387
+ const { authToken } = yield* deployStateStore({
388
+ stage: localStage,
389
+ state: localState,
390
+ force,
391
+ });
392
+
393
+ const { url } = yield* loginWithCloudflare(profileName, force);
394
+ const httpState = yield* makeCloudflareStateStore({ url, authToken });
395
+
396
+ yield* hoistBootstrapStack({
397
+ source: {
398
+ state: localState,
399
+ stage: localStage,
400
+ },
401
+ destination: {
402
+ state: httpState,
403
+ stage: remoteStage,
404
+ },
405
+ });
406
+
407
+ yield* localState.deleteStack({
408
+ stack: "CloudflareStateStore",
409
+ stage: localStage,
410
+ });
411
+
412
+ return httpState;
413
+ }).pipe(
414
+ Effect.withSpan("state_store.finish_bootstrap", {
415
+ attributes: {
416
+ "alchemy.state_store.op": "finish_bootstrap",
417
+ "alchemy.state_store.ci": isCI,
418
+ },
419
+ }),
420
+ );
421
+
422
+ // check if there's a local stack that wasn't properly hoisted
423
+ const hasLocalStack = (stage: string) =>
424
+ Effect.gen(function* () {
425
+ const localState = yield* makeLocalState();
426
+ return yield* Effect.map(
427
+ localState.listStages("CloudflareStateStore"),
428
+ // key off the profile name to avoid conflicts with other profiles
429
+ (stages) => stages.includes(stage),
430
+ );
431
+ });
432
+
433
+ /**
434
+ * Non-destructively copy every resource in the
435
+ * `CloudflareStateStore/<scriptName>` stack from `source` into
436
+ * `destination`, leaving every other stack in `destination` untouched.
437
+ *
438
+ * This intentionally does not delete anything from `destination`: at
439
+ * bootstrap time the destination is the user's live remote state
440
+ * store, and removing entries that happen to be missing locally would
441
+ * be catastrophic.
442
+ */
443
+ const hoistBootstrapStack = Effect.fnUntraced(function* ({
444
+ source,
445
+ destination,
446
+ }: {
447
+ source: {
448
+ state: StateService;
449
+ stage: string;
450
+ };
451
+ destination: {
452
+ state: StateService;
453
+ stage: string;
454
+ };
455
+ }) {
456
+ const stack = "CloudflareStateStore";
457
+ const fqns = yield* source.state.list({ stack, stage: source.stage });
458
+ yield* Effect.annotateCurrentSpan({
459
+ "alchemy.state_store.stack": stack,
460
+ "alchemy.state_store.stage": source.stage,
461
+ "alchemy.state_store.resources.count": fqns.length,
462
+ });
463
+ yield* Effect.forEach(
464
+ fqns,
465
+ Effect.fnUntraced(function* (fqn) {
466
+ const value = yield* source.state.get({
467
+ stack,
468
+ stage: source.stage,
469
+ fqn,
470
+ });
471
+ if (value) {
472
+ yield* destination.state
473
+ .set({
474
+ stack,
475
+ stage: destination.stage,
476
+ fqn,
477
+ value,
478
+ })
479
+ .pipe(
480
+ Effect.retry({
481
+ while: (error) =>
482
+ isHttpClientError(error.cause) &&
483
+ // worker can 404 for a bit on first deploy, retry these
484
+ error.cause.response?.status === 404,
485
+ schedule: Schedule.fixed(200),
486
+ }),
487
+ );
488
+ }
489
+ }),
490
+ { concurrency: "unbounded" },
491
+ );
492
+ }, Effect.withSpan("state_store.hoist_bootstrap_stack"));
493
+
538
494
  /**
539
495
  * Log in to a Cloudflare-deployed HTTP state-store.
540
496
  *
@@ -551,12 +507,23 @@ const deployStateStore = (scriptName: string, state?: StateService) =>
551
507
  * `CloudflareEnvironment`, `Credentials`, `HttpClient`, and
552
508
  * `FileSystem`.
553
509
  */
554
- export const loginWithCloudflare = () =>
510
+ export const loginWithCloudflare = (profileName: string, force: boolean) =>
555
511
  Effect.gen(function* () {
556
- const isCI = yield* Config.boolean("CI").pipe(Config.withDefault(false));
557
- const profileName = yield* ALCHEMY_PROFILE;
512
+ const credStore = yield* CredentialsStore;
513
+ const isCI = yield* CI;
558
514
  const { accountId } = yield* CloudflareEnvironment.CloudflareEnvironment;
559
515
 
516
+ if (!force) {
517
+ // try and read from the cached credentials first if not forcing (force will always refresh)
518
+ const credentials = yield* credStore.read<HttpStateStoreCredentials>(
519
+ profileName,
520
+ CREDENTIALS_FILE,
521
+ );
522
+ if (credentials) {
523
+ return credentials;
524
+ }
525
+ }
526
+
560
527
  // 1. Locate the single Secrets Store on the account.
561
528
  const stores = yield* SecretsStore.listStores({ accountId });
562
529
  const store = stores.result[0];
@@ -569,19 +536,18 @@ export const loginWithCloudflare = () =>
569
536
  );
570
537
  }
571
538
 
572
- // 2. Fetch the auth-token value via an edge-preview worker.
573
- // We piggy-back on the already-deployed state-store script so the
574
- // `cf-workers-preview-token` header has a real workers.dev route to
575
- // swap onto. Uploading the probe under a brand-new script name fails
576
- // on accounts where that script has never been deployed (or where
577
- // workers.dev preview URLs are off by default — the post-2024
578
- // Cloudflare default for new accounts) because the host doesn't
579
- // resolve and Cloudflare's edge serves a 400 HTML error page
580
- // instead of routing to the preview.
539
+ // 2. Fetch the auth-token from Secrets Store with a temporary edge-preview worker
581
540
  const authToken = yield* readSecretViaEdge(
582
541
  STATE_STORE_SCRIPT_NAME,
583
542
  store.id,
584
543
  AuthTokenSecretName,
544
+ ).pipe(
545
+ Effect.retry({
546
+ while: isWorkersPreviewConfigurationError,
547
+ schedule: Schedule.exponential(200).pipe(
548
+ Schedule.both(Schedule.recurs(15)),
549
+ ),
550
+ }),
585
551
  );
586
552
 
587
553
  // 3. Derive the deployed worker URL.
@@ -591,7 +557,6 @@ export const loginWithCloudflare = () =>
591
557
  if (!isCI) {
592
558
  // 4. Persist credentials. The profile entry is managed by
593
559
  // `loadOrConfigure` when this is invoked through `configure`.
594
- const credStore = yield* CredentialsStore;
595
560
  yield* credStore
596
561
  .write<HttpStateStoreCredentials>(profileName, CREDENTIALS_FILE, {
597
562
  url,
@@ -634,74 +599,33 @@ export const loginWithCloudflare = () =>
634
599
  }),
635
600
  );
636
601
 
637
- /**
638
- * Run {@link finishBootstrap} iff the worker at `url` fails the
639
- * version probe; otherwise resolve to `undefined`. Centralises the
640
- * "is the deployed worker still compatible?" guard used by every
641
- * code path that already has a presumably-valid worker URL.
642
- */
643
- const redeployIfStale = ({
602
+ const isStateStoreAvailable = (scriptName: string = "alchemy-state-store") =>
603
+ Effect.gen(function* () {
604
+ // otherwise, the remote one might exist
605
+ const { accountId } = yield* CloudflareEnvironment.CloudflareEnvironment;
606
+ return yield* workers.getScriptSetting({ accountId, scriptName }).pipe(
607
+ Effect.map((setting) => setting !== undefined),
608
+ Effect.catchTag("WorkerNotFound", () => Effect.succeed(false)),
609
+ );
610
+ });
611
+
612
+ const makeCloudflareStateStore = Effect.fnUntraced(function* ({
644
613
  url,
645
- scriptName,
646
- profileName,
647
- localState,
648
- isCI,
614
+ authToken,
649
615
  }: {
650
616
  url: string;
651
- scriptName: string;
652
- profileName: string;
653
- localState: StateService;
654
- isCI: boolean;
655
- }) =>
656
- Effect.gen(function* () {
657
- const { matches, expected, observed } = yield* checkStateStoreVersion(url);
658
- if (matches) return undefined;
659
- yield* Clank.info(
660
- `Cloudflare State Store '${scriptName}' is out of date ` +
661
- `(expected v${expected}, observed v${observed ?? "unknown"}); redeploying...`,
662
- );
663
- return yield* finishBootstrap({
664
- scriptName,
665
- profileName,
666
- localState,
667
- isCI,
668
- });
669
- }).pipe(
670
- Effect.withSpan("state_store.redeploy_if_stale", {
671
- attributes: {
672
- "alchemy.state_store.op": "redeploy_if_stale",
673
- "alchemy.state_store.script_name": scriptName,
674
- "alchemy.state_store.url": url,
675
- },
676
- }),
677
- );
617
+ authToken: string;
618
+ }) {
619
+ const access = yield* Access.Access;
620
+ const accessHeaders = yield* access.getAccessHeaders(new URL(url).host);
621
+ return yield* makeHttpStateStore({
622
+ url,
623
+ authToken,
624
+ transformClient: HttpClientRequest.setHeaders(accessHeaders),
625
+ id: "cloudflare-http",
626
+ });
627
+ });
678
628
 
679
- /**
680
- * Probe the deployed worker's `/version` endpoint and decide whether
681
- * it satisfies the current {@link STATE_STORE_VERSION} contract.
682
- *
683
- * Reuses the same typed {@link StateApi} client the rest of the store
684
- * speaks, just without the bearer-token transform — the `/version`
685
- * route is intentionally unauthenticated so we can probe it before
686
- * trusting any locally-cached credentials.
687
- *
688
- * Returns `true` only on a successful response carrying the matching
689
- * version. Any failure (404 from a pre-`/version` deploy, transport
690
- * error, schema mismatch, version mismatch) collapses to `false`,
691
- * since the caller's response in every case is the same: fall
692
- * through to the idempotent bootstrap flow.
693
- */
694
- /**
695
- * Block until the worker at `url` reports the {@link STATE_STORE_VERSION}
696
- * this CLI was built against, with bounded exponential retry. Used
697
- * post-deploy to wait out Cloudflare's edge cache so any subsequent
698
- * read or write goes to the new worker.
699
- *
700
- * Failures (transport errors, 404 from a pre-`/version` build, schema
701
- * mismatch, version mismatch) all collapse to "not ready yet" and are
702
- * retried; once the budget is exhausted we surface a hard failure
703
- * since continuing would talk to the wrong worker.
704
- */
705
629
  class StateStoreVersionNotReady extends Error {
706
630
  readonly _tag = "StateStoreVersionNotReady";
707
631
  constructor(
@@ -744,6 +668,9 @@ const waitForStateStoreVersion = (url: string) =>
744
668
  const checkStateStoreVersion = (url: string) =>
745
669
  Effect.gen(function* () {
746
670
  const client = yield* HttpApiClient.make(StateApi, { baseUrl: url });
671
+ const isAvailable = yield* Effect.cached(
672
+ isStateStoreAvailable(STATE_STORE_SCRIPT_NAME),
673
+ );
747
674
  // The /version route may 404 transiently after a fresh deploy
748
675
  // while Cloudflare propagates the new script to the edge, and may
749
676
  // also surface transport-level blips on cold workers.dev hosts.
@@ -751,6 +678,19 @@ const checkStateStoreVersion = (url: string) =>
751
678
  // exhausting that budget do we collapse to `undefined` and let
752
679
  // the caller treat it as a version mismatch.
753
680
  const result = yield* client.version.getVersion().pipe(
681
+ Effect.catchTag("HttpClientError", (e) =>
682
+ // if we get a 404 here, it means we assumed the worker shoudl exist, but it does not
683
+ // we should do a check to see if it does
684
+ e.response?.status === 404
685
+ ? isAvailable.pipe(
686
+ Effect.flatMap((isAvailable) =>
687
+ // if the worker is available, then we should assume it was recently created and retry by propagating the error
688
+ // otherwise, return undefined (we don't know the version, there is no worker)
689
+ isAvailable ? Effect.fail(e) : Effect.succeed(undefined),
690
+ ),
691
+ )
692
+ : Effect.fail(e),
693
+ ),
754
694
  Effect.retry({
755
695
  schedule: Schedule.spaced("250 millis").pipe(
756
696
  Schedule.both(Schedule.recurs(40)),
@@ -858,3 +798,63 @@ const readSecretViaEdge = (
858
798
  },
859
799
  }),
860
800
  );
801
+
802
+ const writeCredentials = (url: string, authToken: string) =>
803
+ Effect.gen(function* () {
804
+ const profileName = yield* ALCHEMY_PROFILE;
805
+ const credStore = yield* CredentialsStore;
806
+ yield* credStore.write<HttpStateStoreCredentials>(
807
+ profileName,
808
+ CREDENTIALS_FILE,
809
+ {
810
+ url,
811
+ authToken,
812
+ },
813
+ );
814
+ });
815
+
816
+ const isWorkersPreviewConfigurationError = (error: unknown) =>
817
+ error instanceof EdgeSessionError &&
818
+ (error.message.includes("Invalid Workers Preview configuration") ||
819
+ error.message.includes("Error 1031"));
820
+
821
+ /**
822
+ * SHA-256 hex digest of the Cloudflare account ID. Used as a stable
823
+ * pseudonymous identifier on telemetry spans so the dashboard can
824
+ * count distinct state-store deployments without leaking the raw
825
+ * accountId. Mirrors the `alchemy.git.origin_hash` pattern in
826
+ * `Telemetry/Attributes.ts`.
827
+ */
828
+ const hashAccountId = (accountId: string) =>
829
+ Effect.sync(() =>
830
+ crypto.createHash("sha256").update(accountId).digest("hex"),
831
+ );
832
+
833
+ /**
834
+ * Best-effort Cloudflare-account-hash annotation on the current span.
835
+ * Resolves the accountId from {@link CloudflareEnvironment} and
836
+ * attaches `alchemy.cloudflare.account_hash` to whichever span is
837
+ * active. Silently no-ops if the environment isn't resolvable so
838
+ * State-store layer construction still succeeds in degraded paths.
839
+ *
840
+ * `noTrack` controls whether the hash is attached:
841
+ * - `true` — never annotate (caller-level opt-out).
842
+ * - `false` — always annotate, regardless of env.
843
+ * - `undefined` — fall back to the `NO_TRACK` env var; default off.
844
+ */
845
+ const annotateAccountHash = (noTrack?: boolean) =>
846
+ Effect.gen(function* () {
847
+ if (noTrack === true) return;
848
+ if (noTrack === undefined) {
849
+ const fromEnv = yield* Config.boolean("NO_TRACK").pipe(
850
+ Config.withDefault(false),
851
+ );
852
+ if (fromEnv) return;
853
+ }
854
+ const env = yield* Effect.serviceOption(
855
+ CloudflareEnvironment.CloudflareEnvironment,
856
+ );
857
+ if (env._tag !== "Some") return;
858
+ const hash = yield* hashAccountId(env.value.accountId);
859
+ yield* Effect.annotateCurrentSpan("alchemy.cloudflare.account_hash", hash);
860
+ }).pipe(Effect.catch(() => Effect.void));