alchemy-effect 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (494) hide show
  1. package/bin/alchemy-effect.js +55354 -8
  2. package/bin/alchemy-effect.js.map +1 -1
  3. package/bin/alchemy-effect.ts +266 -10
  4. package/lib/$.d.ts +5 -0
  5. package/lib/$.d.ts.map +1 -0
  6. package/lib/$.js +9 -0
  7. package/lib/$.js.map +1 -0
  8. package/lib/app.d.ts +4 -17
  9. package/lib/app.d.ts.map +1 -1
  10. package/lib/app.js +0 -20
  11. package/lib/app.js.map +1 -1
  12. package/lib/apply.d.ts +15 -75
  13. package/lib/apply.d.ts.map +1 -1
  14. package/lib/apply.js +439 -154
  15. package/lib/apply.js.map +1 -1
  16. package/lib/assert-never.d.ts +12 -0
  17. package/lib/assert-never.d.ts.map +1 -0
  18. package/lib/assert-never.js +11 -0
  19. package/lib/assert-never.js.map +1 -0
  20. package/lib/aws/account.d.ts +10 -1
  21. package/lib/aws/account.d.ts.map +1 -1
  22. package/lib/aws/account.js +18 -3
  23. package/lib/aws/account.js.map +1 -1
  24. package/lib/aws/client.d.ts.map +1 -1
  25. package/lib/aws/client.js +0 -1
  26. package/lib/aws/client.js.map +1 -1
  27. package/lib/aws/config.d.ts +15 -0
  28. package/lib/aws/config.d.ts.map +1 -0
  29. package/lib/aws/config.js +1 -0
  30. package/lib/aws/config.js.map +1 -0
  31. package/lib/aws/credentials.d.ts +10 -0
  32. package/lib/aws/credentials.d.ts.map +1 -1
  33. package/lib/aws/credentials.js +73 -47
  34. package/lib/aws/credentials.js.map +1 -1
  35. package/lib/aws/dynamodb/client.d.ts +1 -1
  36. package/lib/aws/dynamodb/client.d.ts.map +1 -1
  37. package/lib/aws/dynamodb/index.d.ts +2 -1
  38. package/lib/aws/dynamodb/index.d.ts.map +1 -1
  39. package/lib/aws/dynamodb/index.js +1 -2
  40. package/lib/aws/dynamodb/index.js.map +1 -1
  41. package/lib/aws/dynamodb/secondary-index.d.ts +5 -4
  42. package/lib/aws/dynamodb/secondary-index.d.ts.map +1 -1
  43. package/lib/aws/dynamodb/table.d.ts +23 -20
  44. package/lib/aws/dynamodb/table.d.ts.map +1 -1
  45. package/lib/aws/dynamodb/table.get-item.d.ts +8 -6
  46. package/lib/aws/dynamodb/table.get-item.d.ts.map +1 -1
  47. package/lib/aws/dynamodb/table.get-item.js +4 -2
  48. package/lib/aws/dynamodb/table.get-item.js.map +1 -1
  49. package/lib/aws/dynamodb/table.js.map +1 -1
  50. package/lib/aws/dynamodb/table.provider.d.ts +3 -4
  51. package/lib/aws/dynamodb/table.provider.d.ts.map +1 -1
  52. package/lib/aws/dynamodb/table.provider.js +18 -29
  53. package/lib/aws/dynamodb/table.provider.js.map +1 -1
  54. package/lib/aws/ec2/client.d.ts +1 -1
  55. package/lib/aws/ec2/client.d.ts.map +1 -1
  56. package/lib/aws/ec2/index.d.ts +11 -0
  57. package/lib/aws/ec2/index.d.ts.map +1 -1
  58. package/lib/aws/ec2/index.js +11 -0
  59. package/lib/aws/ec2/index.js.map +1 -1
  60. package/lib/aws/ec2/internet-gateway.d.ts +65 -0
  61. package/lib/aws/ec2/internet-gateway.d.ts.map +1 -0
  62. package/lib/aws/ec2/internet-gateway.js +4 -0
  63. package/lib/aws/ec2/internet-gateway.js.map +1 -0
  64. package/lib/aws/ec2/internet-gateway.provider.d.ts +6 -0
  65. package/lib/aws/ec2/internet-gateway.provider.d.ts.map +1 -0
  66. package/lib/aws/ec2/internet-gateway.provider.js +193 -0
  67. package/lib/aws/ec2/internet-gateway.provider.js.map +1 -0
  68. package/lib/aws/ec2/route-table-association.d.ts +63 -0
  69. package/lib/aws/ec2/route-table-association.d.ts.map +1 -0
  70. package/lib/aws/ec2/route-table-association.js +4 -0
  71. package/lib/aws/ec2/route-table-association.js.map +1 -0
  72. package/lib/aws/ec2/route-table-association.provider.d.ts +4 -0
  73. package/lib/aws/ec2/route-table-association.provider.d.ts.map +1 -0
  74. package/lib/aws/ec2/route-table-association.provider.js +121 -0
  75. package/lib/aws/ec2/route-table-association.provider.js.map +1 -0
  76. package/lib/aws/ec2/route-table.d.ts +159 -0
  77. package/lib/aws/ec2/route-table.d.ts.map +1 -0
  78. package/lib/aws/ec2/route-table.js +4 -0
  79. package/lib/aws/ec2/route-table.js.map +1 -0
  80. package/lib/aws/ec2/route-table.provider.d.ts +6 -0
  81. package/lib/aws/ec2/route-table.provider.d.ts.map +1 -0
  82. package/lib/aws/ec2/route-table.provider.js +213 -0
  83. package/lib/aws/ec2/route-table.provider.js.map +1 -0
  84. package/lib/aws/ec2/route.d.ts +155 -0
  85. package/lib/aws/ec2/route.d.ts.map +1 -0
  86. package/lib/aws/ec2/route.js +3 -0
  87. package/lib/aws/ec2/route.js.map +1 -0
  88. package/lib/aws/ec2/route.provider.d.ts +4 -0
  89. package/lib/aws/ec2/route.provider.d.ts.map +1 -0
  90. package/lib/aws/ec2/route.provider.js +166 -0
  91. package/lib/aws/ec2/route.provider.js.map +1 -0
  92. package/lib/aws/ec2/subnet.d.ts +175 -0
  93. package/lib/aws/ec2/subnet.d.ts.map +1 -0
  94. package/lib/aws/ec2/subnet.js +4 -0
  95. package/lib/aws/ec2/subnet.js.map +1 -0
  96. package/lib/aws/ec2/subnet.provider.d.ts +4 -0
  97. package/lib/aws/ec2/subnet.provider.d.ts.map +1 -0
  98. package/lib/aws/ec2/subnet.provider.js +250 -0
  99. package/lib/aws/ec2/subnet.provider.js.map +1 -0
  100. package/lib/aws/ec2/vpc.d.ts +13 -8
  101. package/lib/aws/ec2/vpc.d.ts.map +1 -1
  102. package/lib/aws/ec2/vpc.js +1 -0
  103. package/lib/aws/ec2/vpc.js.map +1 -1
  104. package/lib/aws/ec2/vpc.provider.d.ts +1 -2
  105. package/lib/aws/ec2/vpc.provider.d.ts.map +1 -1
  106. package/lib/aws/ec2/vpc.provider.js +45 -37
  107. package/lib/aws/ec2/vpc.provider.js.map +1 -1
  108. package/lib/aws/index.d.ts +15 -19
  109. package/lib/aws/index.d.ts.map +1 -1
  110. package/lib/aws/index.js +8 -10
  111. package/lib/aws/index.js.map +1 -1
  112. package/lib/aws/lambda/consume.d.ts +10 -11
  113. package/lib/aws/lambda/consume.d.ts.map +1 -1
  114. package/lib/aws/lambda/consume.js +3 -3
  115. package/lib/aws/lambda/consume.js.map +1 -1
  116. package/lib/aws/lambda/function.d.ts +7 -7
  117. package/lib/aws/lambda/function.d.ts.map +1 -1
  118. package/lib/aws/lambda/function.handler.d.ts +1 -1
  119. package/lib/aws/lambda/function.handler.d.ts.map +1 -1
  120. package/lib/aws/lambda/function.handler.js.map +1 -1
  121. package/lib/aws/lambda/function.invoke.d.ts +6 -4
  122. package/lib/aws/lambda/function.invoke.d.ts.map +1 -1
  123. package/lib/aws/lambda/function.invoke.js +3 -1
  124. package/lib/aws/lambda/function.invoke.js.map +1 -1
  125. package/lib/aws/lambda/function.js +1 -1
  126. package/lib/aws/lambda/function.js.map +1 -1
  127. package/lib/aws/lambda/function.provider.d.ts +3 -2
  128. package/lib/aws/lambda/function.provider.d.ts.map +1 -1
  129. package/lib/aws/lambda/function.provider.js +28 -25
  130. package/lib/aws/lambda/function.provider.js.map +1 -1
  131. package/lib/aws/lambda/index.d.ts +1 -0
  132. package/lib/aws/lambda/index.d.ts.map +1 -1
  133. package/lib/aws/lambda/index.js +1 -0
  134. package/lib/aws/lambda/index.js.map +1 -1
  135. package/lib/aws/lambda/serve.d.ts +2 -4
  136. package/lib/aws/lambda/serve.d.ts.map +1 -1
  137. package/lib/aws/profile.d.ts +2 -2
  138. package/lib/aws/profile.d.ts.map +1 -1
  139. package/lib/aws/profile.js +1 -1
  140. package/lib/aws/profile.js.map +1 -1
  141. package/lib/aws/region.d.ts +14 -2
  142. package/lib/aws/region.d.ts.map +1 -1
  143. package/lib/aws/region.js +26 -1
  144. package/lib/aws/region.js.map +1 -1
  145. package/lib/aws/sqs/client.d.ts +1 -1
  146. package/lib/aws/sqs/client.d.ts.map +1 -1
  147. package/lib/aws/sqs/index.d.ts +1 -0
  148. package/lib/aws/sqs/index.d.ts.map +1 -1
  149. package/lib/aws/sqs/index.js +1 -0
  150. package/lib/aws/sqs/index.js.map +1 -1
  151. package/lib/aws/sqs/queue.consume.d.ts +1 -1
  152. package/lib/aws/sqs/queue.consume.d.ts.map +1 -1
  153. package/lib/aws/sqs/queue.consume.js +0 -1
  154. package/lib/aws/sqs/queue.consume.js.map +1 -1
  155. package/lib/aws/sqs/queue.d.ts +6 -4
  156. package/lib/aws/sqs/queue.d.ts.map +1 -1
  157. package/lib/aws/sqs/queue.event-source.d.ts +8 -6
  158. package/lib/aws/sqs/queue.event-source.d.ts.map +1 -1
  159. package/lib/aws/sqs/queue.event-source.js +26 -44
  160. package/lib/aws/sqs/queue.event-source.js.map +1 -1
  161. package/lib/aws/sqs/queue.js +1 -1
  162. package/lib/aws/sqs/queue.js.map +1 -1
  163. package/lib/aws/sqs/queue.provider.d.ts +2 -2
  164. package/lib/aws/sqs/queue.provider.d.ts.map +1 -1
  165. package/lib/aws/sqs/queue.provider.js +2 -1
  166. package/lib/aws/sqs/queue.provider.js.map +1 -1
  167. package/lib/aws/sqs/queue.send-message.d.ts +7 -5
  168. package/lib/aws/sqs/queue.send-message.d.ts.map +1 -1
  169. package/lib/aws/sqs/queue.send-message.js +4 -2
  170. package/lib/aws/sqs/queue.send-message.js.map +1 -1
  171. package/lib/binding.d.ts +12 -12
  172. package/lib/binding.d.ts.map +1 -1
  173. package/lib/binding.js.map +1 -1
  174. package/lib/cli/components/ApprovePlan.d.ts +2 -2
  175. package/lib/cli/components/ApprovePlan.d.ts.map +1 -1
  176. package/lib/cli/components/ApprovePlan.js.map +1 -1
  177. package/lib/cli/components/Plan.d.ts +2 -2
  178. package/lib/cli/components/Plan.d.ts.map +1 -1
  179. package/lib/cli/components/Plan.js.map +1 -1
  180. package/lib/cli/components/PlanProgress.d.ts +8 -4
  181. package/lib/cli/components/PlanProgress.d.ts.map +1 -1
  182. package/lib/cli/components/PlanProgress.js +11 -1
  183. package/lib/cli/components/PlanProgress.js.map +1 -1
  184. package/lib/cli/index.d.ts +384 -264
  185. package/lib/cli/index.d.ts.map +1 -1
  186. package/lib/cli/index.js +57 -65
  187. package/lib/cli/index.js.map +1 -1
  188. package/lib/cli/ink-service.d.ts +4 -0
  189. package/lib/cli/ink-service.d.ts.map +1 -0
  190. package/lib/cli/ink-service.js +43 -0
  191. package/lib/cli/ink-service.js.map +1 -0
  192. package/lib/cli/service.d.ts +21 -0
  193. package/lib/cli/service.d.ts.map +1 -0
  194. package/lib/cli/service.js +5 -0
  195. package/lib/cli/service.js.map +1 -0
  196. package/lib/cloudflare/account.d.ts +10 -0
  197. package/lib/cloudflare/account.d.ts.map +1 -0
  198. package/lib/cloudflare/account.js +24 -0
  199. package/lib/cloudflare/account.js.map +1 -0
  200. package/lib/cloudflare/api.d.ts +7 -7
  201. package/lib/cloudflare/api.d.ts.map +1 -1
  202. package/lib/cloudflare/api.js +18 -17
  203. package/lib/cloudflare/api.js.map +1 -1
  204. package/lib/cloudflare/config.d.ts +9 -0
  205. package/lib/cloudflare/config.d.ts.map +1 -0
  206. package/lib/cloudflare/config.js +1 -0
  207. package/lib/cloudflare/config.js.map +1 -0
  208. package/lib/cloudflare/index.d.ts +3 -1
  209. package/lib/cloudflare/index.d.ts.map +1 -1
  210. package/lib/cloudflare/index.js +3 -0
  211. package/lib/cloudflare/index.js.map +1 -1
  212. package/lib/cloudflare/kv/namespace.binding.d.ts +5 -3
  213. package/lib/cloudflare/kv/namespace.binding.d.ts.map +1 -1
  214. package/lib/cloudflare/kv/namespace.binding.js +1 -1
  215. package/lib/cloudflare/kv/namespace.binding.js.map +1 -1
  216. package/lib/cloudflare/kv/namespace.client.d.ts +1 -1
  217. package/lib/cloudflare/kv/namespace.d.ts +3 -2
  218. package/lib/cloudflare/kv/namespace.d.ts.map +1 -1
  219. package/lib/cloudflare/kv/namespace.js.map +1 -1
  220. package/lib/cloudflare/kv/namespace.provider.d.ts +3 -2
  221. package/lib/cloudflare/kv/namespace.provider.d.ts.map +1 -1
  222. package/lib/cloudflare/kv/namespace.provider.js +9 -7
  223. package/lib/cloudflare/kv/namespace.provider.js.map +1 -1
  224. package/lib/cloudflare/live.d.ts +5 -5
  225. package/lib/cloudflare/live.d.ts.map +1 -1
  226. package/lib/cloudflare/live.js +5 -8
  227. package/lib/cloudflare/live.js.map +1 -1
  228. package/lib/cloudflare/r2/bucket.binding.d.ts +5 -3
  229. package/lib/cloudflare/r2/bucket.binding.d.ts.map +1 -1
  230. package/lib/cloudflare/r2/bucket.binding.js +1 -1
  231. package/lib/cloudflare/r2/bucket.binding.js.map +1 -1
  232. package/lib/cloudflare/r2/bucket.d.ts +3 -2
  233. package/lib/cloudflare/r2/bucket.d.ts.map +1 -1
  234. package/lib/cloudflare/r2/bucket.js.map +1 -1
  235. package/lib/cloudflare/r2/bucket.provider.d.ts +3 -2
  236. package/lib/cloudflare/r2/bucket.provider.d.ts.map +1 -1
  237. package/lib/cloudflare/r2/bucket.provider.js +14 -8
  238. package/lib/cloudflare/r2/bucket.provider.js.map +1 -1
  239. package/lib/cloudflare/worker/assets.fetch.d.ts +3 -2
  240. package/lib/cloudflare/worker/assets.fetch.d.ts.map +1 -1
  241. package/lib/cloudflare/worker/assets.fetch.js +2 -1
  242. package/lib/cloudflare/worker/assets.fetch.js.map +1 -1
  243. package/lib/cloudflare/worker/assets.provider.d.ts +1 -1
  244. package/lib/cloudflare/worker/assets.provider.d.ts.map +1 -1
  245. package/lib/cloudflare/worker/index.d.ts +0 -1
  246. package/lib/cloudflare/worker/index.d.ts.map +1 -1
  247. package/lib/cloudflare/worker/worker.d.ts +5 -6
  248. package/lib/cloudflare/worker/worker.d.ts.map +1 -1
  249. package/lib/cloudflare/worker/worker.handler.d.ts +1 -1
  250. package/lib/cloudflare/worker/worker.handler.d.ts.map +1 -1
  251. package/lib/cloudflare/worker/worker.handler.js.map +1 -1
  252. package/lib/cloudflare/worker/worker.js.map +1 -1
  253. package/lib/cloudflare/worker/worker.provider.d.ts +3 -2
  254. package/lib/cloudflare/worker/worker.provider.d.ts.map +1 -1
  255. package/lib/cloudflare/worker/worker.provider.js +12 -7
  256. package/lib/cloudflare/worker/worker.provider.js.map +1 -1
  257. package/lib/cloudflare/worker/worker.serve.d.ts +7 -7
  258. package/lib/cloudflare/worker/worker.serve.d.ts.map +1 -1
  259. package/lib/cloudflare/worker/worker.serve.js.map +1 -1
  260. package/lib/data.d.ts +3 -0
  261. package/lib/data.d.ts.map +1 -0
  262. package/lib/data.js +8 -0
  263. package/lib/data.js.map +1 -0
  264. package/lib/destroy.d.ts +1 -1
  265. package/lib/destroy.d.ts.map +1 -1
  266. package/lib/destroy.js +1 -4
  267. package/lib/destroy.js.map +1 -1
  268. package/lib/diff.d.ts +18 -0
  269. package/lib/diff.d.ts.map +1 -0
  270. package/lib/diff.js +22 -0
  271. package/lib/diff.js.map +1 -0
  272. package/lib/env.d.ts +5 -0
  273. package/lib/env.d.ts.map +1 -1
  274. package/lib/env.js +15 -29
  275. package/lib/env.js.map +1 -1
  276. package/lib/event.d.ts +1 -1
  277. package/lib/event.d.ts.map +1 -1
  278. package/lib/exports.d.ts +9 -0
  279. package/lib/exports.d.ts.map +1 -0
  280. package/lib/exports.js +13 -0
  281. package/lib/exports.js.map +1 -0
  282. package/lib/index.d.ts +10 -2
  283. package/lib/index.d.ts.map +1 -1
  284. package/lib/index.js +10 -5
  285. package/lib/index.js.map +1 -1
  286. package/lib/input.d.ts +32 -0
  287. package/lib/input.d.ts.map +1 -0
  288. package/lib/input.js +1 -0
  289. package/lib/input.js.map +1 -0
  290. package/lib/instance-id.d.ts +8 -0
  291. package/lib/instance-id.d.ts.map +1 -0
  292. package/lib/instance-id.js +12 -0
  293. package/lib/instance-id.js.map +1 -0
  294. package/lib/output.d.ts +145 -0
  295. package/lib/output.d.ts.map +1 -0
  296. package/lib/output.js +283 -0
  297. package/lib/output.js.map +1 -0
  298. package/lib/physical-name.d.ts +14 -1
  299. package/lib/physical-name.d.ts.map +1 -1
  300. package/lib/physical-name.js +41 -2
  301. package/lib/physical-name.js.map +1 -1
  302. package/lib/plan.d.ts +84 -58
  303. package/lib/plan.d.ts.map +1 -1
  304. package/lib/plan.js +504 -166
  305. package/lib/plan.js.map +1 -1
  306. package/lib/policy.d.ts +3 -4
  307. package/lib/policy.d.ts.map +1 -1
  308. package/lib/policy.js +0 -1
  309. package/lib/policy.js.map +1 -1
  310. package/lib/provider.d.ts +39 -24
  311. package/lib/provider.d.ts.map +1 -1
  312. package/lib/provider.js +9 -0
  313. package/lib/provider.js.map +1 -1
  314. package/lib/ref.d.ts +14 -0
  315. package/lib/ref.d.ts.map +1 -0
  316. package/lib/ref.js +21 -0
  317. package/lib/ref.js.map +1 -0
  318. package/lib/resource.d.ts +13 -8
  319. package/lib/resource.d.ts.map +1 -1
  320. package/lib/resource.js.map +1 -1
  321. package/lib/runtime.d.ts +7 -6
  322. package/lib/runtime.d.ts.map +1 -1
  323. package/lib/runtime.js.map +1 -1
  324. package/lib/service.d.ts +9 -6
  325. package/lib/service.d.ts.map +1 -1
  326. package/lib/service.js.map +1 -1
  327. package/lib/stack.d.ts +60 -0
  328. package/lib/stack.d.ts.map +1 -0
  329. package/lib/stack.js +11 -0
  330. package/lib/stack.js.map +1 -0
  331. package/lib/stage.d.ts +39 -0
  332. package/lib/stage.d.ts.map +1 -0
  333. package/lib/stage.js +32 -0
  334. package/lib/stage.js.map +1 -0
  335. package/lib/state.d.ts +135 -17
  336. package/lib/state.d.ts.map +1 -1
  337. package/lib/state.js +34 -30
  338. package/lib/state.js.map +1 -1
  339. package/lib/tags.d.ts +15 -0
  340. package/lib/tags.d.ts.map +1 -1
  341. package/lib/tags.js +27 -0
  342. package/lib/tags.js.map +1 -1
  343. package/lib/test.d.ts +25 -4
  344. package/lib/test.d.ts.map +1 -1
  345. package/lib/test.js +54 -14
  346. package/lib/test.js.map +1 -1
  347. package/lib/todo.d.ts +3 -0
  348. package/lib/todo.d.ts.map +1 -0
  349. package/lib/todo.js +3 -0
  350. package/lib/todo.js.map +1 -0
  351. package/lib/tsconfig.test.tsbuildinfo +1 -1
  352. package/lib/type.d.ts +3 -0
  353. package/lib/type.d.ts.map +1 -1
  354. package/lib/unknown.d.ts +4 -0
  355. package/lib/unknown.d.ts.map +1 -0
  356. package/lib/unknown.js +4 -0
  357. package/lib/unknown.js.map +1 -0
  358. package/lib/user.d.ts +3 -0
  359. package/lib/user.d.ts.map +1 -0
  360. package/lib/user.js +3 -0
  361. package/lib/user.js.map +1 -0
  362. package/lib/util.d.ts +6 -0
  363. package/lib/util.d.ts.map +1 -0
  364. package/lib/util.js +9 -0
  365. package/lib/util.js.map +1 -0
  366. package/package.json +18 -12
  367. package/src/$.ts +17 -0
  368. package/src/app.ts +3 -32
  369. package/src/apply.ts +824 -452
  370. package/src/assert-never.ts +18 -0
  371. package/src/aws/account.ts +23 -3
  372. package/src/aws/client.ts +0 -1
  373. package/src/aws/config.ts +16 -0
  374. package/src/aws/credentials.ts +212 -177
  375. package/src/aws/dynamodb/index.ts +3 -3
  376. package/src/aws/dynamodb/table.get-item.ts +5 -9
  377. package/src/aws/dynamodb/table.provider.ts +36 -39
  378. package/src/aws/dynamodb/table.ts +29 -84
  379. package/src/aws/ec2/index.ts +12 -0
  380. package/src/aws/ec2/internet-gateway.provider.ts +316 -0
  381. package/src/aws/ec2/internet-gateway.ts +79 -0
  382. package/src/aws/ec2/route-table-association.provider.ts +214 -0
  383. package/src/aws/ec2/route-table-association.ts +82 -0
  384. package/src/aws/ec2/route-table.provider.ts +306 -0
  385. package/src/aws/ec2/route-table.ts +175 -0
  386. package/src/aws/ec2/route.provider.ts +213 -0
  387. package/src/aws/ec2/route.ts +192 -0
  388. package/src/aws/ec2/subnet.provider.ts +358 -0
  389. package/src/aws/ec2/subnet.ts +213 -0
  390. package/src/aws/ec2/vpc.provider.ts +58 -50
  391. package/src/aws/ec2/vpc.ts +21 -8
  392. package/src/aws/index.ts +49 -40
  393. package/src/aws/lambda/consume.ts +8 -7
  394. package/src/aws/lambda/function.handler.ts +1 -1
  395. package/src/aws/lambda/function.invoke.ts +6 -2
  396. package/src/aws/lambda/function.provider.ts +41 -32
  397. package/src/aws/lambda/function.ts +7 -4
  398. package/src/aws/lambda/index.ts +2 -0
  399. package/src/aws/profile.ts +1 -4
  400. package/src/aws/region.ts +42 -3
  401. package/src/aws/sqs/index.ts +2 -0
  402. package/src/aws/sqs/queue.consume.ts +1 -1
  403. package/src/aws/sqs/queue.event-source.ts +29 -55
  404. package/src/aws/sqs/queue.provider.ts +10 -2
  405. package/src/aws/sqs/queue.send-message.ts +5 -8
  406. package/src/aws/sqs/queue.ts +9 -4
  407. package/src/binding.ts +19 -19
  408. package/src/cli/components/ApprovePlan.tsx +2 -2
  409. package/src/cli/components/Plan.tsx +3 -2
  410. package/src/cli/components/PlanProgress.tsx +32 -14
  411. package/src/cli/index.ts +2 -6
  412. package/src/cli/ink-service.tsx +61 -0
  413. package/src/cli/service.ts +23 -0
  414. package/src/cloudflare/account.ts +37 -0
  415. package/src/cloudflare/api.ts +33 -29
  416. package/src/cloudflare/config.ts +7 -0
  417. package/src/cloudflare/index.ts +3 -1
  418. package/src/cloudflare/kv/namespace.binding.ts +3 -1
  419. package/src/cloudflare/kv/namespace.provider.ts +10 -8
  420. package/src/cloudflare/kv/namespace.ts +3 -2
  421. package/src/cloudflare/live.ts +11 -17
  422. package/src/cloudflare/r2/bucket.binding.ts +3 -1
  423. package/src/cloudflare/r2/bucket.provider.ts +16 -9
  424. package/src/cloudflare/r2/bucket.ts +8 -2
  425. package/src/cloudflare/worker/assets.fetch.ts +3 -1
  426. package/src/cloudflare/worker/assets.provider.ts +1 -1
  427. package/src/cloudflare/worker/index.ts +0 -2
  428. package/src/cloudflare/worker/worker.handler.ts +1 -1
  429. package/src/cloudflare/worker/worker.provider.ts +21 -14
  430. package/src/cloudflare/worker/worker.serve.ts +5 -2
  431. package/src/cloudflare/worker/worker.ts +4 -3
  432. package/src/data.ts +18 -0
  433. package/src/destroy.ts +1 -5
  434. package/src/diff.ts +48 -0
  435. package/src/env.ts +20 -32
  436. package/src/event.ts +6 -0
  437. package/src/exports.ts +21 -0
  438. package/src/index.ts +10 -5
  439. package/src/input.ts +81 -0
  440. package/src/instance-id.ts +16 -0
  441. package/src/output.ts +542 -0
  442. package/src/physical-name.ts +57 -2
  443. package/src/plan.ts +757 -278
  444. package/src/policy.ts +3 -5
  445. package/src/provider.ts +70 -31
  446. package/src/ref.ts +48 -0
  447. package/src/resource.ts +70 -10
  448. package/src/runtime.ts +15 -8
  449. package/src/service.ts +11 -7
  450. package/src/stack.ts +116 -0
  451. package/src/stage.ts +85 -0
  452. package/src/state.ts +269 -76
  453. package/src/tags.ts +31 -0
  454. package/src/test.ts +118 -17
  455. package/src/todo.ts +4 -0
  456. package/src/type.ts +4 -0
  457. package/src/unknown.ts +6 -0
  458. package/src/user.ts +4 -0
  459. package/src/util.ts +21 -0
  460. package/lib/approve.d.ts +0 -15
  461. package/lib/approve.d.ts.map +0 -1
  462. package/lib/approve.js +0 -7
  463. package/lib/approve.js.map +0 -1
  464. package/lib/cli/approve.d.ts +0 -4
  465. package/lib/cli/approve.d.ts.map +0 -1
  466. package/lib/cli/approve.js +0 -18
  467. package/lib/cli/approve.js.map +0 -1
  468. package/lib/cli/clack.d.ts +0 -14
  469. package/lib/cli/clack.d.ts.map +0 -1
  470. package/lib/cli/clack.js +0 -12
  471. package/lib/cli/clack.js.map +0 -1
  472. package/lib/cli/main.d.ts +0 -2
  473. package/lib/cli/main.d.ts.map +0 -1
  474. package/lib/cli/main.js +0 -1
  475. package/lib/cli/main.js.map +0 -1
  476. package/lib/cli/plan.d.ts +0 -13
  477. package/lib/cli/plan.d.ts.map +0 -1
  478. package/lib/cli/plan.js +0 -1
  479. package/lib/cli/plan.js.map +0 -1
  480. package/lib/cli/progress.d.ts +0 -7
  481. package/lib/cli/progress.d.ts.map +0 -1
  482. package/lib/cli/progress.js +0 -30
  483. package/lib/cli/progress.js.map +0 -1
  484. package/lib/cli/spinner.d.ts +0 -2
  485. package/lib/cli/spinner.d.ts.map +0 -1
  486. package/lib/cli/spinner.js +0 -13
  487. package/lib/cli/spinner.js.map +0 -1
  488. package/src/approve.ts +0 -13
  489. package/src/cli/approve.tsx +0 -30
  490. package/src/cli/clack.ts +0 -22
  491. package/src/cli/main.ts +0 -0
  492. package/src/cli/plan.ts +0 -16
  493. package/src/cli/progress.tsx +0 -46
  494. package/src/cli/spinner.ts +0 -14
