@alchemy.run/modal 0.0.0 → 2.0.0-beta.87

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 (499) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +96 -4
  3. package/lib/App.d.ts +143 -0
  4. package/lib/App.d.ts.map +1 -0
  5. package/lib/App.js +198 -0
  6. package/lib/App.js.map +1 -0
  7. package/lib/AuthProvider.d.ts +53 -0
  8. package/lib/AuthProvider.d.ts.map +1 -0
  9. package/lib/AuthProvider.js +209 -0
  10. package/lib/AuthProvider.js.map +1 -0
  11. package/lib/BindingHttp.d.ts +56 -0
  12. package/lib/BindingHttp.d.ts.map +1 -0
  13. package/lib/BindingHttp.js +50 -0
  14. package/lib/BindingHttp.js.map +1 -0
  15. package/lib/Blob.d.ts +34 -0
  16. package/lib/Blob.d.ts.map +1 -0
  17. package/lib/Blob.js +59 -0
  18. package/lib/Blob.js.map +1 -0
  19. package/lib/Bootstrap.d.ts +8 -0
  20. package/lib/Bootstrap.d.ts.map +1 -0
  21. package/lib/Bootstrap.js +26 -0
  22. package/lib/Bootstrap.js.map +1 -0
  23. package/lib/Cbor.d.ts +10 -0
  24. package/lib/Cbor.d.ts.map +1 -0
  25. package/lib/Cbor.js +177 -0
  26. package/lib/Cbor.js.map +1 -0
  27. package/lib/ConnectSandbox.d.ts +64 -0
  28. package/lib/ConnectSandbox.d.ts.map +1 -0
  29. package/lib/ConnectSandbox.js +33 -0
  30. package/lib/ConnectSandbox.js.map +1 -0
  31. package/lib/Container.d.ts +124 -0
  32. package/lib/Container.d.ts.map +1 -0
  33. package/lib/Container.js +1 -0
  34. package/lib/Container.js.map +1 -0
  35. package/lib/Contributions.d.ts +7 -0
  36. package/lib/Contributions.d.ts.map +1 -0
  37. package/lib/Contributions.js +6 -0
  38. package/lib/Contributions.js.map +1 -0
  39. package/lib/CreateDict.d.ts +83 -0
  40. package/lib/CreateDict.d.ts.map +1 -0
  41. package/lib/CreateDict.js +3 -0
  42. package/lib/CreateDict.js.map +1 -0
  43. package/lib/CreateDictHttp.d.ts +33 -0
  44. package/lib/CreateDictHttp.d.ts.map +1 -0
  45. package/lib/CreateDictHttp.js +67 -0
  46. package/lib/CreateDictHttp.js.map +1 -0
  47. package/lib/CreateImage.d.ts +98 -0
  48. package/lib/CreateImage.d.ts.map +1 -0
  49. package/lib/CreateImage.js +3 -0
  50. package/lib/CreateImage.js.map +1 -0
  51. package/lib/CreateImageHttp.d.ts +51 -0
  52. package/lib/CreateImageHttp.d.ts.map +1 -0
  53. package/lib/CreateImageHttp.js +87 -0
  54. package/lib/CreateImageHttp.js.map +1 -0
  55. package/lib/CreateQueue.d.ts +83 -0
  56. package/lib/CreateQueue.d.ts.map +1 -0
  57. package/lib/CreateQueue.js +3 -0
  58. package/lib/CreateQueue.js.map +1 -0
  59. package/lib/CreateQueueHttp.d.ts +31 -0
  60. package/lib/CreateQueueHttp.d.ts.map +1 -0
  61. package/lib/CreateQueueHttp.js +67 -0
  62. package/lib/CreateQueueHttp.js.map +1 -0
  63. package/lib/CreateSandbox.d.ts +186 -0
  64. package/lib/CreateSandbox.d.ts.map +1 -0
  65. package/lib/CreateSandbox.js +3 -0
  66. package/lib/CreateSandbox.js.map +1 -0
  67. package/lib/CreateSandboxHttp.d.ts +17 -0
  68. package/lib/CreateSandboxHttp.d.ts.map +1 -0
  69. package/lib/CreateSandboxHttp.js +117 -0
  70. package/lib/CreateSandboxHttp.js.map +1 -0
  71. package/lib/CreateSecret.d.ts +95 -0
  72. package/lib/CreateSecret.d.ts.map +1 -0
  73. package/lib/CreateSecret.js +3 -0
  74. package/lib/CreateSecret.js.map +1 -0
  75. package/lib/CreateSecretHttp.d.ts +24 -0
  76. package/lib/CreateSecretHttp.d.ts.map +1 -0
  77. package/lib/CreateSecretHttp.js +60 -0
  78. package/lib/CreateSecretHttp.js.map +1 -0
  79. package/lib/CreateVolume.d.ts +84 -0
  80. package/lib/CreateVolume.d.ts.map +1 -0
  81. package/lib/CreateVolume.js +3 -0
  82. package/lib/CreateVolume.js.map +1 -0
  83. package/lib/CreateVolumeHttp.d.ts +32 -0
  84. package/lib/CreateVolumeHttp.d.ts.map +1 -0
  85. package/lib/CreateVolumeHttp.js +69 -0
  86. package/lib/CreateVolumeHttp.js.map +1 -0
  87. package/lib/Credentials.d.ts +32 -0
  88. package/lib/Credentials.d.ts.map +1 -0
  89. package/lib/Credentials.js +44 -0
  90. package/lib/Credentials.js.map +1 -0
  91. package/lib/Dict.d.ts +147 -0
  92. package/lib/Dict.d.ts.map +1 -0
  93. package/lib/Dict.js +175 -0
  94. package/lib/Dict.js.map +1 -0
  95. package/lib/DictClient.d.ts +55 -0
  96. package/lib/DictClient.d.ts.map +1 -0
  97. package/lib/DictClient.js +89 -0
  98. package/lib/DictClient.js.map +1 -0
  99. package/lib/Domain.d.ts +86 -0
  100. package/lib/Domain.d.ts.map +1 -0
  101. package/lib/Domain.js +113 -0
  102. package/lib/Domain.js.map +1 -0
  103. package/lib/Endpoint.d.ts +181 -0
  104. package/lib/Endpoint.d.ts.map +1 -0
  105. package/lib/Endpoint.js +283 -0
  106. package/lib/Endpoint.js.map +1 -0
  107. package/lib/Environment.d.ts +174 -0
  108. package/lib/Environment.d.ts.map +1 -0
  109. package/lib/Environment.js +244 -0
  110. package/lib/Environment.js.map +1 -0
  111. package/lib/FetchServer.d.ts +105 -0
  112. package/lib/FetchServer.d.ts.map +1 -0
  113. package/lib/FetchServer.js +3 -0
  114. package/lib/FetchServer.js.map +1 -0
  115. package/lib/FetchServerHttp.d.ts +21 -0
  116. package/lib/FetchServerHttp.d.ts.map +1 -0
  117. package/lib/FetchServerHttp.js +84 -0
  118. package/lib/FetchServerHttp.js.map +1 -0
  119. package/lib/Function.d.ts +236 -0
  120. package/lib/Function.d.ts.map +1 -0
  121. package/lib/Function.js +273 -0
  122. package/lib/Function.js.map +1 -0
  123. package/lib/FunctionAutoscaler.d.ts +99 -0
  124. package/lib/FunctionAutoscaler.d.ts.map +1 -0
  125. package/lib/FunctionAutoscaler.js +136 -0
  126. package/lib/FunctionAutoscaler.js.map +1 -0
  127. package/lib/FunctionTarget.d.ts +61 -0
  128. package/lib/FunctionTarget.d.ts.map +1 -0
  129. package/lib/FunctionTarget.js +136 -0
  130. package/lib/FunctionTarget.js.map +1 -0
  131. package/lib/Image.d.ts +159 -0
  132. package/lib/Image.d.ts.map +1 -0
  133. package/lib/Image.js +203 -0
  134. package/lib/Image.js.map +1 -0
  135. package/lib/ImageBuild.d.ts +108 -0
  136. package/lib/ImageBuild.d.ts.map +1 -0
  137. package/lib/ImageBuild.js +157 -0
  138. package/lib/ImageBuild.js.map +1 -0
  139. package/lib/ImagePlan.d.ts +31 -0
  140. package/lib/ImagePlan.d.ts.map +1 -0
  141. package/lib/ImagePlan.js +70 -0
  142. package/lib/ImagePlan.js.map +1 -0
  143. package/lib/InferEnv.d.ts +26 -0
  144. package/lib/InferEnv.d.ts.map +1 -0
  145. package/lib/InferEnv.js +1 -0
  146. package/lib/InferEnv.js.map +1 -0
  147. package/lib/InvokeFunction.d.ts +186 -0
  148. package/lib/InvokeFunction.d.ts.map +1 -0
  149. package/lib/InvokeFunction.js +10 -0
  150. package/lib/InvokeFunction.js.map +1 -0
  151. package/lib/InvokeFunctionHttp.d.ts +39 -0
  152. package/lib/InvokeFunctionHttp.d.ts.map +1 -0
  153. package/lib/InvokeFunctionHttp.js +210 -0
  154. package/lib/InvokeFunctionHttp.js.map +1 -0
  155. package/lib/ModalBindings.d.ts +138 -0
  156. package/lib/ModalBindings.d.ts.map +1 -0
  157. package/lib/ModalBindings.js +223 -0
  158. package/lib/ModalBindings.js.map +1 -0
  159. package/lib/MountBucket.d.ts +80 -0
  160. package/lib/MountBucket.d.ts.map +1 -0
  161. package/lib/MountBucket.js +44 -0
  162. package/lib/MountBucket.js.map +1 -0
  163. package/lib/MountVolume.d.ts +77 -0
  164. package/lib/MountVolume.d.ts.map +1 -0
  165. package/lib/MountVolume.js +41 -0
  166. package/lib/MountVolume.js.map +1 -0
  167. package/lib/Mounts.d.ts +134 -0
  168. package/lib/Mounts.d.ts.map +1 -0
  169. package/lib/Mounts.js +87 -0
  170. package/lib/Mounts.js.map +1 -0
  171. package/lib/PersistentSandbox.d.ts +75 -0
  172. package/lib/PersistentSandbox.d.ts.map +1 -0
  173. package/lib/PersistentSandbox.js +93 -0
  174. package/lib/PersistentSandbox.js.map +1 -0
  175. package/lib/Providers.d.ts +23 -0
  176. package/lib/Providers.d.ts.map +1 -0
  177. package/lib/Providers.js +64 -0
  178. package/lib/Providers.js.map +1 -0
  179. package/lib/Proxy.d.ts +94 -0
  180. package/lib/Proxy.d.ts.map +1 -0
  181. package/lib/Proxy.js +132 -0
  182. package/lib/Proxy.js.map +1 -0
  183. package/lib/Queue.d.ts +147 -0
  184. package/lib/Queue.d.ts.map +1 -0
  185. package/lib/Queue.js +177 -0
  186. package/lib/Queue.js.map +1 -0
  187. package/lib/QueueClient.d.ts +64 -0
  188. package/lib/QueueClient.d.ts.map +1 -0
  189. package/lib/QueueClient.js +73 -0
  190. package/lib/QueueClient.js.map +1 -0
  191. package/lib/ReadApp.d.ts +85 -0
  192. package/lib/ReadApp.d.ts.map +1 -0
  193. package/lib/ReadApp.js +3 -0
  194. package/lib/ReadApp.js.map +1 -0
  195. package/lib/ReadAppHttp.d.ts +26 -0
  196. package/lib/ReadAppHttp.d.ts.map +1 -0
  197. package/lib/ReadAppHttp.js +106 -0
  198. package/lib/ReadAppHttp.js.map +1 -0
  199. package/lib/ReadDict.d.ts +39 -0
  200. package/lib/ReadDict.d.ts.map +1 -0
  201. package/lib/ReadDict.js +3 -0
  202. package/lib/ReadDict.js.map +1 -0
  203. package/lib/ReadDictHttp.d.ts +12 -0
  204. package/lib/ReadDictHttp.d.ts.map +1 -0
  205. package/lib/ReadDictHttp.js +23 -0
  206. package/lib/ReadDictHttp.js.map +1 -0
  207. package/lib/ReadFunction.d.ts +73 -0
  208. package/lib/ReadFunction.d.ts.map +1 -0
  209. package/lib/ReadFunction.js +3 -0
  210. package/lib/ReadFunction.js.map +1 -0
  211. package/lib/ReadFunctionHttp.d.ts +25 -0
  212. package/lib/ReadFunctionHttp.d.ts.map +1 -0
  213. package/lib/ReadFunctionHttp.js +63 -0
  214. package/lib/ReadFunctionHttp.js.map +1 -0
  215. package/lib/ReadQueue.d.ts +39 -0
  216. package/lib/ReadQueue.d.ts.map +1 -0
  217. package/lib/ReadQueue.js +3 -0
  218. package/lib/ReadQueue.js.map +1 -0
  219. package/lib/ReadQueueHttp.d.ts +12 -0
  220. package/lib/ReadQueueHttp.d.ts.map +1 -0
  221. package/lib/ReadQueueHttp.js +23 -0
  222. package/lib/ReadQueueHttp.js.map +1 -0
  223. package/lib/ReadServer.d.ts +72 -0
  224. package/lib/ReadServer.d.ts.map +1 -0
  225. package/lib/ReadServer.js +3 -0
  226. package/lib/ReadServer.js.map +1 -0
  227. package/lib/ReadServerHttp.d.ts +25 -0
  228. package/lib/ReadServerHttp.d.ts.map +1 -0
  229. package/lib/ReadServerHttp.js +70 -0
  230. package/lib/ReadServerHttp.js.map +1 -0
  231. package/lib/ReadVolume.d.ts +40 -0
  232. package/lib/ReadVolume.d.ts.map +1 -0
  233. package/lib/ReadVolume.js +3 -0
  234. package/lib/ReadVolume.js.map +1 -0
  235. package/lib/ReadVolumeHttp.d.ts +12 -0
  236. package/lib/ReadVolumeHttp.d.ts.map +1 -0
  237. package/lib/ReadVolumeHttp.js +24 -0
  238. package/lib/ReadVolumeHttp.js.map +1 -0
  239. package/lib/ReadWriteDict.d.ts +39 -0
  240. package/lib/ReadWriteDict.d.ts.map +1 -0
  241. package/lib/ReadWriteDict.js +3 -0
  242. package/lib/ReadWriteDict.js.map +1 -0
  243. package/lib/ReadWriteDictHttp.d.ts +12 -0
  244. package/lib/ReadWriteDictHttp.d.ts.map +1 -0
  245. package/lib/ReadWriteDictHttp.js +23 -0
  246. package/lib/ReadWriteDictHttp.js.map +1 -0
  247. package/lib/ReadWriteQueue.d.ts +40 -0
  248. package/lib/ReadWriteQueue.d.ts.map +1 -0
  249. package/lib/ReadWriteQueue.js +3 -0
  250. package/lib/ReadWriteQueue.js.map +1 -0
  251. package/lib/ReadWriteQueueHttp.d.ts +12 -0
  252. package/lib/ReadWriteQueueHttp.d.ts.map +1 -0
  253. package/lib/ReadWriteQueueHttp.js +23 -0
  254. package/lib/ReadWriteQueueHttp.js.map +1 -0
  255. package/lib/ReadWriteVolume.d.ts +40 -0
  256. package/lib/ReadWriteVolume.d.ts.map +1 -0
  257. package/lib/ReadWriteVolume.js +3 -0
  258. package/lib/ReadWriteVolume.js.map +1 -0
  259. package/lib/ReadWriteVolumeHttp.d.ts +12 -0
  260. package/lib/ReadWriteVolumeHttp.d.ts.map +1 -0
  261. package/lib/ReadWriteVolumeHttp.js +24 -0
  262. package/lib/ReadWriteVolumeHttp.js.map +1 -0
  263. package/lib/RuntimeOwner.d.ts +39 -0
  264. package/lib/RuntimeOwner.d.ts.map +1 -0
  265. package/lib/RuntimeOwner.js +85 -0
  266. package/lib/RuntimeOwner.js.map +1 -0
  267. package/lib/Sandbox.d.ts +215 -0
  268. package/lib/Sandbox.d.ts.map +1 -0
  269. package/lib/Sandbox.js +278 -0
  270. package/lib/Sandbox.js.map +1 -0
  271. package/lib/SandboxClient.d.ts +748 -0
  272. package/lib/SandboxClient.d.ts.map +1 -0
  273. package/lib/SandboxClient.js +710 -0
  274. package/lib/SandboxClient.js.map +1 -0
  275. package/lib/ScaleFunction.d.ts +54 -0
  276. package/lib/ScaleFunction.d.ts.map +1 -0
  277. package/lib/ScaleFunction.js +3 -0
  278. package/lib/ScaleFunction.js.map +1 -0
  279. package/lib/ScaleFunctionHttp.d.ts +21 -0
  280. package/lib/ScaleFunctionHttp.d.ts.map +1 -0
  281. package/lib/ScaleFunctionHttp.js +39 -0
  282. package/lib/ScaleFunctionHttp.js.map +1 -0
  283. package/lib/Secret.d.ts +170 -0
  284. package/lib/Secret.d.ts.map +1 -0
  285. package/lib/Secret.js +196 -0
  286. package/lib/Secret.js.map +1 -0
  287. package/lib/Server.d.ts +155 -0
  288. package/lib/Server.d.ts.map +1 -0
  289. package/lib/Server.js +184 -0
  290. package/lib/Server.js.map +1 -0
  291. package/lib/ServiceUser.d.ts +139 -0
  292. package/lib/ServiceUser.d.ts.map +1 -0
  293. package/lib/ServiceUser.js +210 -0
  294. package/lib/ServiceUser.js.map +1 -0
  295. package/lib/Volume.d.ts +170 -0
  296. package/lib/Volume.d.ts.map +1 -0
  297. package/lib/Volume.js +220 -0
  298. package/lib/Volume.js.map +1 -0
  299. package/lib/VolumeClient.d.ts +53 -0
  300. package/lib/VolumeClient.d.ts.map +1 -0
  301. package/lib/VolumeClient.js +22 -0
  302. package/lib/VolumeClient.js.map +1 -0
  303. package/lib/VolumeFiles.d.ts +94 -0
  304. package/lib/VolumeFiles.d.ts.map +1 -0
  305. package/lib/VolumeFiles.js +139 -0
  306. package/lib/VolumeFiles.js.map +1 -0
  307. package/lib/WebhookToken.d.ts +102 -0
  308. package/lib/WebhookToken.d.ts.map +1 -0
  309. package/lib/WebhookToken.js +176 -0
  310. package/lib/WebhookToken.js.map +1 -0
  311. package/lib/WorkspaceSettings.d.ts +70 -0
  312. package/lib/WorkspaceSettings.d.ts.map +1 -0
  313. package/lib/WorkspaceSettings.js +105 -0
  314. package/lib/WorkspaceSettings.js.map +1 -0
  315. package/lib/WriteApp.d.ts +38 -0
  316. package/lib/WriteApp.d.ts.map +1 -0
  317. package/lib/WriteApp.js +3 -0
  318. package/lib/WriteApp.js.map +1 -0
  319. package/lib/WriteAppHttp.d.ts +23 -0
  320. package/lib/WriteAppHttp.d.ts.map +1 -0
  321. package/lib/WriteAppHttp.js +36 -0
  322. package/lib/WriteAppHttp.js.map +1 -0
  323. package/lib/WriteDict.d.ts +39 -0
  324. package/lib/WriteDict.d.ts.map +1 -0
  325. package/lib/WriteDict.js +3 -0
  326. package/lib/WriteDict.js.map +1 -0
  327. package/lib/WriteDictHttp.d.ts +12 -0
  328. package/lib/WriteDictHttp.d.ts.map +1 -0
  329. package/lib/WriteDictHttp.js +23 -0
  330. package/lib/WriteDictHttp.js.map +1 -0
  331. package/lib/WriteQueue.d.ts +39 -0
  332. package/lib/WriteQueue.d.ts.map +1 -0
  333. package/lib/WriteQueue.js +3 -0
  334. package/lib/WriteQueue.js.map +1 -0
  335. package/lib/WriteQueueHttp.d.ts +12 -0
  336. package/lib/WriteQueueHttp.d.ts.map +1 -0
  337. package/lib/WriteQueueHttp.js +23 -0
  338. package/lib/WriteQueueHttp.js.map +1 -0
  339. package/lib/WriteSecret.d.ts +53 -0
  340. package/lib/WriteSecret.d.ts.map +1 -0
  341. package/lib/WriteSecret.js +3 -0
  342. package/lib/WriteSecret.js.map +1 -0
  343. package/lib/WriteSecretHttp.d.ts +23 -0
  344. package/lib/WriteSecretHttp.d.ts.map +1 -0
  345. package/lib/WriteSecretHttp.js +44 -0
  346. package/lib/WriteSecretHttp.js.map +1 -0
  347. package/lib/WriteVolume.d.ts +39 -0
  348. package/lib/WriteVolume.d.ts.map +1 -0
  349. package/lib/WriteVolume.js +3 -0
  350. package/lib/WriteVolume.js.map +1 -0
  351. package/lib/WriteVolumeHttp.d.ts +12 -0
  352. package/lib/WriteVolumeHttp.d.ts.map +1 -0
  353. package/lib/WriteVolumeHttp.js +24 -0
  354. package/lib/WriteVolumeHttp.js.map +1 -0
  355. package/lib/index.d.ts +80 -0
  356. package/lib/index.d.ts.map +1 -0
  357. package/lib/index.js +78 -0
  358. package/lib/index.js.map +1 -0
  359. package/lib/internal/Declared.d.ts +29 -0
  360. package/lib/internal/Declared.d.ts.map +1 -0
  361. package/lib/internal/Declared.js +31 -0
  362. package/lib/internal/Declared.js.map +1 -0
  363. package/lib/internal/Deploy.d.ts +110 -0
  364. package/lib/internal/Deploy.d.ts.map +1 -0
  365. package/lib/internal/Deploy.js +230 -0
  366. package/lib/internal/Deploy.js.map +1 -0
  367. package/lib/internal/Entrypoint.d.ts +52 -0
  368. package/lib/internal/Entrypoint.d.ts.map +1 -0
  369. package/lib/internal/Entrypoint.js +193 -0
  370. package/lib/internal/Entrypoint.js.map +1 -0
  371. package/lib/internal/Rpc.d.ts +17 -0
  372. package/lib/internal/Rpc.d.ts.map +1 -0
  373. package/lib/internal/Rpc.js +49 -0
  374. package/lib/internal/Rpc.js.map +1 -0
  375. package/lib/internal/RpcAuth.d.ts +41 -0
  376. package/lib/internal/RpcAuth.d.ts.map +1 -0
  377. package/lib/internal/RpcAuth.js +100 -0
  378. package/lib/internal/RpcAuth.js.map +1 -0
  379. package/lib/internal/Runtime.d.ts +127 -0
  380. package/lib/internal/Runtime.d.ts.map +1 -0
  381. package/lib/internal/Runtime.js +246 -0
  382. package/lib/internal/Runtime.js.map +1 -0
  383. package/lib/internal/Tags.d.ts +19 -0
  384. package/lib/internal/Tags.d.ts.map +1 -0
  385. package/lib/internal/Tags.js +33 -0
  386. package/lib/internal/Tags.js.map +1 -0
  387. package/lib/internal/TcpConnect.d.ts +12 -0
  388. package/lib/internal/TcpConnect.d.ts.map +1 -0
  389. package/lib/internal/TcpConnect.js +46 -0
  390. package/lib/internal/TcpConnect.js.map +1 -0
  391. package/lib/internal/Transport.d.ts +13 -0
  392. package/lib/internal/Transport.d.ts.map +1 -0
  393. package/lib/internal/Transport.js +16 -0
  394. package/lib/internal/Transport.js.map +1 -0
  395. package/lib/internal.d.ts +71 -0
  396. package/lib/internal.d.ts.map +1 -0
  397. package/lib/internal.js +102 -0
  398. package/lib/internal.js.map +1 -0
  399. package/package.json +59 -3
  400. package/src/App.ts +262 -0
  401. package/src/AuthProvider.ts +295 -0
  402. package/src/BindingHttp.ts +112 -0
  403. package/src/Blob.ts +71 -0
  404. package/src/Bootstrap.ts +52 -0
  405. package/src/Cbor.ts +158 -0
  406. package/src/ConnectSandbox.ts +99 -0
  407. package/src/Container.ts +121 -0
  408. package/src/Contributions.ts +9 -0
  409. package/src/CreateDict.ts +95 -0
  410. package/src/CreateDictHttp.ts +91 -0
  411. package/src/CreateImage.ts +105 -0
  412. package/src/CreateImageHttp.ts +117 -0
  413. package/src/CreateQueue.ts +95 -0
  414. package/src/CreateQueueHttp.ts +91 -0
  415. package/src/CreateSandbox.ts +228 -0
  416. package/src/CreateSandboxHttp.ts +160 -0
  417. package/src/CreateSecret.ts +109 -0
  418. package/src/CreateSecretHttp.ts +83 -0
  419. package/src/CreateVolume.ts +96 -0
  420. package/src/CreateVolumeHttp.ts +93 -0
  421. package/src/Credentials.ts +74 -0
  422. package/src/Dict.ts +245 -0
  423. package/src/DictClient.ts +166 -0
  424. package/src/Domain.ts +147 -0
  425. package/src/Endpoint.ts +406 -0
  426. package/src/Environment.ts +373 -0
  427. package/src/FetchServer.ts +126 -0
  428. package/src/FetchServerHttp.ts +110 -0
  429. package/src/Function.ts +397 -0
  430. package/src/FunctionAutoscaler.ts +210 -0
  431. package/src/FunctionTarget.ts +181 -0
  432. package/src/Image.ts +281 -0
  433. package/src/ImageBuild.ts +228 -0
  434. package/src/ImagePlan.ts +78 -0
  435. package/src/InferEnv.ts +27 -0
  436. package/src/InvokeFunction.ts +211 -0
  437. package/src/InvokeFunctionHttp.ts +316 -0
  438. package/src/ModalBindings.ts +310 -0
  439. package/src/MountBucket.ts +137 -0
  440. package/src/MountVolume.ts +113 -0
  441. package/src/Mounts.ts +230 -0
  442. package/src/PersistentSandbox.ts +142 -0
  443. package/src/Providers.ts +100 -0
  444. package/src/Proxy.ts +191 -0
  445. package/src/Queue.ts +258 -0
  446. package/src/QueueClient.ts +149 -0
  447. package/src/ReadApp.ts +101 -0
  448. package/src/ReadAppHttp.ts +142 -0
  449. package/src/ReadDict.ts +44 -0
  450. package/src/ReadDictHttp.ts +27 -0
  451. package/src/ReadFunction.ts +107 -0
  452. package/src/ReadFunctionHttp.ts +91 -0
  453. package/src/ReadQueue.ts +44 -0
  454. package/src/ReadQueueHttp.ts +27 -0
  455. package/src/ReadServer.ts +98 -0
  456. package/src/ReadServerHttp.ts +101 -0
  457. package/src/ReadVolume.ts +45 -0
  458. package/src/ReadVolumeHttp.ts +28 -0
  459. package/src/ReadWriteDict.ts +44 -0
  460. package/src/ReadWriteDictHttp.ts +27 -0
  461. package/src/ReadWriteQueue.ts +45 -0
  462. package/src/ReadWriteQueueHttp.ts +27 -0
  463. package/src/ReadWriteVolume.ts +45 -0
  464. package/src/ReadWriteVolumeHttp.ts +28 -0
  465. package/src/RuntimeOwner.ts +111 -0
  466. package/src/Sandbox.ts +402 -0
  467. package/src/SandboxClient.ts +1404 -0
  468. package/src/ScaleFunction.ts +70 -0
  469. package/src/ScaleFunctionHttp.ts +54 -0
  470. package/src/Secret.ts +296 -0
  471. package/src/Server.ts +285 -0
  472. package/src/ServiceUser.ts +295 -0
  473. package/src/Volume.ts +320 -0
  474. package/src/VolumeClient.ts +108 -0
  475. package/src/VolumeFiles.ts +194 -0
  476. package/src/WebhookToken.ts +230 -0
  477. package/src/WorkspaceSettings.ts +160 -0
  478. package/src/WriteApp.ts +43 -0
  479. package/src/WriteAppHttp.ts +51 -0
  480. package/src/WriteDict.ts +44 -0
  481. package/src/WriteDictHttp.ts +27 -0
  482. package/src/WriteQueue.ts +44 -0
  483. package/src/WriteQueueHttp.ts +27 -0
  484. package/src/WriteSecret.ts +60 -0
  485. package/src/WriteSecretHttp.ts +56 -0
  486. package/src/WriteVolume.ts +44 -0
  487. package/src/WriteVolumeHttp.ts +28 -0
  488. package/src/globals.d.ts +2 -0
  489. package/src/index.ts +85 -0
  490. package/src/internal/Declared.ts +80 -0
  491. package/src/internal/Deploy.ts +309 -0
  492. package/src/internal/Entrypoint.ts +198 -0
  493. package/src/internal/Rpc.ts +59 -0
  494. package/src/internal/RpcAuth.ts +132 -0
  495. package/src/internal/Runtime.ts +386 -0
  496. package/src/internal/Tags.ts +44 -0
  497. package/src/internal/TcpConnect.ts +78 -0
  498. package/src/internal/Transport.ts +25 -0
  499. package/src/internal.ts +147 -0
