@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.
- package/LICENSE +201 -0
- package/README.md +96 -4
- package/lib/App.d.ts +143 -0
- package/lib/App.d.ts.map +1 -0
- package/lib/App.js +198 -0
- package/lib/App.js.map +1 -0
- package/lib/AuthProvider.d.ts +53 -0
- package/lib/AuthProvider.d.ts.map +1 -0
- package/lib/AuthProvider.js +209 -0
- package/lib/AuthProvider.js.map +1 -0
- package/lib/BindingHttp.d.ts +56 -0
- package/lib/BindingHttp.d.ts.map +1 -0
- package/lib/BindingHttp.js +50 -0
- package/lib/BindingHttp.js.map +1 -0
- package/lib/Blob.d.ts +34 -0
- package/lib/Blob.d.ts.map +1 -0
- package/lib/Blob.js +59 -0
- package/lib/Blob.js.map +1 -0
- package/lib/Bootstrap.d.ts +8 -0
- package/lib/Bootstrap.d.ts.map +1 -0
- package/lib/Bootstrap.js +26 -0
- package/lib/Bootstrap.js.map +1 -0
- package/lib/Cbor.d.ts +10 -0
- package/lib/Cbor.d.ts.map +1 -0
- package/lib/Cbor.js +177 -0
- package/lib/Cbor.js.map +1 -0
- package/lib/ConnectSandbox.d.ts +64 -0
- package/lib/ConnectSandbox.d.ts.map +1 -0
- package/lib/ConnectSandbox.js +33 -0
- package/lib/ConnectSandbox.js.map +1 -0
- package/lib/Container.d.ts +124 -0
- package/lib/Container.d.ts.map +1 -0
- package/lib/Container.js +1 -0
- package/lib/Container.js.map +1 -0
- package/lib/Contributions.d.ts +7 -0
- package/lib/Contributions.d.ts.map +1 -0
- package/lib/Contributions.js +6 -0
- package/lib/Contributions.js.map +1 -0
- package/lib/CreateDict.d.ts +83 -0
- package/lib/CreateDict.d.ts.map +1 -0
- package/lib/CreateDict.js +3 -0
- package/lib/CreateDict.js.map +1 -0
- package/lib/CreateDictHttp.d.ts +33 -0
- package/lib/CreateDictHttp.d.ts.map +1 -0
- package/lib/CreateDictHttp.js +67 -0
- package/lib/CreateDictHttp.js.map +1 -0
- package/lib/CreateImage.d.ts +98 -0
- package/lib/CreateImage.d.ts.map +1 -0
- package/lib/CreateImage.js +3 -0
- package/lib/CreateImage.js.map +1 -0
- package/lib/CreateImageHttp.d.ts +51 -0
- package/lib/CreateImageHttp.d.ts.map +1 -0
- package/lib/CreateImageHttp.js +87 -0
- package/lib/CreateImageHttp.js.map +1 -0
- package/lib/CreateQueue.d.ts +83 -0
- package/lib/CreateQueue.d.ts.map +1 -0
- package/lib/CreateQueue.js +3 -0
- package/lib/CreateQueue.js.map +1 -0
- package/lib/CreateQueueHttp.d.ts +31 -0
- package/lib/CreateQueueHttp.d.ts.map +1 -0
- package/lib/CreateQueueHttp.js +67 -0
- package/lib/CreateQueueHttp.js.map +1 -0
- package/lib/CreateSandbox.d.ts +186 -0
- package/lib/CreateSandbox.d.ts.map +1 -0
- package/lib/CreateSandbox.js +3 -0
- package/lib/CreateSandbox.js.map +1 -0
- package/lib/CreateSandboxHttp.d.ts +17 -0
- package/lib/CreateSandboxHttp.d.ts.map +1 -0
- package/lib/CreateSandboxHttp.js +117 -0
- package/lib/CreateSandboxHttp.js.map +1 -0
- package/lib/CreateSecret.d.ts +95 -0
- package/lib/CreateSecret.d.ts.map +1 -0
- package/lib/CreateSecret.js +3 -0
- package/lib/CreateSecret.js.map +1 -0
- package/lib/CreateSecretHttp.d.ts +24 -0
- package/lib/CreateSecretHttp.d.ts.map +1 -0
- package/lib/CreateSecretHttp.js +60 -0
- package/lib/CreateSecretHttp.js.map +1 -0
- package/lib/CreateVolume.d.ts +84 -0
- package/lib/CreateVolume.d.ts.map +1 -0
- package/lib/CreateVolume.js +3 -0
- package/lib/CreateVolume.js.map +1 -0
- package/lib/CreateVolumeHttp.d.ts +32 -0
- package/lib/CreateVolumeHttp.d.ts.map +1 -0
- package/lib/CreateVolumeHttp.js +69 -0
- package/lib/CreateVolumeHttp.js.map +1 -0
- package/lib/Credentials.d.ts +32 -0
- package/lib/Credentials.d.ts.map +1 -0
- package/lib/Credentials.js +44 -0
- package/lib/Credentials.js.map +1 -0
- package/lib/Dict.d.ts +147 -0
- package/lib/Dict.d.ts.map +1 -0
- package/lib/Dict.js +175 -0
- package/lib/Dict.js.map +1 -0
- package/lib/DictClient.d.ts +55 -0
- package/lib/DictClient.d.ts.map +1 -0
- package/lib/DictClient.js +89 -0
- package/lib/DictClient.js.map +1 -0
- package/lib/Domain.d.ts +86 -0
- package/lib/Domain.d.ts.map +1 -0
- package/lib/Domain.js +113 -0
- package/lib/Domain.js.map +1 -0
- package/lib/Endpoint.d.ts +181 -0
- package/lib/Endpoint.d.ts.map +1 -0
- package/lib/Endpoint.js +283 -0
- package/lib/Endpoint.js.map +1 -0
- package/lib/Environment.d.ts +174 -0
- package/lib/Environment.d.ts.map +1 -0
- package/lib/Environment.js +244 -0
- package/lib/Environment.js.map +1 -0
- package/lib/FetchServer.d.ts +105 -0
- package/lib/FetchServer.d.ts.map +1 -0
- package/lib/FetchServer.js +3 -0
- package/lib/FetchServer.js.map +1 -0
- package/lib/FetchServerHttp.d.ts +21 -0
- package/lib/FetchServerHttp.d.ts.map +1 -0
- package/lib/FetchServerHttp.js +84 -0
- package/lib/FetchServerHttp.js.map +1 -0
- package/lib/Function.d.ts +236 -0
- package/lib/Function.d.ts.map +1 -0
- package/lib/Function.js +273 -0
- package/lib/Function.js.map +1 -0
- package/lib/FunctionAutoscaler.d.ts +99 -0
- package/lib/FunctionAutoscaler.d.ts.map +1 -0
- package/lib/FunctionAutoscaler.js +136 -0
- package/lib/FunctionAutoscaler.js.map +1 -0
- package/lib/FunctionTarget.d.ts +61 -0
- package/lib/FunctionTarget.d.ts.map +1 -0
- package/lib/FunctionTarget.js +136 -0
- package/lib/FunctionTarget.js.map +1 -0
- package/lib/Image.d.ts +159 -0
- package/lib/Image.d.ts.map +1 -0
- package/lib/Image.js +203 -0
- package/lib/Image.js.map +1 -0
- package/lib/ImageBuild.d.ts +108 -0
- package/lib/ImageBuild.d.ts.map +1 -0
- package/lib/ImageBuild.js +157 -0
- package/lib/ImageBuild.js.map +1 -0
- package/lib/ImagePlan.d.ts +31 -0
- package/lib/ImagePlan.d.ts.map +1 -0
- package/lib/ImagePlan.js +70 -0
- package/lib/ImagePlan.js.map +1 -0
- package/lib/InferEnv.d.ts +26 -0
- package/lib/InferEnv.d.ts.map +1 -0
- package/lib/InferEnv.js +1 -0
- package/lib/InferEnv.js.map +1 -0
- package/lib/InvokeFunction.d.ts +186 -0
- package/lib/InvokeFunction.d.ts.map +1 -0
- package/lib/InvokeFunction.js +10 -0
- package/lib/InvokeFunction.js.map +1 -0
- package/lib/InvokeFunctionHttp.d.ts +39 -0
- package/lib/InvokeFunctionHttp.d.ts.map +1 -0
- package/lib/InvokeFunctionHttp.js +210 -0
- package/lib/InvokeFunctionHttp.js.map +1 -0
- package/lib/ModalBindings.d.ts +138 -0
- package/lib/ModalBindings.d.ts.map +1 -0
- package/lib/ModalBindings.js +223 -0
- package/lib/ModalBindings.js.map +1 -0
- package/lib/MountBucket.d.ts +80 -0
- package/lib/MountBucket.d.ts.map +1 -0
- package/lib/MountBucket.js +44 -0
- package/lib/MountBucket.js.map +1 -0
- package/lib/MountVolume.d.ts +77 -0
- package/lib/MountVolume.d.ts.map +1 -0
- package/lib/MountVolume.js +41 -0
- package/lib/MountVolume.js.map +1 -0
- package/lib/Mounts.d.ts +134 -0
- package/lib/Mounts.d.ts.map +1 -0
- package/lib/Mounts.js +87 -0
- package/lib/Mounts.js.map +1 -0
- package/lib/PersistentSandbox.d.ts +75 -0
- package/lib/PersistentSandbox.d.ts.map +1 -0
- package/lib/PersistentSandbox.js +93 -0
- package/lib/PersistentSandbox.js.map +1 -0
- package/lib/Providers.d.ts +23 -0
- package/lib/Providers.d.ts.map +1 -0
- package/lib/Providers.js +64 -0
- package/lib/Providers.js.map +1 -0
- package/lib/Proxy.d.ts +94 -0
- package/lib/Proxy.d.ts.map +1 -0
- package/lib/Proxy.js +132 -0
- package/lib/Proxy.js.map +1 -0
- package/lib/Queue.d.ts +147 -0
- package/lib/Queue.d.ts.map +1 -0
- package/lib/Queue.js +177 -0
- package/lib/Queue.js.map +1 -0
- package/lib/QueueClient.d.ts +64 -0
- package/lib/QueueClient.d.ts.map +1 -0
- package/lib/QueueClient.js +73 -0
- package/lib/QueueClient.js.map +1 -0
- package/lib/ReadApp.d.ts +85 -0
- package/lib/ReadApp.d.ts.map +1 -0
- package/lib/ReadApp.js +3 -0
- package/lib/ReadApp.js.map +1 -0
- package/lib/ReadAppHttp.d.ts +26 -0
- package/lib/ReadAppHttp.d.ts.map +1 -0
- package/lib/ReadAppHttp.js +106 -0
- package/lib/ReadAppHttp.js.map +1 -0
- package/lib/ReadDict.d.ts +39 -0
- package/lib/ReadDict.d.ts.map +1 -0
- package/lib/ReadDict.js +3 -0
- package/lib/ReadDict.js.map +1 -0
- package/lib/ReadDictHttp.d.ts +12 -0
- package/lib/ReadDictHttp.d.ts.map +1 -0
- package/lib/ReadDictHttp.js +23 -0
- package/lib/ReadDictHttp.js.map +1 -0
- package/lib/ReadFunction.d.ts +73 -0
- package/lib/ReadFunction.d.ts.map +1 -0
- package/lib/ReadFunction.js +3 -0
- package/lib/ReadFunction.js.map +1 -0
- package/lib/ReadFunctionHttp.d.ts +25 -0
- package/lib/ReadFunctionHttp.d.ts.map +1 -0
- package/lib/ReadFunctionHttp.js +63 -0
- package/lib/ReadFunctionHttp.js.map +1 -0
- package/lib/ReadQueue.d.ts +39 -0
- package/lib/ReadQueue.d.ts.map +1 -0
- package/lib/ReadQueue.js +3 -0
- package/lib/ReadQueue.js.map +1 -0
- package/lib/ReadQueueHttp.d.ts +12 -0
- package/lib/ReadQueueHttp.d.ts.map +1 -0
- package/lib/ReadQueueHttp.js +23 -0
- package/lib/ReadQueueHttp.js.map +1 -0
- package/lib/ReadServer.d.ts +72 -0
- package/lib/ReadServer.d.ts.map +1 -0
- package/lib/ReadServer.js +3 -0
- package/lib/ReadServer.js.map +1 -0
- package/lib/ReadServerHttp.d.ts +25 -0
- package/lib/ReadServerHttp.d.ts.map +1 -0
- package/lib/ReadServerHttp.js +70 -0
- package/lib/ReadServerHttp.js.map +1 -0
- package/lib/ReadVolume.d.ts +40 -0
- package/lib/ReadVolume.d.ts.map +1 -0
- package/lib/ReadVolume.js +3 -0
- package/lib/ReadVolume.js.map +1 -0
- package/lib/ReadVolumeHttp.d.ts +12 -0
- package/lib/ReadVolumeHttp.d.ts.map +1 -0
- package/lib/ReadVolumeHttp.js +24 -0
- package/lib/ReadVolumeHttp.js.map +1 -0
- package/lib/ReadWriteDict.d.ts +39 -0
- package/lib/ReadWriteDict.d.ts.map +1 -0
- package/lib/ReadWriteDict.js +3 -0
- package/lib/ReadWriteDict.js.map +1 -0
- package/lib/ReadWriteDictHttp.d.ts +12 -0
- package/lib/ReadWriteDictHttp.d.ts.map +1 -0
- package/lib/ReadWriteDictHttp.js +23 -0
- package/lib/ReadWriteDictHttp.js.map +1 -0
- package/lib/ReadWriteQueue.d.ts +40 -0
- package/lib/ReadWriteQueue.d.ts.map +1 -0
- package/lib/ReadWriteQueue.js +3 -0
- package/lib/ReadWriteQueue.js.map +1 -0
- package/lib/ReadWriteQueueHttp.d.ts +12 -0
- package/lib/ReadWriteQueueHttp.d.ts.map +1 -0
- package/lib/ReadWriteQueueHttp.js +23 -0
- package/lib/ReadWriteQueueHttp.js.map +1 -0
- package/lib/ReadWriteVolume.d.ts +40 -0
- package/lib/ReadWriteVolume.d.ts.map +1 -0
- package/lib/ReadWriteVolume.js +3 -0
- package/lib/ReadWriteVolume.js.map +1 -0
- package/lib/ReadWriteVolumeHttp.d.ts +12 -0
- package/lib/ReadWriteVolumeHttp.d.ts.map +1 -0
- package/lib/ReadWriteVolumeHttp.js +24 -0
- package/lib/ReadWriteVolumeHttp.js.map +1 -0
- package/lib/RuntimeOwner.d.ts +39 -0
- package/lib/RuntimeOwner.d.ts.map +1 -0
- package/lib/RuntimeOwner.js +85 -0
- package/lib/RuntimeOwner.js.map +1 -0
- package/lib/Sandbox.d.ts +215 -0
- package/lib/Sandbox.d.ts.map +1 -0
- package/lib/Sandbox.js +278 -0
- package/lib/Sandbox.js.map +1 -0
- package/lib/SandboxClient.d.ts +748 -0
- package/lib/SandboxClient.d.ts.map +1 -0
- package/lib/SandboxClient.js +710 -0
- package/lib/SandboxClient.js.map +1 -0
- package/lib/ScaleFunction.d.ts +54 -0
- package/lib/ScaleFunction.d.ts.map +1 -0
- package/lib/ScaleFunction.js +3 -0
- package/lib/ScaleFunction.js.map +1 -0
- package/lib/ScaleFunctionHttp.d.ts +21 -0
- package/lib/ScaleFunctionHttp.d.ts.map +1 -0
- package/lib/ScaleFunctionHttp.js +39 -0
- package/lib/ScaleFunctionHttp.js.map +1 -0
- package/lib/Secret.d.ts +170 -0
- package/lib/Secret.d.ts.map +1 -0
- package/lib/Secret.js +196 -0
- package/lib/Secret.js.map +1 -0
- package/lib/Server.d.ts +155 -0
- package/lib/Server.d.ts.map +1 -0
- package/lib/Server.js +184 -0
- package/lib/Server.js.map +1 -0
- package/lib/ServiceUser.d.ts +139 -0
- package/lib/ServiceUser.d.ts.map +1 -0
- package/lib/ServiceUser.js +210 -0
- package/lib/ServiceUser.js.map +1 -0
- package/lib/Volume.d.ts +170 -0
- package/lib/Volume.d.ts.map +1 -0
- package/lib/Volume.js +220 -0
- package/lib/Volume.js.map +1 -0
- package/lib/VolumeClient.d.ts +53 -0
- package/lib/VolumeClient.d.ts.map +1 -0
- package/lib/VolumeClient.js +22 -0
- package/lib/VolumeClient.js.map +1 -0
- package/lib/VolumeFiles.d.ts +94 -0
- package/lib/VolumeFiles.d.ts.map +1 -0
- package/lib/VolumeFiles.js +139 -0
- package/lib/VolumeFiles.js.map +1 -0
- package/lib/WebhookToken.d.ts +102 -0
- package/lib/WebhookToken.d.ts.map +1 -0
- package/lib/WebhookToken.js +176 -0
- package/lib/WebhookToken.js.map +1 -0
- package/lib/WorkspaceSettings.d.ts +70 -0
- package/lib/WorkspaceSettings.d.ts.map +1 -0
- package/lib/WorkspaceSettings.js +105 -0
- package/lib/WorkspaceSettings.js.map +1 -0
- package/lib/WriteApp.d.ts +38 -0
- package/lib/WriteApp.d.ts.map +1 -0
- package/lib/WriteApp.js +3 -0
- package/lib/WriteApp.js.map +1 -0
- package/lib/WriteAppHttp.d.ts +23 -0
- package/lib/WriteAppHttp.d.ts.map +1 -0
- package/lib/WriteAppHttp.js +36 -0
- package/lib/WriteAppHttp.js.map +1 -0
- package/lib/WriteDict.d.ts +39 -0
- package/lib/WriteDict.d.ts.map +1 -0
- package/lib/WriteDict.js +3 -0
- package/lib/WriteDict.js.map +1 -0
- package/lib/WriteDictHttp.d.ts +12 -0
- package/lib/WriteDictHttp.d.ts.map +1 -0
- package/lib/WriteDictHttp.js +23 -0
- package/lib/WriteDictHttp.js.map +1 -0
- package/lib/WriteQueue.d.ts +39 -0
- package/lib/WriteQueue.d.ts.map +1 -0
- package/lib/WriteQueue.js +3 -0
- package/lib/WriteQueue.js.map +1 -0
- package/lib/WriteQueueHttp.d.ts +12 -0
- package/lib/WriteQueueHttp.d.ts.map +1 -0
- package/lib/WriteQueueHttp.js +23 -0
- package/lib/WriteQueueHttp.js.map +1 -0
- package/lib/WriteSecret.d.ts +53 -0
- package/lib/WriteSecret.d.ts.map +1 -0
- package/lib/WriteSecret.js +3 -0
- package/lib/WriteSecret.js.map +1 -0
- package/lib/WriteSecretHttp.d.ts +23 -0
- package/lib/WriteSecretHttp.d.ts.map +1 -0
- package/lib/WriteSecretHttp.js +44 -0
- package/lib/WriteSecretHttp.js.map +1 -0
- package/lib/WriteVolume.d.ts +39 -0
- package/lib/WriteVolume.d.ts.map +1 -0
- package/lib/WriteVolume.js +3 -0
- package/lib/WriteVolume.js.map +1 -0
- package/lib/WriteVolumeHttp.d.ts +12 -0
- package/lib/WriteVolumeHttp.d.ts.map +1 -0
- package/lib/WriteVolumeHttp.js +24 -0
- package/lib/WriteVolumeHttp.js.map +1 -0
- package/lib/index.d.ts +80 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +78 -0
- package/lib/index.js.map +1 -0
- package/lib/internal/Declared.d.ts +29 -0
- package/lib/internal/Declared.d.ts.map +1 -0
- package/lib/internal/Declared.js +31 -0
- package/lib/internal/Declared.js.map +1 -0
- package/lib/internal/Deploy.d.ts +110 -0
- package/lib/internal/Deploy.d.ts.map +1 -0
- package/lib/internal/Deploy.js +230 -0
- package/lib/internal/Deploy.js.map +1 -0
- package/lib/internal/Entrypoint.d.ts +52 -0
- package/lib/internal/Entrypoint.d.ts.map +1 -0
- package/lib/internal/Entrypoint.js +193 -0
- package/lib/internal/Entrypoint.js.map +1 -0
- package/lib/internal/Rpc.d.ts +17 -0
- package/lib/internal/Rpc.d.ts.map +1 -0
- package/lib/internal/Rpc.js +49 -0
- package/lib/internal/Rpc.js.map +1 -0
- package/lib/internal/RpcAuth.d.ts +41 -0
- package/lib/internal/RpcAuth.d.ts.map +1 -0
- package/lib/internal/RpcAuth.js +100 -0
- package/lib/internal/RpcAuth.js.map +1 -0
- package/lib/internal/Runtime.d.ts +127 -0
- package/lib/internal/Runtime.d.ts.map +1 -0
- package/lib/internal/Runtime.js +246 -0
- package/lib/internal/Runtime.js.map +1 -0
- package/lib/internal/Tags.d.ts +19 -0
- package/lib/internal/Tags.d.ts.map +1 -0
- package/lib/internal/Tags.js +33 -0
- package/lib/internal/Tags.js.map +1 -0
- package/lib/internal/TcpConnect.d.ts +12 -0
- package/lib/internal/TcpConnect.d.ts.map +1 -0
- package/lib/internal/TcpConnect.js +46 -0
- package/lib/internal/TcpConnect.js.map +1 -0
- package/lib/internal/Transport.d.ts +13 -0
- package/lib/internal/Transport.d.ts.map +1 -0
- package/lib/internal/Transport.js +16 -0
- package/lib/internal/Transport.js.map +1 -0
- package/lib/internal.d.ts +71 -0
- package/lib/internal.d.ts.map +1 -0
- package/lib/internal.js +102 -0
- package/lib/internal.js.map +1 -0
- package/package.json +59 -3
- package/src/App.ts +262 -0
- package/src/AuthProvider.ts +295 -0
- package/src/BindingHttp.ts +112 -0
- package/src/Blob.ts +71 -0
- package/src/Bootstrap.ts +52 -0
- package/src/Cbor.ts +158 -0
- package/src/ConnectSandbox.ts +99 -0
- package/src/Container.ts +121 -0
- package/src/Contributions.ts +9 -0
- package/src/CreateDict.ts +95 -0
- package/src/CreateDictHttp.ts +91 -0
- package/src/CreateImage.ts +105 -0
- package/src/CreateImageHttp.ts +117 -0
- package/src/CreateQueue.ts +95 -0
- package/src/CreateQueueHttp.ts +91 -0
- package/src/CreateSandbox.ts +228 -0
- package/src/CreateSandboxHttp.ts +160 -0
- package/src/CreateSecret.ts +109 -0
- package/src/CreateSecretHttp.ts +83 -0
- package/src/CreateVolume.ts +96 -0
- package/src/CreateVolumeHttp.ts +93 -0
- package/src/Credentials.ts +74 -0
- package/src/Dict.ts +245 -0
- package/src/DictClient.ts +166 -0
- package/src/Domain.ts +147 -0
- package/src/Endpoint.ts +406 -0
- package/src/Environment.ts +373 -0
- package/src/FetchServer.ts +126 -0
- package/src/FetchServerHttp.ts +110 -0
- package/src/Function.ts +397 -0
- package/src/FunctionAutoscaler.ts +210 -0
- package/src/FunctionTarget.ts +181 -0
- package/src/Image.ts +281 -0
- package/src/ImageBuild.ts +228 -0
- package/src/ImagePlan.ts +78 -0
- package/src/InferEnv.ts +27 -0
- package/src/InvokeFunction.ts +211 -0
- package/src/InvokeFunctionHttp.ts +316 -0
- package/src/ModalBindings.ts +310 -0
- package/src/MountBucket.ts +137 -0
- package/src/MountVolume.ts +113 -0
- package/src/Mounts.ts +230 -0
- package/src/PersistentSandbox.ts +142 -0
- package/src/Providers.ts +100 -0
- package/src/Proxy.ts +191 -0
- package/src/Queue.ts +258 -0
- package/src/QueueClient.ts +149 -0
- package/src/ReadApp.ts +101 -0
- package/src/ReadAppHttp.ts +142 -0
- package/src/ReadDict.ts +44 -0
- package/src/ReadDictHttp.ts +27 -0
- package/src/ReadFunction.ts +107 -0
- package/src/ReadFunctionHttp.ts +91 -0
- package/src/ReadQueue.ts +44 -0
- package/src/ReadQueueHttp.ts +27 -0
- package/src/ReadServer.ts +98 -0
- package/src/ReadServerHttp.ts +101 -0
- package/src/ReadVolume.ts +45 -0
- package/src/ReadVolumeHttp.ts +28 -0
- package/src/ReadWriteDict.ts +44 -0
- package/src/ReadWriteDictHttp.ts +27 -0
- package/src/ReadWriteQueue.ts +45 -0
- package/src/ReadWriteQueueHttp.ts +27 -0
- package/src/ReadWriteVolume.ts +45 -0
- package/src/ReadWriteVolumeHttp.ts +28 -0
- package/src/RuntimeOwner.ts +111 -0
- package/src/Sandbox.ts +402 -0
- package/src/SandboxClient.ts +1404 -0
- package/src/ScaleFunction.ts +70 -0
- package/src/ScaleFunctionHttp.ts +54 -0
- package/src/Secret.ts +296 -0
- package/src/Server.ts +285 -0
- package/src/ServiceUser.ts +295 -0
- package/src/Volume.ts +320 -0
- package/src/VolumeClient.ts +108 -0
- package/src/VolumeFiles.ts +194 -0
- package/src/WebhookToken.ts +230 -0
- package/src/WorkspaceSettings.ts +160 -0
- package/src/WriteApp.ts +43 -0
- package/src/WriteAppHttp.ts +51 -0
- package/src/WriteDict.ts +44 -0
- package/src/WriteDictHttp.ts +27 -0
- package/src/WriteQueue.ts +44 -0
- package/src/WriteQueueHttp.ts +27 -0
- package/src/WriteSecret.ts +60 -0
- package/src/WriteSecretHttp.ts +56 -0
- package/src/WriteVolume.ts +44 -0
- package/src/WriteVolumeHttp.ts +28 -0
- package/src/globals.d.ts +2 -0
- package/src/index.ts +85 -0
- package/src/internal/Declared.ts +80 -0
- package/src/internal/Deploy.ts +309 -0
- package/src/internal/Entrypoint.ts +198 -0
- package/src/internal/Rpc.ts +59 -0
- package/src/internal/RpcAuth.ts +132 -0
- package/src/internal/Runtime.ts +386 -0
- package/src/internal/Tags.ts +44 -0
- package/src/internal/TcpConnect.ts +78 -0
- package/src/internal/Transport.ts +25 -0
- 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
|
+
});
|