package/src/plan.ts CHANGED
@@ -2,18 +2,32 @@ import * as Context from "effect/Context";
2
2
  import * as Data from "effect/Data";
3
3
  import * as Effect from "effect/Effect";
4
4
  import { omit } from "effect/Struct";
5
+ import { App } from "./app.ts";
5
6
  import type {
6
7
  AnyBinding,
7
8
  BindingDiffProps,
8
9
  BindingService,
9
10
  } from "./binding.ts";
10
11
  import type { Capability } from "./capability.ts";
11
- import type { Phase } from "./phase.ts";
12
+ import type { Diff, NoopDiff, UpdateDiff } from "./diff.ts";
13
+ import * as Output from "./output.ts";
12
14
  import type { Instance } from "./policy.ts";
13
- import { type Diff, type ProviderService } from "./provider.ts";
14
- import type { Resource, ResourceTags } from "./resource.ts";
15
+ import type { Provider } from "./provider.ts";
16
+ import { type ProviderService, getProviderByType } from "./provider.ts";
17
+ import type { AnyResource, Resource, ResourceTags } from "./resource.ts";
15
18
  import { isService, type IService, type Service } from "./service.ts";
16
- import { State, StateStoreError, type ResourceState } from "./state.ts";
19
+ import {
20
+ type CreatedResourceState,
21
+ type CreatingResourceState,
22
+ type ReplacedResourceState,
23
+ type ReplacingResourceState,
24
+ type UpdatedResourceState,
25
+ type UpdatingReourceState,
26
+ State,
27
+ StateStoreError,
28
+ type ResourceState,
29
+ } from "./state.ts";
30
+ import { asEffect } from "./util.ts";
17
31
 