@@ -0,0 +1,181 @@
1
+ import {
2
+ serializeClassParameters,
3
+ type ClassParameterValue,
4
+ } from "@distilled.cloud/modal/ClassParameters";
5
+ import * as functions from "@distilled.cloud/modal/functions";
6
+ import type { Input } from "alchemy/Input";
7
+ import * as Data from "effect/Data";
8
+ import * as Effect from "effect/Effect";
9
+ import { environmentOf, type ErrorOf, type OpError, operations } from "./internal.ts";
10
+ import type { OperationServices } from "./internal.ts";
11
+ import type { FunctionRef, FunctionTarget } from "./InvokeFunction.ts";
12
+ import * as ModalBindings from "./ModalBindings.ts";
13
+
14
+ /*
15
+ * Shared scaffolding for bindings on a function (a `Modal.Function` or
16
+ * `Modal.Server` of the Stack, or a deployed function by name). NOT exported
17
+ * from `index.ts`.
18
+ */
19
+
20
+ export const isFunctionRef = (target: unknown): target is FunctionRef =>
21
+ typeof target === "object" &&
22
+ target !== null &&
23
+ typeof (target as FunctionRef).app === "string" &&
24
+ typeof (target as FunctionRef).function === "string";
25
+
26
+ /** A resolved target: its function id (looked up once) and the class method, if any. */
27
+ export interface ResolvedFunction {
28
+ readonly functionId: Effect.Effect<string, FunctionLookupError>;
29
+ readonly method: string | undefined;
30
+ }
31
+
32
+ /** A parameter value missing from a parameterized class call. */
33
+ export class FunctionParameterMissing extends Data.TaggedError("Modal.FunctionParameterMissing")<{
34
+ readonly message: string;
35
+ }> {}
36
+
37
+ const PARAM_TYPE = {
38
+ string: "PARAM_TYPE_STRING",
39
+ number: "PARAM_TYPE_INT",
40
+ boolean: "PARAM_TYPE_BOOL",
41
+ bytes: "PARAM_TYPE_BYTES",
42
+ } as const;
43
+
44
+ /**
45
+ * Serialize class parameters as Modal's `ClassParameterSet`, in the class's
46
+ * schema order with its defaults applied, as Modal's clients do: the bytes
47
+ * identify the bound function.
48
+ */
49
+ const serializeParameters = Effect.fn(function* (
50
+ schema: ReadonlyArray<functions.ClassParameterSpec>,
51
+ values: Record<string, string | number | boolean | Uint8Array>,
52
+ ) {
53
+ const parameters: Array<ClassParameterValue> = [];
54
+ for (const spec of schema) {
55
+ const name = spec.name ?? "";
56
+ const value = values[name];
57
+ if (value === undefined) {
58
+ if (!spec.hasDefault) {
59
+ return yield* new FunctionParameterMissing({ message: `Missing class parameter ${name}` });
60
+ }
61
+ parameters.push({
62
+ name,
63
+ type: spec.type ?? "PARAM_TYPE_UNSPECIFIED",
64
+ stringValue: spec.stringDefault,
65
+ intValue: spec.intDefault,
66
+ bytesValue: spec.bytesDefault,
67
+ boolValue: spec.boolDefault,
68
+ });
69
+ continue;
70
+ }
71
+ parameters.push(
72
+ typeof value === "string"
73
+ ? { name, type: PARAM_TYPE.string, stringValue: value }
74
+ : typeof value === "number"
75
+ ? { name, type: PARAM_TYPE.number, intValue: String(Math.trunc(value)) }
76
+ : typeof value === "boolean"
77
+ ? { name, type: PARAM_TYPE.boolean, boolValue: value }
78
+ : {
79
+ name,
80
+ type: PARAM_TYPE.bytes,
81
+ bytesValue: Buffer.from(value).toString("base64"),
82
+ },
83
+ );
84
+ }
85
+ return yield* Effect.sync(() => serializeClassParameters(parameters));
86
+ });
87
+
88
+ /**
89
+ * Resolve a target in the construction phase. A resource of the Stack
90
+ * carries its id; a name is looked up on first use and cached (the id only
91
+ * changes when the App is redeployed).
92
+ */
93
+ /** The operations that look a function up by name, resolved by `ModalBindings.withCredentials`. */
94
+ export const FunctionLookupOps = operations({
95
+ get: functions.getFunction,
96
+ bindParams: functions.functionBindParams,
97
+ });
98
+
99
+ export type FunctionLookupOps = Effect.Success<typeof FunctionLookupOps>;
100
+
101
+ /** Looking a function up by name (and binding a class's parameters). */
102
+ export type FunctionLookupError =
103
+ | OpError<FunctionLookupOps, "get" | "bindParams">
104
+ | ErrorOf<typeof serializeParameters>;
105
+
106
+ export const resolveFunction = Effect.fn(function* (
107
+ lookup: FunctionLookupOps,
108
+ target: FunctionTarget | { readonly functionId: unknown },
109
+ ) {
110
+ if (!isFunctionRef(target)) {
111
+ const functionId = yield* (
112
+ target as { readonly functionId: Effect.Effect<Effect.Effect<string>> }
113
+ ).functionId;
114
+ return { functionId, method: undefined } satisfies ResolvedFunction;
115
+ }
116
+ const [tag, method] = target.function.includes(".")
117
+ ? [`${target.function.split(".")[0]}.*`, target.function.split(".").slice(1).join(".")]
118
+ : [target.function, undefined];
119
+ let cached: string | undefined;
120
+ const functionId = Effect.suspend(() =>
121
+ cached !== undefined
122
+ ? Effect.succeed(cached)
123
+ : Effect.gen(function* () {
124
+ const environmentName = environmentOf(target.environment);
125
+ const found = yield* lookup.get({
126
+ appName: target.app,
127
+ objectTag: tag,
128
+ environmentName,
129
+ });
130
+ let id = found.functionId ?? "";
131
+ // A parameterized class runs a bound function per parameter set.
132
+ if (target.parameters !== undefined) {
133
+ const schema = found.handleMetadata?.classParameterInfo?.schema ?? [];
134
+ const serializedParams = yield* serializeParameters(schema, target.parameters);
135
+ const bound = yield* lookup.bindParams({
136
+ functionId: id,
137
+ serializedParams,
138
+ environmentName,
139
+ });
140
+ id = bound.boundFunctionId ?? id;
141
+ }
142
+ cached = id;
143
+ return id;
144
+ }),
145
+ );
146
+ return { functionId, method } satisfies ResolvedFunction;
147
+ });
148
+
149
+ /**
150
+ * A binding layer over a function target: grants the host's identity `role`
151
+ * on the function's environment (deploy only), resolves the target, and
152
+ * builds the client over `operations`.
153
+ */
154
+ export const makeFunctionBinding = <Target, Ops, Client>(options: {
155
+ readonly tag: string;
156
+ readonly operations: Effect.Effect<Ops, never, OperationServices>;
157
+ readonly role: ModalBindings.EnvironmentGrant["role"];
158
+ readonly make: (resolved: ResolvedFunction, ops: Ops, target: Target) => Effect.Effect<Client>;
159
+ }) =>
160
+ Effect.gen(function* () {
161
+ const lookup = yield* ModalBindings.withCredentials(FunctionLookupOps);
162
+ const ops = yield* ModalBindings.withCredentials(options.operations);
163
+ const bind = yield* ModalBindings.Bind;
164
+ return Effect.fn(options.tag)(function* (target: Target) {
165
+ if (!globalThis.__ALCHEMY_RUNTIME__) {
166
+ if (isFunctionRef(target)) {
167
+ yield* bind`${options.tag}`({
168
+ roles: [{ environment: target.environment, role: options.role }],
169
+ });
170
+ } else {
171
+ const environment = (target as { readonly environment: Input<string | undefined> })
172
+ .environment;
173
+ yield* bind`${options.tag}(${target})`({
174
+ roles: [{ environment, role: options.role }],
175
+ });
176
+ }
177
+ }
178
+ const resolved = yield* resolveFunction(lookup, target as FunctionTarget);
179
+ return yield* options.make(resolved, ops, target);
180
+ });
181
+ });
package/src/Image.ts ADDED
@@ -0,0 +1,281 @@
1
+ import * as BuildPlan from "@alchemy.run/docker/BuildPlan";
2
+ import type { InlineDockerfile } from "@alchemy.run/docker/Dockerfile";
3
+ import * as image from "@distilled.cloud/modal/image";
4
+ import { isResolved } from "alchemy/Diff";
5
+ import * as Provider from "alchemy/Provider";
6
+ import { Resource } from "alchemy/Resource";
7
+ import { sha256Object } from "alchemy/Util/sha256";
8
+ import * as Data from "effect/Data";
9
+ import * as Effect from "effect/Effect";
10
+ import { buildImage, ImageBuildOps, type ImageBuildSpec } from "./ImageBuild.ts";
11
+ import { checkCommands, lowerPlan, MODAL_CAPABILITIES } from "./ImagePlan.ts";
12
+ import { declarable } from "./internal/Declared.ts";
13
+ import type { Providers } from "./Providers.ts";
14
+ import { createSandbox, SandboxOps } from "./SandboxClient.ts";
15
+
16
+ /** A `run` command of a warm-base image exited non-zero. */
17
+ export class ImageSetupFailed extends Data.TaggedError("Modal.ImageSetupFailed")<{
18
+ readonly command: string;
19
+ readonly exitCode: number;
20
+ readonly message: string;
21
+ }> {}
22
+
23
+ export interface ImageProps {
24
+ /** The App the image is built in (`Modal.App`). Images are cached per workspace. */
25
+ app: { readonly appId: string; readonly name?: string; readonly environment?: string };
26
+ /**
27
+ * Start from a registry image (`node:22-slim`, `ghcr.io/org/base:1`).
28
+ * Exclusive with {@link dockerfile} and {@link from}.
29
+ */
30
+ image?: string;
31
+ /**
32
+ * Build from a Dockerfile: a path, or inline content
33
+ * (`Dockerfile.inline\`…\``). Modal's builder runs it, not Docker: a
34
+ * multi-stage build, BuildKit mounts, heredocs, `EXPOSE`, `HEALTHCHECK`,
35
+ * `LABEL`, `ONBUILD`, `STOPSIGNAL` and `VOLUME` fail at plan; `USER` is
36
+ * ignored (containers run as root) with a warning.
37
+ */
38
+ dockerfile?: string | InlineDockerfile;
39
+ /** Build context for the Dockerfile's COPY/ADD sources. */
40
+ context?: string;
41
+ /** Start from another `Modal.Image` (a warm base built on a base). */
42
+ from?: { readonly imageId: string };
43
+ /** Dockerfile lines appended to the build (`"RUN pip install pandas"`). */
44
+ commands?: string[];
45
+ /**
46
+ * Shell commands run in a sandbox booted from the built image; its
47
+ * filesystem is then snapshotted into this image. Use it for setup that
48
+ * needs network or secrets at runtime (cloning a repo, installing
49
+ * dependencies), the deploy-time warm base sandboxes fork from.
50
+ */
51
+ run?: string[];
52
+ /** Secrets the build (and `run`) sees as environment variables. */
53
+ secrets?: Array<{ readonly secretId: string }>;
54
+ /** Pull a private base image with credentials stored in a `Modal.Secret`. */
55
+ registry?: {
56
+ readonly type: "aws" | "gcp" | "static";
57
+ readonly secret: { readonly secretId: string };
58
+ };
59
+ /** Run build steps on a GPU (`"A10G"`, `"H100"`). */
60
+ gpu?: string;
61
+ /** Pin Modal's image builder version (`"2025.06"`). */
62
+ builderVersion?: string;
63
+ /**
64
+ * Publish the image under this tag in its app's environment, so code
65
+ * outside the Stack finds it with `images.fromTag(tag)` or
66
+ * `modal.Image.from_tag`.
67
+ */
68
+ tag?: string;
69
+ }
70
+
71
+ export interface ImageAttributes {
72
+ /** Image ID (`im-…`): pass it as `image` when creating sandboxes. */
73
+ imageId: string;
74
+ /** The App the image was built in; `CreateSandbox(image)` creates sandboxes there. */
75
+ appId: string;
76
+ /** Name of that App (for finding its sandboxes by name). */
77
+ appName: string | undefined;
78
+ /** Environment of that App. */
79
+ environment: string | undefined;
80
+ /** Content hash of the build inputs; a change rebuilds. */
81
+ hash: string;
82
+ }
83
+
84
+ /**
85
+ * A Modal Image: a container image built by Modal's own builder (not
86
+ * Docker), from a registry image, a Dockerfile, or another Modal Image plus
87
+ * commands. Sandboxes boot from it.
88
+ *
89
+ * With `run`, the image becomes a warm base: Alchemy boots a sandbox from the
90
+ * built image, runs the commands, snapshots its filesystem, and terminates
91
+ * it. Sandboxes created from the result start with the repo cloned and
92
+ * dependencies installed. Runtime snapshots (`sandbox.snapshotFilesystem()`)
93
+ * are images too and work anywhere an image does.
94
+ *
95
+ * Modal builds remotely: deploying needs no local Docker.
96
+ *
97
+ * ### Building an Image
98
+ * **Example:** From a registry image with extra packages
99
+ * ```typescript
100
+ * import * as Modal from "@alchemy.run/modal";
101
+ *
102
+ * export const Coder = Modal.App("Coder", {});
103
+ * export const Node = Modal.Image("Node", {
104
+ * app: Coder,
105
+ * image: "node:22-bookworm",
106
+ * commands: ["RUN apt-get update && apt-get install -y git ripgrep"],
107
+ * });
108
+ * ```
109
+ *
110
+ * **Example:** From an inline Dockerfile
111
+ * ```typescript
112
+ * import * as Dockerfile from "@alchemy.run/docker/Dockerfile";
113
+ * import * as Modal from "@alchemy.run/modal";
114
+ *
115
+ * export const Coder = Modal.App("Coder", {});
116
+ * export const Python = Modal.Image("Python", {
117
+ * app: Coder,
118
+ * dockerfile: Dockerfile.inline`
119
+ * FROM python:3.12-slim
120
+ * RUN pip install pandas
121
+ * `,
122
+ * });
123
+ * ```
124
+ *
125
+ * ### A warm base to fork from
126
+ * **Example:** Clone and install once, start every sandbox from it
127
+ * ```typescript
128
+ * import * as Modal from "@alchemy.run/modal";
129
+ *
130
+ * export const Coder = Modal.App("Coder", {});
131
+ * export const Node = Modal.Image("Node", { app: Coder, image: "node:22-bookworm" });
132
+ * export const Warm = Modal.Image("Warm", {
133
+ * app: Coder,
134
+ * from: Node,
135
+ * run: [
136
+ * "git clone https://github.com/alchemy-run/alchemy /work",
137
+ * "cd /work && corepack enable && pnpm install",
138
+ * ],
139
+ * });
140
+ * ```
141
+ *
142
+ * @resource
143
+ * @product Image
144
+ */
145
+ export const Image = declarable<Image>()(Resource<Image>("Modal.Image"));
146
+
147
+ export type Image = Resource<"Modal.Image", ImageProps, ImageAttributes, never, Providers>;
148
+
149
+ const idOf = <K extends string>(value: unknown, key: K): string | undefined =>
150
+ typeof value === "object" && value !== null && key in value
151
+ ? ((value as Record<K, unknown>)[key] as string | undefined)
152
+ : undefined;
153
+
154
+ /** The build spec for an Image's props (Dockerfile lowered, context collected). */
155
+ /**
156
+ * Plan an Image's build with the shared {@link BuildPlan}: its environment
157
+ * (`image`, `dockerfile`/`context`, or a Modal base via `from`) checked
158
+ * against what Modal's builder runs, then the `commands` appended.
159
+ */
160
+ const planOf = Effect.fn(function* (news: ImageProps) {
161
+ const fromId = idOf(news.from, "imageId");
162
+ const source =
163
+ fromId !== undefined
164
+ ? { dockerfile: { content: "FROM base\n" } }
165
+ : news.dockerfile !== undefined || news.context !== undefined
166
+ ? { dockerfile: news.dockerfile, context: news.context }
167
+ : { image: news.image ?? "debian:bookworm-slim" };
168
+ const plan = yield* BuildPlan.plan(source, { capabilities: MODAL_CAPABILITIES });
169
+ const extra = yield* checkCommands(news.commands ?? []);
170
+ const lowered = yield* lowerPlan(plan, extra);
171
+ const spec: ImageBuildSpec = {
172
+ ...lowered,
173
+ // `FROM base` names the Modal image passed as a base image.
174
+ ...(fromId !== undefined ? { baseImageId: fromId, commands: lowered.commands.slice(1) } : {}),
175
+ secretIds: (news.secrets ?? []).flatMap((s) => idOf(s, "secretId") ?? []),
176
+ registry: news.registry
177
+ ? { authType: news.registry.type, secretId: idOf(news.registry.secret, "secretId") ?? "" }
178
+ : undefined,
179
+ gpu: news.gpu,
180
+ builderVersion: news.builderVersion,
181
+ };
182
+ const hash = yield* sha256Object({
183
+ plan: plan.hash,
184
+ from: fromId,
185
+ extra,
186
+ secrets: spec.secretIds,
187
+ registry: spec.registry,
188
+ gpu: spec.gpu,
189
+ builder: spec.builderVersion,
190
+ // `tag` and `run` happen after the build; part of the hash so a change re-runs reconcile.
191
+ tag: news.tag,
192
+ run: news.run,
193
+ });
194
+ return { spec, hash, warnings: plan.warnings };
195
+ });
196
+
197
+ export const ImageProvider = () =>
198
+ Provider.effect(
199
+ Image,
200
+ Effect.gen(function* () {
201
+ return {
202
+ stables: [],
203
+ diff: Effect.fn(function* ({ news, output }) {
204
+ if (!isResolved(news) || output === undefined) return undefined;
205
+ // A changed build input (including files under `context`) is a new image.
206
+ const { hash } = yield* planOf(news);
207
+ return hash !== output.hash ? ({ action: "update" } as const) : undefined;
208
+ }),
209
+ read: Effect.fn(function* ({ output }) {
210
+ if (output === undefined) return undefined;
211
+ const found = yield* image
212
+ .imageFromId({ imageId: output.imageId })
213
+ .pipe(Effect.catchTag("ImageNotFound", () => Effect.undefined));
214
+ return found?.imageId ? output : undefined;
215
+ }),
216
+ reconcile: Effect.fn(function* ({ news, output, session }) {
217
+ const appId = idOf(news.app, "appId") ?? "";
218
+ const { spec, hash, warnings } = yield* planOf(news);
219
+ for (const warning of warnings) yield* session.note(warning);
220
+ // Observe: an image built from the same inputs still exists.
221
+ if (output !== undefined && output.hash === hash && output.appId === appId) {
222
+ const found = yield* image
223
+ .imageFromId({ imageId: output.imageId })
224
+ .pipe(Effect.catchTag("ImageNotFound", () => Effect.undefined));
225
+ if (found?.imageId) return output;
226
+ }
227
+ // Ensure: Modal content-addresses builds, so this is a no-op for
228
+ // inputs it has built before.
229
+ let imageId = yield* buildImage(yield* ImageBuildOps, appId, spec, {
230
+ note: session.note,
231
+ });
232
+ // Warm base: run setup in a sandbox and snapshot its filesystem.
233
+ if (news.run && news.run.length > 0) {
234
+ yield* session.note(`Running setup for image ${imageId}...`);
235
+ const box = yield* createSandbox(
236
+ yield* SandboxOps,
237
+ { appId },
238
+ {
239
+ image: { imageId },
240
+ secrets: spec.secretIds?.map((secretId) => ({ secretId })),
241
+ timeout: "1 hour",
242
+ },
243
+ );
244
+ const snapshot = yield* Effect.gen(function* () {
245
+ for (const command of news.run ?? []) {
246
+ const result = yield* box.exec(["sh", "-lc", command]);
247
+ if (result.exitCode !== 0) {
248
+ return yield* new ImageSetupFailed({
249
+ command,
250
+ exitCode: result.exitCode,
251
+ message: `\`${command}\` exited ${result.exitCode}: ${result.stderr.slice(-2000)}`,
252
+ });
253
+ }
254
+ }
255
+ return yield* box.snapshotFilesystem("5 minutes");
256
+ }).pipe(Effect.ensuring(Effect.ignore(box.terminate())));
257
+ imageId = snapshot.imageId;
258
+ }
259
+ if (news.tag !== undefined) {
260
+ yield* image.publishImage({
261
+ imageId,
262
+ tag: news.tag,
263
+ environmentName: idOf(news.app, "environment") ?? "",
264
+ });
265
+ }
266
+ return {
267
+ imageId,
268
+ appId,
269
+ appName: idOf(news.app, "name"),
270
+ environment: idOf(news.app, "environment"),
271
+ hash,
272
+ };
273
+ }),
274
+ delete: Effect.fn(function* ({ output }) {
275
+ yield* image
276
+ .deleteImage({ imageId: output.imageId })
277
+ .pipe(Effect.catchTag("ImageNotFound", () => Effect.void));
278
+ }),
279
+ };
280
+ }),
281
+ );
@@ -0,0 +1,228 @@
1
+ import { createHash } from "node:crypto";
2
+ import * as image from "@distilled.cloud/modal/image";
3
+ import * as mount from "@distilled.cloud/modal/mount";
4
+ import * as Data from "effect/Data";
5
+ import * as Effect from "effect/Effect";
6
+ import { BlobOps, MAX_INLINE_BYTES, uploadBlob } from "./Blob.ts";
7
+ import { operations } from "./internal.ts";
8
+
9
+ /** Modal's builder finished the image build with a failure. */
10
+ export class ImageBuildFailed extends Data.TaggedError("Modal.ImageBuildFailed")<{
11
+ readonly imageId: string;
12
+ readonly status: string;
13
+ readonly message: string;
14
+ /** The last lines the build printed. */
15
+ readonly logs: string;
16
+ }> {}
17
+
18
+ /** The build didn't finish within its time budget. */
19
+ export class ImageBuildTimeout extends Data.TaggedError("Modal.ImageBuildTimeout")<{
20
+ readonly imageId: string;
21
+ readonly message: string;
22
+ }> {}
23
+
24
+ /** What an image is built from, after Dockerfile lowering. */
25
+ export interface ImageBuildSpec {
26
+ /** A registry image to start from (`FROM <ref>`). */
27
+ readonly dockerTag?: string;
28
+ /** A Modal image to start from. */
29
+ readonly baseImageId?: string;
30
+ /**
31
+ * Dockerfile commands. With `dockerTag`/`baseImageId` they follow the
32
+ * generated `FROM`; otherwise they are a whole Dockerfile.
33
+ */
34
+ readonly commands: ReadonlyArray<string>;
35
+ /**
36
+ * Files the commands `COPY`, by context-relative path. They are uploaded
37
+ * as a mount owned by the app, so their size isn't limited by Modal's
38
+ * 2 MB cap on an image definition.
39
+ */
40
+ readonly contextFiles?: ReadonlyArray<{
41
+ readonly filename: string;
42
+ readonly data: Uint8Array;
43
+ /** Permission bits. @default 0o644 */
44
+ readonly mode?: number;
45
+ }>;
46
+ /**
47
+ * A mount whose files the commands `COPY` (e.g. Modal's standalone
48
+ * Python). Not combined with {@link contextFiles}.
49
+ */
50
+ readonly contextMountId?: string;
51
+ /** Build arguments (`ARG` values). */
52
+ readonly buildArgs?: Readonly<Record<string, string>>;
53
+ /** Secrets (ids) the build steps see as environment variables. */
54
+ readonly secretIds?: ReadonlyArray<string>;
55
+ /** Credentials to pull a private base image. */
56
+ readonly registry?: {
57
+ readonly authType: "aws" | "gcp" | "static";
58
+ readonly secretId: string;
59
+ };
60
+ /** Run the build on a GPU (`"A10G"`, `"H100"`, ...). */
61
+ readonly gpu?: string;
62
+ readonly builderVersion?: string;
63
+ readonly forceBuild?: boolean;
64
+ }
65
+
66
+ const AUTH_TYPE = {
67
+ aws: "REGISTRY_AUTH_TYPE_AWS",
68
+ gcp: "REGISTRY_AUTH_TYPE_GCP",
69
+ static: "REGISTRY_AUTH_TYPE_STATIC_CREDS",
70
+ } as const;
71
+
72
+ /** A `"A100-80GB:2"`-style GPU spec as Modal's `GPUConfig`. */
73
+ export const gpuConfig = (gpu: string | undefined): image.GPUConfig | undefined => {
74
+ if (gpu === undefined) return undefined;
75
+ const [type, count] = gpu.split(":");
76
+ return { gpuType: type!.toUpperCase(), count: count ? Number(count) : 1 };
77
+ };
78
+
79
+ /** The operations a build needs, resolved by `ModalBindings.withCredentials` (or at deploy). */
80
+ export const ImageBuildOps = operations({
81
+ getOrCreate: image.imageGetOrCreate,
82
+ joinStreaming: image.imageJoinStreaming,
83
+ putFile: mount.mountPutFile,
84
+ getOrCreateMount: mount.mountGetOrCreate,
85
+ blobs: BlobOps,
86
+ });
87
+
88
+ export type ImageBuildOps = Effect.Success<typeof ImageBuildOps>;
89
+
90
+ /** Modal returned a response without a field the build needs. */
91
+ export class ImageBuildUnexpectedResponse extends Data.TaggedError(
92
+ "Modal.ImageBuildUnexpectedResponse",
93
+ )<{
94
+ readonly message: string;
95
+ }> {}
96
+
97
+ /**
98
+ * Upload build context files as a mount owned by the app, the way Modal's
99
+ * clients send `COPY` sources. Modal stores files by content hash, so
100
+ * unchanged files are not sent again.
101
+ */
102
+ const contextMount = Effect.fn(function* (
103
+ ops: ImageBuildOps,
104
+ appId: string,
105
+ files: NonNullable<ImageBuildSpec["contextFiles"]>,
106
+ ) {
107
+ const entries = [];
108
+ for (const file of files) {
109
+ const sha256Hex = yield* Effect.sync(() =>
110
+ createHash("sha256").update(file.data).digest("hex"),
111
+ );
112
+ const known = yield* ops.putFile({ sha256Hex });
113
+ if (!known.exists) {
114
+ const upload =
115
+ file.data.length > MAX_INLINE_BYTES
116
+ ? { dataBlobId: yield* uploadBlob(ops.blobs, file.data) }
117
+ : { data: yield* Effect.sync(() => Buffer.from(file.data).toString("base64")) };
118
+ const put = yield* ops.putFile({ sha256Hex, ...upload });
119
+ if (!put.exists) {
120
+ return yield* new ImageBuildUnexpectedResponse({
121
+ message: `Modal didn't store build context file ${file.filename}`,
122
+ });
123
+ }
124
+ }
125
+ entries.push({
126
+ filename: `/${file.filename.replace(/^\/+/, "")}`,
127
+ sha256Hex,
128
+ size: String(file.data.length),
129
+ mode: 0o100000 | ((file.mode ?? 0o644) & 0o7777),
130
+ });
131
+ }
132
+ const { mountId } = yield* ops.getOrCreateMount({
133
+ objectCreationType: "OBJECT_CREATION_TYPE_ANONYMOUS_OWNED_BY_APP",
134
+ appId,
135
+ files: entries,
136
+ });
137
+ if (!mountId) {
138
+ return yield* new ImageBuildUnexpectedResponse({
139
+ message: "Modal returned no id for the build context mount",
140
+ });
141
+ }
142
+ return mountId;
143
+ });
144
+
145
+ const FINISHED = (status: string | undefined) =>
146
+ status !== undefined && status !== "GENERIC_STATUS_UNSPECIFIED";
147
+
148
+ /**
149
+ * Build (or reuse) an image with Modal's builder and wait until it is ready.
150
+ * Modal content-addresses images, so the same spec returns the same image
151
+ * without rebuilding. Fails with {@link ImageBuildFailed} on a failed build.
152
+ */
153
+ export const buildImage = Effect.fn(function* (
154
+ ops: ImageBuildOps,
155
+ appId: string,
156
+ spec: ImageBuildSpec,
157
+ options: { readonly note?: (message: string) => Effect.Effect<void> } = {},
158
+ ) {
159
+ const from = spec.baseImageId
160
+ ? { baseImages: [{ imageId: spec.baseImageId, dockerTag: "base" }], first: ["FROM base"] }
161
+ : spec.dockerTag
162
+ ? { baseImages: [], first: [`FROM ${spec.dockerTag}`] }
163
+ : { baseImages: [], first: [] as string[] };
164
+ const contextMountId =
165
+ spec.contextFiles !== undefined && spec.contextFiles.length > 0
166
+ ? yield* contextMount(ops, appId, spec.contextFiles)
167
+ : spec.contextMountId;
168
+ const created = yield* ops.getOrCreate({
169
+ appId,
170
+ image: {
171
+ baseImages: from.baseImages,
172
+ dockerfileCommands: [...from.first, ...spec.commands],
173
+ contextMountId,
174
+ secretIds: spec.secretIds ? [...spec.secretIds] : undefined,
175
+ imageRegistryConfig: spec.registry
176
+ ? { registryAuthType: AUTH_TYPE[spec.registry.authType], secretId: spec.registry.secretId }
177
+ : undefined,
178
+ gpuConfig: gpuConfig(spec.gpu),
179
+ buildArgs: spec.buildArgs ? { ...spec.buildArgs } : undefined,
180
+ },
181
+ builderVersion: spec.builderVersion,
182
+ forceBuild: spec.forceBuild,
183
+ });
184
+ const imageId = created.imageId;
185
+ if (!imageId) {
186
+ return yield* new ImageBuildFailed({
187
+ imageId: "",
188
+ status: "GENERIC_STATUS_UNSPECIFIED",
189
+ message: "Modal returned no image id",
190
+ logs: "",
191
+ });
192
+ }
193
+ let result = created.result;
194
+ let lastEntryId: string | undefined;
195
+ const logs: string[] = [];
196
+ // Each join call streams build logs for up to ~55 s; a build may take many.
197
+ for (let attempt = 0; !FINISHED(result?.status) && attempt < 60; attempt++) {
198
+ if (attempt === 0 && options.note) yield* options.note(`Building image ${imageId}...`);
199
+ const { items } = yield* ops.joinStreaming({
200
+ imageId,
201
+ timeout: 55,
202
+ lastEntryId,
203
+ includeLogsForFinished: true,
204
+ });
205
+ for (const item of items) {
206
+ if (item.entryId) lastEntryId = item.entryId;
207
+ for (const log of item.taskLogs ?? []) {
208
+ if (log.data) logs.push(log.data);
209
+ }
210
+ if (FINISHED(item.result?.status)) result = item.result;
211
+ }
212
+ }
213
+ if (!FINISHED(result?.status)) {
214
+ return yield* new ImageBuildTimeout({
215
+ imageId,
216
+ message: `Modal image ${imageId} was still building after 55 minutes`,
217
+ });
218
+ }
219
+ if (result?.status !== "GENERIC_STATUS_SUCCESS") {
220
+ return yield* new ImageBuildFailed({
221
+ imageId,
222
+ status: result?.status ?? "GENERIC_STATUS_UNSPECIFIED",
223
+ message: result?.exception || `Modal image build ${imageId} ended with ${result?.status}`,
224
+ logs: logs.join("").split("\n").slice(-40).join("\n"),
225
+ });
226
+ }
227
+ return imageId;
228
+ });