18
32
  export type PlanError = never;
19
33
 
@@ -76,311 +90,669 @@ export const isCRUD = (node: any): node is CRUD => {
76
90
  /**
77
91
  * A node in the plan that represents a resource CRUD operation.
78
92
  */
79
- export type CRUD<R extends Resource = Resource> =
93
+ export type CRUD<R extends Resource = AnyResource> =
80
94
  | Create<R>
81
95
  | Update<R>
82
96
  | Delete<R>
83
97
  | Replace<R>
84
98
  | NoopUpdate<R>;
85
99
 
86
- export type Apply<R extends Resource = Resource> =
100
+ export type Apply<R extends Resource = AnyResource> =
87
101
  | Create<R>
88
102
  | Update<R>
89
103
  | Replace<R>
90
104
  | NoopUpdate<R>;
91
105
 
92
- const Node = <T extends Apply>(node: T) => ({
93
- ...node,
94
- toString(): string {
95
- return `${this.action.charAt(0).toUpperCase()}${this.action.slice(1)}(${this.resource})`;
96
- },
97
- [Symbol.toStringTag]() {
98
- return this.toString();
99
- },
100
- });
101
-
102
- export type Create<R extends Resource> = {
103
- action: "create";
106
+ export interface BaseNode<R extends Resource = AnyResource> {
104
107
  resource: R;
105
- news: any;
106
- provider: ProviderService;
107
- attributes: R["attr"];
108
+ provider: ProviderService<R>;
108
109
  bindings: BindNode[];
109
- };
110
+ downstream: string[];
111
+ }
112
+
113
+ export interface Create<R extends Resource = AnyResource> extends BaseNode<R> {
114
+ action: "create";
115
+ props: any;
116
+ state: CreatingResourceState | undefined;
117
+ }
110
118
 
111
- export type Update<R extends Resource> = {
119
+ export interface Update<R extends Resource = AnyResource> extends BaseNode<R> {
112
120
  action: "update";
113
- resource: R;
114
- olds: any;
115
- news: any;
116
- output: any;
117
- provider: ProviderService;
118
- attributes: R["attr"];
119
- bindings: BindNode[];
120
- };
121
+ props: any;
122
+ state:
123
+ | CreatedResourceState
124
+ | UpdatedResourceState
125
+ | UpdatingReourceState
126
+ // the props can change after creating the replacement resource,
127
+ // so Apply needs to handle updates and then continue with cleaning up the replaced graph
128
+ | ReplacedResourceState;
129
+ }
121
130
 
122
- export type Delete<R extends Resource> = {
131
+ export interface Delete<R extends Resource = AnyResource> extends BaseNode<R> {
123
132
  action: "delete";
124
- resource: R;
125
- olds: any;
126
- output: any;
127
- provider: ProviderService;
128
- bindings: BindNode[];
129
- attributes: R["attr"];
130
- downstream: string[];
131
- };
133
+ // a resource can be deleted no matter what state it's in
134
+ state: ResourceState;
135
+ }
132
136
 
133
- export type NoopUpdate<R extends Resource> = {
137
+ export interface NoopUpdate<R extends Resource = AnyResource>
138
+ extends BaseNode<R> {
134
139
  action: "noop";
135
- resource: R;
136
- attributes: R["attr"];
137
- bindings: BindNode[];
138
- };
140
+ state: CreatedResourceState | UpdatedResourceState;
141
+ }
139
142
 
140
- export type Replace<R extends Resource> = {
143
+ export interface Replace<R extends Resource = AnyResource> extends BaseNode<R> {
141
144
  action: "replace";
142
- resource: R;
143
- olds: any;
144
- news: any;
145
- output: any;
146
- provider: ProviderService;
147
- bindings: BindNode[];
148
- attributes: R["attr"];
149
- deleteFirst?: boolean;
145
+ props: any;
146
+ deleteFirst: boolean;
147
+ state:
148
+ | CreatingResourceState
149
+ | CreatedResourceState
150
+ | UpdatingReourceState
151
+ | UpdatedResourceState
152
+ | ReplacingResourceState
153
+ | ReplacedResourceState;
154
+ }
155
+
156
+ export type ResourceGraph<Resources extends Service | Resource> = ToGraph<
157
+ TraverseResources<Resources>
158
+ >;
159
+
160
+ export type TraverseResources<Resources extends Service | Resource> =
161
+ | Resources
162
+ | BoundResources<Resources>
163
+ | TransitiveResources<Resources>;
164
+
165
+ type ToGraph<Resources extends Service | Resource> = {
166
+ [ID in Resources["id"]]: Apply<Extract<Resources, { id: ID }>>;
150
167
  };
151
168
 
152
- export type Plan = {
153
- phase: Phase;
169
+ export type BoundResources<Resources extends Service | Resource> = NeverUnknown<
170
+ Extract<
171
+ Resources,
172
+ IService
173
+ >["props"]["bindings"]["capabilities"][number]["resource"]
174
+ >;
175
+
176
+ // finds transitive dependencies at most two levels deep
177
+ // TODO(sam): figure out an efficient way to do arbitrary depth
178
+ export type TransitiveResources<
179
+ Resources extends Service | Resource,
180
+ Found extends Service | Resource = never,
181
+ > = Extract<
182
+ | Found
183
+ | {
184
+ [prop in keyof Resources["props"]]: IsAny<
185
+ Resources["props"][prop]
186
+ > extends true
187
+ ? Found
188
+ : Resources["props"][prop] extends { kind: "alchemy/Policy" }
189
+ ? Found
190
+ : Resources["props"][prop] extends Output.Output<any, infer Src, any>
191
+ ? Src extends Found
192
+ ? Found
193
+ : TransitiveResources<Src, Src | Found>
194
+ : {
195
+ [p in keyof Resources["props"][prop]]: IsAny<
196
+ Resources["props"][prop][p]
197
+ > extends true
198
+ ? Found
199
+ : Resources["props"][prop][p] extends Output.Output<
200
+ any,
201
+ infer Src,
202
+ any
203
+ >
204
+ ? Src extends Found
205
+ ? Found
206
+ : TransitiveResources<Src, Src | Found>
207
+ : Found;
208
+ }[keyof Resources["props"][prop]];
209
+ }[keyof Resources["props"]],
210
+ Service | Resource
211
+ >;
212
+
213
+ export type Providers<Resources extends Service | Resource> =
214
+ | ResourceProviders<Resources>
215
+ | BindingTags<Resources>;
216
+
217
+ export type ResourceProviders<Res extends Service | Resource> = Res extends any
218
+ ? Provider<Extract<Res["base"], Service | Resource>>
219
+ : never;
220
+
221
+ export type BindingTags<Resources extends Service | Resource> = NeverUnknown<
222
+ Extract<Resources, Service>["props"]["bindings"]["tags"][number]
223
+ >;
224
+
225
+ type NeverUnknown<T> = unknown extends T ? never : T;
226
+
227
+ type IsAny<T> = 0 extends 1 & T ? true : false;
228
+
229
+ export type DerivePlan<Resources extends Service | Resource> = {
154
230
  resources: {
155
- [id in string]: CRUD;
231
+ [ID in keyof ResourceGraph<Resources>]: ResourceGraph<Resources>[ID];
156
232
  };
157
233
  deletions: {
158
- [id in string]?: Delete<Resource>;
234
+ [ID in string]: Delete<AnyResource>;
159
235
  };
160
236
  };
161
237
 
162
- export const plan = <
163
- const Phase extends "update" | "destroy",
164
- const Resources extends (Service | Resource)[],
165
- >({
166
- phase,
167
- resources,
168
- }: {
169
- phase: Phase;
170
- resources: Resources;
171
- }) => {
172
- type Services = Extract<Resources[number], IService>[];
173
- type ServiceIDs = Services[number]["id"];
174
- type ServiceHosts = {
175
- [ID in ServiceIDs]: Extract<Services[number], Service<Extract<ID, string>>>;
238
+ export type IPlan = {
239
+ resources: {
240
+ [id in string]: Apply<any>;
176
241
  };
177
- type UpstreamTags = {
178
- [ID in ServiceIDs]: ServiceHosts[ID]["props"]["bindings"]["tags"][number];
179
- }[ServiceIDs];
180
- type UpstreamResources = {
181
- [ID in ServiceIDs]: Extract<
182
- ServiceHosts[ID]["props"]["bindings"]["capabilities"][number]["resource"],
183
- Resource
184
- >;
185
- }[ServiceIDs];
186
- type ExplicitResources = Resources[number];
187
- type ResourceGraph = {
188
- [ID in ServiceIDs]: Apply<Extract<Instance<ServiceHosts[ID]>, Resource>>;
189
- } & {
190
- [ID in UpstreamResources["id"]]: Apply<
191
- Extract<UpstreamResources, { id: ID }>
192
- >;
193
- } & {
194
- [ID in ExplicitResources["id"]]: Apply<
195
- Extract<ExplicitResources, { id: ID }>
196
- >;
242
+ deletions: {
243
+ [id in string]?: Delete<Resource>;
197
244
  };
245
+ };
198
246
 
199
- return Effect.gen(function* () {
247
+ export type Plan<Resources extends Service | Resource> = Effect.Effect<
248
+ DerivePlan<Resources>,
249
+ | CannotReplacePartiallyReplacedResource
250
+ | DeleteResourceHasDownstreamDependencies,
251
+ Providers<Resources> | State
252
+ >;
253
+
254
+ export const plan = <const Resources extends (Service | Resource)[]>(
255
+ ..._resources: Resources
256
+ ): Plan<Instance<Resources[number]>> =>
257
+ Effect.gen(function* () {
200
258
  const state = yield* State;
201
259
 
202
- const resourceIds = yield* state.list();
203
- const resourcesState = yield* Effect.all(
204
- resourceIds.map((id) => state.get(id)),
260
+ const findResources = (
261
+ resource: Service | Resource,
262
+ visited: Set<string>,
263
+ ): (Service | Resource)[] => {
264
+ if (visited.has(resource.id)) {
265
+ return [];
266
+ }
267
+ visited.add(resource.id);
268
+ const upstream = Object.values(Output.upstreamAny(resource.props)) as (
269
+ | Service
270
+ | Resource
271
+ )[];
272
+ return [
273
+ resource,
274
+ ...upstream,
275
+ ...upstream.flatMap((r) => findResources(r, visited)),
276
+ ];
277
+ };
278
+ const resources = _resources
279
+ .flatMap((r) => findResources(r, new Set()))
280
+ .filter((r, i, arr) => arr.findIndex((r2) => r2.id === r.id) === i);
281
+
282
+ // TODO(sam): rename terminology to Stack
283
+ const app = yield* App;
284
+
285
+ const resourceIds = yield* state.list({
286
+ stack: app.name,
287
+ stage: app.stage,
288
+ });
289
+ const oldResources = yield* Effect.all(
290
+ resourceIds.map((id) =>
291
+ state.get({ stack: app.name, stage: app.stage, resourceId: id }),
292
+ ),
205
293
  );
294
+
295
+ type ResolveEffect<T> = Effect.Effect<T, ResolveErr, ResolveReq>;
296
+ type ResolveErr = StateStoreError;
297
+ type ResolveReq =
298
+ | Context.TagClass<
299
+ Provider<Resource<string, string, any, any>>,
300
+ string,
301
+ ProviderService<Resource<string, string, any, any>>
302
+ >
303
+ | State;
304
+
305
+ const resolvedResources: Record<
306
+ string,
307
+ ResolveEffect<
308
+ | {
309
+ [attr in string]: any;
310
+ }
311
+ | undefined
312
+ >
313
+ > = {};
314
+
315
+ const resolveResource = (
316
+ resourceExpr: Output.ResourceExpr<any, any, any>,
317
+ ) =>
318
+ Effect.gen(function* () {
319
+ return yield* (resolvedResources[resourceExpr.src.id] ??=
320
+ yield* Effect.cached(
321
+ Effect.gen(function* () {
322
+ const resource = resourceExpr.src as Resource & {
323
+ provider: ResourceTags<Resource<string, string, any, any>>;
324
+ };
325
+ const provider = yield* resource.provider.tag;
326
+ const props = yield* resolveInput(resource.props);
327
+ const oldState = yield* state.get({
328
+ stack: app.name,
329
+ stage: app.stage,
330
+ resourceId: resource.id,
331
+ });
332
+
333
+ if (!oldState || oldState.status === "creating") {
334
+ return resourceExpr;
335
+ }
336
+
337
+ const oldProps =
338
+ oldState.status === "created" ||
339
+ oldState.status === "updated" ||
340
+ oldState.status === "replaced"
341
+ ? // if we're in a stable state, then just use the props
342
+ oldState.props
343
+ : // if we failed to update or replace, compare with the last known stable props
344
+ oldState.status === "updating" ||
345
+ oldState.status === "replacing"
346
+ ? oldState.old.props
347
+ : // TODO(sam): it kinda doesn't make sense to diff with a "deleting" state
348
+ oldState.props;
349
+
350
+ const diff = yield* provider.diff
351
+ ? provider.diff({
352
+ id: resource.id,
353
+ olds: oldProps,
354
+ instanceId: oldState.instanceId,
355
+ news: props,
356
+ output: oldState.attr,
357
+ })
358
+ : Effect.succeed(undefined);
359
+
360
+ const stables: string[] = [
361
+ ...(provider.stables ?? []),
362
+ ...(diff?.stables ?? []),
363
+ ];
364
+
365
+ const withStables = (output: any) =>
366
+ stables.length > 0
367
+ ? new Output.ResourceExpr(
368
+ resourceExpr.src,
369
+ Object.fromEntries(
370
+ stables.map((stable) => [stable, output?.[stable]]),
371
+ ),
372
+ )
373
+ : // if there are no stable properties, treat every property as changed
374
+ resourceExpr;
375
+
376
+ if (diff == null) {
377
+ if (arePropsChanged(oldProps, props)) {
378
+ // the props have changed but the provider did not provide any hints as to what is stable
379
+ // so we must assume everything has changed
380
+ return withStables(oldState?.attr);
381
+ }
382
+ } else if (diff.action === "update") {
383
+ return withStables(oldState?.attr);
384
+ } else if (diff.action === "replace") {
385
+ return resourceExpr;
386
+ }
387
+ if (
388
+ oldState.status === "created" ||
389
+ oldState.status === "updated" ||
390
+ oldState.status === "replaced"
391
+ ) {
392
+ // we can safely return the attributes if we know they have stabilized
393
+ return oldState?.attr;
394
+ } else {
395
+ // we must assume the resource doesn't exist if it hasn't stabilized
396
+ return resourceExpr;
397
+ }
398
+ }),
399
+ ));
400
+ });
401
+
402
+ const resolveInput = (input: any): ResolveEffect<any> =>
403
+ Effect.gen(function* () {
404
+ if (!input) {
405
+ return input;
406
+ } else if (Output.isExpr(input)) {
407
+ return yield* resolveOutput(input);
408
+ } else if (Array.isArray(input)) {
409
+ return yield* Effect.all(input.map(resolveInput));
410
+ } else if (typeof input === "object") {
411
+ return Object.fromEntries(
412
+ yield* Effect.all(
413
+ Object.entries(input).map(([key, value]) =>
414
+ resolveInput(value).pipe(Effect.map((value) => [key, value])),
415
+ ),
416
+ ),
417
+ );
418
+ }
419
+ return input;
420
+ });
421
+
422
+ const resolveOutput = (expr: Output.Expr<any>): ResolveEffect<any> =>
423
+ Effect.gen(function* () {
424
+ if (Output.isResourceExpr(expr)) {
425
+ return yield* resolveResource(expr);
426
+ } else if (Output.isPropExpr(expr)) {
427
+ const upstream = yield* resolveOutput(expr.expr);
428
+ return upstream?.[expr.identifier];
429
+ } else if (Output.isApplyExpr(expr)) {
430
+ const upstream = yield* resolveOutput(expr.expr);
431
+ return Output.hasOutputs(upstream) ? expr : expr.f(upstream);
432
+ } else if (Output.isEffectExpr(expr)) {
433
+ const upstream = yield* resolveOutput(expr.expr);
434
+ return Output.hasOutputs(upstream) ? expr : yield* expr.f(upstream);
435
+ } else if (Output.isAllExpr(expr)) {
436
+ return yield* Effect.all(expr.outs.map(resolveOutput));
437
+ }
438
+ return yield* Effect.die(new Error("Not implemented yet"));
439
+ });
440
+
206
441
  // map of resource ID -> its downstream dependencies (resources that depend on it)
207
- const downstream = resourcesState
208
- .filter(
209
- (
210
- resource,
211
- ): resource is ResourceState & {
212
- bindings: BindNode[];
213
- } => !!resource?.bindings,
214
- )
215
- .flatMap((resource) =>
216
- resource.bindings.flatMap(({ binding }) => [
217
- [binding.capability.resource.id, binding.capability.resource],
218
- ]),
219
- )
220
- .reduce(
221
- (acc, [id, resourceId]) => ({
222
- ...acc,
223
- [id]: [...(acc[id] ?? []), resourceId],
224
- }),
225
- {} as Record<string, string[]>,
226
- );
442
+ const oldDownstreamDependencies: {
443
+ [resourceId: string]: string[];
444
+ } = Object.fromEntries(
445
+ oldResources
446
+ .filter((resource) => !!resource)
447
+ .map((resource) => [resource.logicalId, resource.downstream]),
448
+ );
227
449
 
228
- const resourceGraph =
229
- phase === "update"
230
- ? (Object.fromEntries(
231
- (yield* Effect.all(
232
- resources
233
- .flatMap((resource) => [
234
- ...(isService(resource)
235
- ? resource.props.bindings.capabilities.map(
236
- (cap: Capability) => cap.resource as Resource,
237
- )
238
- : []),
239
- resource,
240
- ])
241
- .filter(
242
- (node, i, arr) =>
243
- arr.findIndex((n) => n.id === node.id) === i,
450
+ const newUpstreamDependencies: {
451
+ [resourceId: string]: string[];
452
+ } = Object.fromEntries(
453
+ resources.map((resource) => [
454
+ resource.id,
455
+ [
456
+ ...Object.values(Output.upstreamAny(resource.props)).map((r) => r.id),
457
+ ...(isService(resource)
458
+ ? resource.props.bindings.capabilities.map((cap) => cap.resource.id)
459
+ : []),
460
+ ],
461
+ ]),
462
+ );
463
+
464
+ const newDownstreamDependencies: {
465
+ [resourceId: string]: string[];
466
+ } = Object.fromEntries(
467
+ resources.map((resource) => [
468
+ resource.id,
469
+ Object.entries(newUpstreamDependencies)
470
+ .filter(([_, downstream]) => downstream.includes(resource.id))
471
+ .map(([id]) => id),
472
+ ]),
473
+ );
474
+
475
+ const resourceGraph = Object.fromEntries(
476
+ (yield* Effect.all(
477
+ resources
478
+ .flatMap((resource) => [
479
+ ...(isService(resource)
480
+ ? resource.props.bindings.capabilities.map(
481
+ (cap: Capability) => cap.resource as Resource,
244
482
  )
245
- .map(
246
- Effect.fn(function* (node) {
247
- const id = node.id;
248
- const resource = node as Resource & {
249
- provider: ResourceTags<Resource>;
250
- };
251
- const news = resource.props;
252
-
253
- const oldState = yield* state.get(id);
254
- const provider = yield* resource.provider.tag;
255
-
256
- const bindings = isService(node)
257
- ? yield* diffBindings({
258
- oldState,
259
- bindings: (
260
- node.props.bindings as unknown as {
261
- bindings: AnyBinding[];
262
- }
263
- ).bindings,
264
- target: {
265
- id: node.id,
266
- props: node.props,
267
- oldAttr: oldState?.output,
268
- oldProps: oldState?.props,
269
- },
270
- })
271
- : []; // TODO(sam): return undefined instead of empty array
272
-
273
- if (
274
- oldState === undefined ||
275
- oldState.status === "creating"
276
- ) {
277
- return Node<Create<Resource>>({
278
- action: "create",
279
- news,
280
- provider,
281
- resource,
282
- bindings,
283
- // phantom
284
- attributes: undefined!,
285
- });
286
- }
287
-
288
- const diff = provider.diff
289
- ? yield* provider.diff({
290
- id,
291
- olds: oldState.props,
292
- news,
293
- output: oldState.output,
294
- })
295
- : undefined;
296
-
297
- if (!diff && arePropsChanged(oldState, resource.props)) {
298
- return Node<Update<Resource>>({
299
- action: "update",
300
- olds: oldState.props,
301
- news,
302
- output: oldState.output,
303
- provider,
304
- resource,
305
- bindings,
306
- // phantom
307
- attributes: undefined!,
308
- });
309
- } else if (diff?.action === "replace") {
310
- return Node<Replace<Resource>>({
311
- action: "replace",
312
- olds: oldState.props,
313
- news,
314
- output: oldState.output,
315
- provider,
316
- resource,
317
- bindings,
318
- // phantom
319
- attributes: undefined!,
320
- });
321
- } else if (diff?.action === "update") {
322
- return Node<Update<Resource>>({
323
- action: "update",
324
- olds: oldState.props,
325
- news,
326
- output: oldState.output,
327
- provider,
328
- resource,
329
- bindings,
330
- // phantom
331
- attributes: undefined!,
332
- });
333
- } else {
334
- return Node<NoopUpdate<Resource>>({
335
- action: "noop",
336
- resource,
337
- bindings,
338
- // phantom
339
- attributes: undefined!,
340
- });
341
- }
342
- }),
483
+ : []),
484
+ ...Object.values(Output.upstreamAny(resource.props)),
485
+ resource,
486
+ ])
487
+ .filter(
488
+ (node, i, arr) => arr.findIndex((n) => n.id === node.id) === i,
489
+ )
490
+ .map(
491
+ Effect.fn(function* (node) {
492
+ const id = node.id;
493
+ const resource = node as Resource & {
494
+ provider: ResourceTags<Resource<string, string, any, any>>;
495
+ };
496
+ const news = yield* resolveInput(resource.props);
497
+
498
+ const oldState = yield* state.get({
499
+ stack: app.name,
500
+ stage: app.stage,
501
+ resourceId: id,
502
+ });
503
+ const provider = yield* resource.provider.tag;
504
+
505
+ const downstream = newDownstreamDependencies[id] ?? [];
506
+
507
+ const bindings = isService(node)
508
+ ? yield* diffBindings({
509
+ oldState,
510
+ bindings: (
511
+ node.props.bindings as unknown as {
512
+ bindings: AnyBinding[];
513
+ }
514
+ ).bindings,
515
+ target: {
516
+ id: node.id,
517
+ props: node.props,
518
+ // TODO(sam): pick the right ones based on old status
519
+ oldAttr: oldState?.attr,
520
+ oldProps: oldState?.props,
521
+ },
522
+ })
523
+ : []; // TODO(sam): return undefined instead of empty array
524
+
525
+ const Node = <T extends Apply>(
526
+ node: Omit<
527
+ T,
528
+ "provider" | "resource" | "bindings" | "downstream"
529
+ >,
530
+ ) =>
531
+ ({
532
+ ...node,
533
+ provider,
534
+ resource,
535
+ bindings,
536
+ downstream,
537
+ }) as any as T;
538
+
539
+ // handle empty and intermediate (non-final) states:
540
+ if (oldState === undefined) {
541
+ return Node<Create<Resource>>({
542
+ action: "create",
543
+ props: news,
544
+ state: oldState,
545
+ });
546
+ }
547
+
548
+ // TODO(sam): is this correct for all possible states a resource can be in?
549
+ const oldProps = oldState.props;
550
+
551
+ const diff = yield* asEffect(
552
+ provider.diff
553
+ ? provider.diff({
554
+ id,
555
+ olds: oldProps,
556
+ instanceId: oldState.instanceId,
557
+ output: oldState.attr,
558
+ news,
559
+ })
560
+ : undefined,
561
+ ).pipe(
562
+ Effect.map(
563
+ (diff) =>
564
+ diff ??
565
+ ({
566
+ action: arePropsChanged(oldProps, news)
567
+ ? "update"
568
+ : "noop",
569
+ } as UpdateDiff | NoopDiff),
343
570
  ),
344
- )).map((update) => [update.resource.id, update]),
345
- ) as Plan["resources"])
346
- : ({} as Plan["resources"]);
571
+ );
572
+
573
+ if (oldState.status === "creating") {
574
+ if (diff.action === "noop") {
575
+ // we're in the creating state and props are un-changed
576
+ // let's just continue where we left off
577
+ return Node<Create<Resource>>({
578
+ action: "create",
579
+ props: news,
580
+ state: oldState,
581
+ });
582
+ } else if (diff.action === "update") {
583
+ // props have changed in a way that is updatable
584
+ // again, just continue with the create
585
+ // TODO(sam): should we maybe try an update instead?
586
+ return Node<Create<Resource>>({
587
+ action: "create",
588
+ props: news,
589
+ state: oldState,
590
+ });
591
+ } else {
592
+ // props have changed in an incompatible way
593
+ // because it's possible that an un-updatable resource has already been created
594
+ // we must use a replace step to create a new one and delete the potential old one
595
+ return Node<Replace<Resource>>({
596
+ action: "replace",
597
+ props: news,
598
+ deleteFirst: diff.deleteFirst ?? false,
599
+ state: oldState,
600
+ });
601
+ }
602
+ } else if (oldState.status === "updating") {
603
+ // we started to update a resource but did not complete
604
+ if (diff.action === "update" || diff.action === "noop") {
605
+ return Node<Update<Resource>>({
606
+ action: "update",
607
+ props: news,
608
+ state: oldState,
609
+ });
610
+ } else {
611
+ // we started to update a resource but now believe we should replace it
612
+ return Node<Replace<Resource>>({
613
+ action: "replace",
614
+ deleteFirst: diff.deleteFirst ?? false,
615
+ props: news,
616
+ // TODO(sam): can Apply handle replacements when the oldState is UpdatingResourceState?
617
+ // -> or is there we do a provider.read to try and reconcile back to UpdatedResourceState?
618
+ state: oldState,
619
+ });
620
+ }
621
+ } else if (oldState.status === "replacing") {
622
+ // resource replacement started, but the replacement may or may not have been created
623
+ if (diff.action === "noop") {
624
+ // this is the stable case - noop means just continue with the replacement
625
+ return Node<Replace<Resource>>({
626
+ action: "replace",
627
+ deleteFirst: oldState.deleteFirst,
628
+ props: news,
629
+ state: oldState,
630
+ });
631
+ } else if (diff.action === "update") {
632
+ // potential problem here - the props have changed since we tried to replace,
633
+ // but not enough to trigger another replacement. the resource provider should
634
+ // be designed as idempotent to converge to the right state when creating the new resource
635
+ // the newly generated instanceId is intended to assist with this
636
+ return Node<Replace<Resource>>({
637
+ action: "replace",
638
+ deleteFirst: oldState.deleteFirst,
639
+ props: news,
640
+ state: oldState,
641
+ });
642
+ } else {
643
+ // ah shit, so we tried to replace the resource and then crashed
644
+ // now the props have changed again in such a way that the (maybe, maybe not)
645
+ // created resource should also be replaced
646
+
647
+ // TODO(sam): what should we do?
648
+ // 1. trigger a deletion of the potentially created resource
649
+ // 2. expect the resource provider to handle it idempotently?
650
+ // -> i don't think this case is fair to put on the resource provider
651
+ // because if the resource was created, it's in a state that can't be updated
652
+ return yield* Effect.fail(
653
+ new CannotReplacePartiallyReplacedResource(id),
654
+ );
655
+ }
656
+ } else if (oldState.status === "replaced") {
657
+ // replacement has been created but we're not done cleaning up the old state
658
+ if (diff.action === "noop") {
659
+ // this is the stable case - noop means just continue cleaning up the replacement
660
+ return Node<Replace<Resource>>({
661
+ action: "replace",
662
+ deleteFirst: oldState.deleteFirst,
663
+ props: news,
664
+ state: oldState,
665
+ });
666
+ } else if (diff.action === "update") {
667
+ // the replacement has been created but now also needs to be updated
668
+ // the resource provider should:
669
+ // 1. Update the newly created replacement resource
670
+ // 2. Then proceed as normal to delete the replaced resources (after all downstream references are updated)
671
+ return Node<Update<Resource>>({
672
+ action: "update",
673
+ props: news,
674
+ state: oldState,
675
+ });
676
+ } else {
677
+ // the replacement has been created but now it needs to be replaced
678
+ // this is the worst-case scenario because downstream resources
679
+ // could have been been updated to point to the replaced resources
680
+ return yield* Effect.fail(
681
+ new CannotReplacePartiallyReplacedResource(id),
682
+ );
683
+ }
684
+ } else if (oldState.status === "deleting") {
685
+ if (diff.action === "noop" || diff.action === "update") {
686
+ // we're in a partially deleted state, it is unclear whether it was or was not deleted
687
+ // it should be safe to re-create it with the same instanceId?
688
+ return Node<Create<Resource>>({
689
+ action: "create",
690
+ props: news,
691
+ state: {
692
+ ...oldState,
693
+ status: "creating",
694
+ props: news,
695
+ },
696
+ });
697
+ } else {
698
+ return yield* Effect.fail(
699
+ new CannotReplacePartiallyReplacedResource(id),
700
+ );
701
+ }
702
+ } else if (diff.action === "update") {
703
+ return Node<Update<Resource>>({
704
+ action: "update",
705
+ props: news,
706
+ state: oldState,
707
+ });
708
+ } else if (diff.action === "replace") {
709
+ return Node<Replace<Resource>>({
710
+ action: "replace",
711
+ props: news,
712
+ state: oldState,
713
+ deleteFirst: diff?.deleteFirst ?? false,
714
+ });
715
+ } else {
716
+ return Node<NoopUpdate<Resource>>({
717
+ action: "noop",
718
+ state: oldState,
719
+ });
720
+ }
721
+ }),
722
+ ),
723
+ )).map((update) => [update.resource.id, update]),
724
+ ) as IPlan["resources"];
347
725
 
348
726
  const deletions = Object.fromEntries(
349
727
  (yield* Effect.all(
350
- (yield* state.list()).map(
728
+ (yield* state.list({ stack: app.name, stage: app.stage })).map(
351
729
  Effect.fn(function* (id) {
352
730
  if (id in resourceGraph) {
353
731
  return;
354
732
  }
355
- const oldState = yield* state.get(id);
356
- const context = yield* Effect.context<never>();
733
+ const oldState = yield* state.get({
734
+ stack: app.name,
735
+ stage: app.stage,
736
+ resourceId: id,
737
+ });
357
738
  if (oldState) {
358
- const provider: ProviderService = context.unsafeMap.get(
359
- oldState?.type,
360
- );
361
- if (!provider) {
362
- yield* Effect.die(
363
- new Error(`Provider not found for ${oldState?.type}`),
364
- );
365
- }
739
+ const provider = yield* getProviderByType(oldState.resourceType);
366
740
  return [
367
741
  id,
368
742
  {
369
743
  action: "delete",
370
- olds: oldState.props,
371
- output: oldState.output,
372
- provider,
373
- attributes: oldState?.output,
374
- // TODO(sam): Support Detach Bindings
744
+ state: oldState,
745
+ // // TODO(sam): Support Detach Bindings
375
746
  bindings: [],
747
+ provider,
376
748
  resource: {
377
749
  id: id,
378
- parent: undefined,
379
- type: oldState.type,
380
- attr: oldState.output,
750
+ type: oldState.resourceType,
751
+ attr: oldState.attr,
381
752
  props: oldState.props,
382
753
  } as Resource,
383
- downstream: downstream[id] ?? [],
754
+ // TODO(sam): is it enough to just pass through oldState?
755
+ downstream: oldDownstreamDependencies[id] ?? [],
384
756
  } satisfies Delete<Resource>,
385
757
  ] as const;
386
758
  }
@@ -390,7 +762,7 @@ export const plan = <
390
762
  );
391
763
 
392
764
  for (const [resourceId, deletion] of Object.entries(deletions)) {
393
- const dependencies = deletion.downstream.filter(
765
+ const dependencies = deletion.state.downstream.filter(
394
766
  (d) => d in resourceGraph,
395
767
  );
396
768
  if (dependencies.length > 0) {
@@ -405,26 +777,29 @@ export const plan = <
405
777
  }
406
778
 
407
779
  return {
408
- phase,
409
780
  resources: resourceGraph,
410
781
  deletions,
411
- } satisfies Plan as Plan;
412
- }) as Effect.Effect<
413
- {
414
- phase: Phase;
415
- resources: {
416
- [ID in keyof ResourceGraph]: ResourceGraph[ID];
417
- };
418
- deletions: {
419
- [id in Exclude<string, keyof ResourceGraph>]?: Delete<Resource>;
420
- };
421
- },
422
- never,
423
- UpstreamTags | State
424
- >;
425
- };
782
+ } satisfies IPlan as IPlan;
783
+ }) as any;
784
+
785
+ export class CannotReplacePartiallyReplacedResource extends Data.TaggedError(
786
+ "CannotReplacePartiallyReplacedResource",
787
+ )<{
788
+ message: string;
789
+ logicalId: string;
790
+ }> {
791
+ constructor(logicalId: string) {
792
+ super({
793
+ message:
794
+ `Resource '${logicalId}' did not finish being replaced in a previous deployment ` +
795
+ `and is expected to be replaced again in this deployment. ` +
796
+ `You should revert its properties and try again after a successful deployment.`,
797
+ logicalId,
798
+ });
799
+ }
800
+ }
426
801
 
427
- class DeleteResourceHasDownstreamDependencies extends Data.TaggedError(
802
+ export class DeleteResourceHasDownstreamDependencies extends Data.TaggedError(
428
803
  "DeleteResourceHasDownstreamDependencies",
429
804
  )<{
430
805
  message: string;
@@ -433,12 +808,13 @@ class DeleteResourceHasDownstreamDependencies extends Data.TaggedError(
433
808
  }> {}
434
809
 
435
810
  const arePropsChanged = <R extends Resource>(
436
- oldState: ResourceState | undefined,
811
+ oldProps: R["props"] | undefined,
437
812
  newProps: R["props"],
438
813
  ) => {
439
814
  return (
440
- JSON.stringify(omit(oldState?.props ?? {}, "bindings")) !==
441
- JSON.stringify(omit((newProps ?? {}) as any, "bindings"))
815
+ Output.hasOutputs(newProps) ||
816
+ JSON.stringify(omit((oldProps ?? {}) as any, "bindings")) !==
817
+ JSON.stringify(omit((newProps ?? {}) as any, "bindings"))
442
818
  );
443
819
  };
444
820
 
@@ -453,9 +829,9 @@ const diffBindings = Effect.fn(function* ({
453
829
  }) {
454
830
  // const actions: BindNode[] = [];
455
831
  const oldBindings = oldState?.bindings;
456
- const oldSids = new Set(
457
- oldBindings?.map(({ binding }) => binding.capability.sid),
458
- );
832
+ // const oldSids = new Set(
833
+ // oldBindings?.map(({ binding }) => binding.capability.sid),
834
+ // );
459
835
 
460
836
  const diffBinding: (
461
837
  binding: AnyBinding,
@@ -559,7 +935,7 @@ const isBindingDiff = Effect.fn(function* ({
559
935
  id: oldCap.resource.id,
560
936
  props: newCap.resource.props,
561
937
  oldProps: oldState?.props,
562
- oldAttr: oldState?.output,
938
+ oldAttr: oldState?.attr,
563
939
  },
564
940
  props: newBinding.props,
565
941
  attr: oldBinding.attr,
@@ -581,3 +957,106 @@ const isBindingDiff = Effect.fn(function* ({
581
957
  });
582
958
  // TODO(sam): compare props
583
959
  // oldBinding.props !== newBinding.props;
960
+
961
+ /**
962
+ * Print a plan in a human-readable format that shows the graph topology.
963
+ */
964
+ export const printPlan = (plan: IPlan): string => {
965
+ const lines: string[] = [];
966
+ const allNodes = { ...plan.resources, ...plan.deletions };
967
+
968
+ // Build reverse mapping: upstream -> downstream
969
+ const upstreamMap: Record<string, string[]> = {};
970
+ for (const [id] of Object.entries(allNodes)) {
971
+ upstreamMap[id] = [];
972
+ }
973
+ for (const [id, node] of Object.entries(allNodes)) {
974
+ if (!node) continue;
975
+ for (const downstreamId of node.state?.downstream ?? []) {
976
+ if (upstreamMap[downstreamId]) {
977
+ upstreamMap[downstreamId].push(id);
978
+ }
979
+ }
980
+ }
981
+
982
+ // Action symbols
983
+ const actionSymbol = (action: string) => {
984
+ switch (action) {
985
+ case "create":
986
+ return "+";
987
+ case "update":
988
+ return "~";
989
+ case "delete":
990
+ return "-";
991
+ case "replace":
992
+ return "±";
993
+ case "noop":
994
+ return "=";
995
+ default:
996
+ return "?";
997
+ }
998
+ };
999
+
1000
+ // Print header
1001
+ lines.push(
1002
+ "╔════════════════════════════════════════════════════════════════╗",
1003
+ );
1004
+ lines.push(
1005
+ "║ PLAN ║",
1006
+ );
1007
+ lines.push(
1008
+ "╠════════════════════════════════════════════════════════════════╣",
1009
+ );
1010
+ lines.push(
1011
+ "║ Legend: + create, ~ update, - delete, ± replace, = noop ║",
1012
+ );
1013
+ lines.push(
1014
+ "╚════════════════════════════════════════════════════════════════╝",
1015
+ );
1016
+ lines.push("");
1017
+
1018
+ // Print resources section
1019
+ lines.push(
1020
+ "┌─ Resources ────────────────────────────────────────────────────┐",
1021
+ );
1022
+ const resourceIds = Object.keys(plan.resources).sort();
1023
+ for (const id of resourceIds) {
1024
+ const node = plan.resources[id];
1025
+ const symbol = actionSymbol(node.action);
1026
+ const type = node.resource?.type ?? "unknown";
1027
+ const downstream = node.state?.downstream?.length
1028
+ ? ` → [${node.state?.downstream.join(", ")}]`
1029
+ : "";
1030
+ lines.push(`│ [${symbol}] ${id} (${type})${downstream}`);
1031
+ }
1032
+ if (resourceIds.length === 0) {
1033
+ lines.push("│ (none)");
1034
+ }
1035
+ lines.push(
1036
+ "└────────────────────────────────────────────────────────────────┘",
1037
+ );
1038
+ lines.push("");
1039
+
1040
+ // Print deletions section
1041
+ lines.push(
1042
+ "┌─ Deletions ────────────────────────────────────────────────────┐",
1043
+ );
1044
+ const deletionIds = Object.keys(plan.deletions).sort();
1045
+ for (const id of deletionIds) {
1046
+ const node = plan.deletions[id]!;
1047
+ const type = node.resource?.type ?? "unknown";
1048
+ const downstream = node.state.downstream?.length
1049
+ ? ` → [${node.state.downstream.join(", ")}]`
1050
+ : "";
1051
+ lines.push(`│ [-] ${id} (${type})${downstream}`);
1052
+ }
1053
+ if (deletionIds.length === 0) {
1054
+ lines.push("│ (none)");
1055
+ }
1056
+ lines.push(
1057
+ "└────────────────────────────────────────────────────────────────┘",
1058
+ );
1059
+ lines.push("");
1060
+
1061
+ return lines.join("\n");
1062
+ };