@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,309 @@
|
|
|
1
|
+
import * as BuildPlan from "@alchemy.run/docker/BuildPlan";
|
|
2
|
+
import * as app from "@distilled.cloud/modal/app";
|
|
3
|
+
import * as mount from "@distilled.cloud/modal/mount";
|
|
4
|
+
import * as workspace from "@distilled.cloud/modal/workspace";
|
|
5
|
+
import type * as Bundle from "alchemy/Bundle/Bundle";
|
|
6
|
+
import { type MountCapability, mountInstallLines } from "alchemy/Mount";
|
|
7
|
+
import { sha256, sha256Object } from "alchemy/Util/sha256";
|
|
8
|
+
import * as Data from "effect/Data";
|
|
9
|
+
import * as Effect from "effect/Effect";
|
|
10
|
+
import * as Semaphore from "effect/Semaphore";
|
|
11
|
+
import { buildImage, ImageBuildOps, type ImageBuildSpec } from "../ImageBuild.ts";
|
|
12
|
+
import { checkCommands, lowerPlan, MODAL_CAPABILITIES } from "../ImagePlan.ts";
|
|
13
|
+
import { checkModalMounts } from "../Mounts.ts";
|
|
14
|
+
import { ENTRYPOINT_PATH, ENTRYPOINT_SOURCE } from "./Entrypoint.ts";
|
|
15
|
+
|
|
16
|
+
/*
|
|
17
|
+
* Deploy-time plumbing shared by `Modal.Function` and `Modal.Server`: the
|
|
18
|
+
* Python layer and mounts every Modal Function container needs, and
|
|
19
|
+
* publishing a function into its App.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/** Modal returned a response without a field the deploy needs. */
|
|
23
|
+
export class ModalDeployUnexpectedResponse extends Data.TaggedError(
|
|
24
|
+
"Modal.DeployUnexpectedResponse",
|
|
25
|
+
)<{
|
|
26
|
+
readonly message: string;
|
|
27
|
+
}> {}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The Modal Python client whose container entrypoint runs the function.
|
|
31
|
+
* Modal publishes it as the global mount `modal-client-mount-<version>`.
|
|
32
|
+
*/
|
|
33
|
+
export const MODAL_CLIENT_VERSION = "1.6.3";
|
|
34
|
+
|
|
35
|
+
/** Standalone Python builds Modal publishes as global mounts, by image builder version. */
|
|
36
|
+
const PYTHON_STANDALONE: Record<
|
|
37
|
+
"2026.10" | "2025.06",
|
|
38
|
+
Record<string, readonly [string, string]>
|
|
39
|
+
> = {
|
|
40
|
+
"2026.10": {
|
|
41
|
+
"3.11": ["20261021", "3.11.17"],
|
|
42
|
+
"3.12": ["20261021", "3.12.15"],
|
|
43
|
+
"3.13": ["20261021", "3.13.17"],
|
|
44
|
+
"3.14": ["20261021", "3.14.9"],
|
|
45
|
+
},
|
|
46
|
+
"2025.06": {
|
|
47
|
+
"3.11": ["20230826", "3.11.5"],
|
|
48
|
+
"3.12": ["20240107", "3.12.1"],
|
|
49
|
+
"3.13": ["20241008", "3.13.0"],
|
|
50
|
+
"3.14": ["20251205", "3.14.2"],
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/** The Python version added to a base image without one. */
|
|
55
|
+
export const DEFAULT_PYTHON = "3.12";
|
|
56
|
+
|
|
57
|
+
/** Port the program listens on when the resource doesn't set one. */
|
|
58
|
+
export const DEFAULT_PORT = 8000;
|
|
59
|
+
|
|
60
|
+
const lookupMount = (deploymentName: string) =>
|
|
61
|
+
Effect.gen(function* () {
|
|
62
|
+
const { mountId } = yield* mount.mountGetOrCreate({
|
|
63
|
+
deploymentName,
|
|
64
|
+
namespace: "DEPLOYMENT_NAMESPACE_GLOBAL",
|
|
65
|
+
objectCreationType: "OBJECT_CREATION_TYPE_UNSPECIFIED",
|
|
66
|
+
});
|
|
67
|
+
if (!mountId) {
|
|
68
|
+
return yield* new ModalDeployUnexpectedResponse({
|
|
69
|
+
message: `Modal returned no id for mount ${deploymentName}`,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
return mountId;
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
/** The mount holding Modal's Python client, which runs the container entrypoint. */
|
|
76
|
+
export const clientMount = /*@__PURE__*/ lookupMount(`modal-client-mount-${MODAL_CLIENT_VERSION}`);
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* The image builder version builds use: the resource's pin, else the
|
|
80
|
+
* workspace's setting.
|
|
81
|
+
*/
|
|
82
|
+
const builderVersionOf = Effect.fn(function* (pinned: string | undefined) {
|
|
83
|
+
if (pinned !== undefined) return pinned;
|
|
84
|
+
const settings = yield* workspace.workspaceSettings({});
|
|
85
|
+
return settings.imageBuilderVersion ?? "2025.06";
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Upload {@link ENTRYPOINT_SOURCE} as a mount owned by the app. Modal stores
|
|
90
|
+
* files by content hash, so redeploys reuse it.
|
|
91
|
+
*/
|
|
92
|
+
export const entrypointMount = Effect.fn(function* (appId: string) {
|
|
93
|
+
const bytes = new TextEncoder().encode(ENTRYPOINT_SOURCE);
|
|
94
|
+
const hash = yield* sha256(bytes);
|
|
95
|
+
const data = yield* Effect.sync(() => Buffer.from(bytes).toString("base64"));
|
|
96
|
+
for (let attempt = 0; attempt < 10; attempt++) {
|
|
97
|
+
const { exists } = yield* mount.mountPutFile({ sha256Hex: hash, data });
|
|
98
|
+
if (exists) break;
|
|
99
|
+
}
|
|
100
|
+
const { mountId } = yield* mount.mountGetOrCreate({
|
|
101
|
+
objectCreationType: "OBJECT_CREATION_TYPE_ANONYMOUS_OWNED_BY_APP",
|
|
102
|
+
appId,
|
|
103
|
+
files: [
|
|
104
|
+
{ filename: ENTRYPOINT_PATH, sha256Hex: hash, size: String(bytes.length), mode: 0o100644 },
|
|
105
|
+
],
|
|
106
|
+
});
|
|
107
|
+
if (!mountId) {
|
|
108
|
+
return yield* new ModalDeployUnexpectedResponse({
|
|
109
|
+
message: "Modal returned no id for the entrypoint mount",
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
return mountId;
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
/** What a runtime's image is built from. */
|
|
116
|
+
export interface RuntimeImageInput {
|
|
117
|
+
readonly main: string;
|
|
118
|
+
readonly image?: string;
|
|
119
|
+
readonly commands?: ReadonlyArray<string>;
|
|
120
|
+
readonly handler?: string;
|
|
121
|
+
readonly build?: Bundle.BundleConfig;
|
|
122
|
+
readonly isExternal?: boolean;
|
|
123
|
+
readonly python?: string | false;
|
|
124
|
+
readonly builderVersion?: string;
|
|
125
|
+
readonly gpu?: string;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Plan a runtime's image with the shared BuildPlan: the bundled program on
|
|
130
|
+
* its base image, without the image's `ENTRYPOINT`/`CMD` (Modal starts the
|
|
131
|
+
* container with its own Python entrypoint, which then runs the program).
|
|
132
|
+
*/
|
|
133
|
+
export const planRuntimeImage = Effect.fn(function* (
|
|
134
|
+
target: string,
|
|
135
|
+
input: RuntimeImageInput,
|
|
136
|
+
port: number,
|
|
137
|
+
bootstrap: ((importPath: string) => string) | undefined,
|
|
138
|
+
capabilities: ReadonlyArray<MountCapability> = [],
|
|
139
|
+
) {
|
|
140
|
+
yield* checkModalMounts(target, capabilities);
|
|
141
|
+
const plan = yield* BuildPlan.plan(
|
|
142
|
+
{ main: input.main, image: input.image, handler: input.handler, build: input.build },
|
|
143
|
+
{
|
|
144
|
+
capabilities: { ...MODAL_CAPABILITIES, target },
|
|
145
|
+
port,
|
|
146
|
+
bootstrap,
|
|
147
|
+
install: mountInstallLines(capabilities),
|
|
148
|
+
},
|
|
149
|
+
);
|
|
150
|
+
const extra = yield* checkCommands(input.commands ?? []);
|
|
151
|
+
const lowered = yield* lowerPlan(plan, extra);
|
|
152
|
+
const command = [...(BuildPlan.startCommand(plan) ?? [])];
|
|
153
|
+
// Lines after `FROM`, minus the start instruction, run on top of the Python layer.
|
|
154
|
+
const commands = lowered.commands
|
|
155
|
+
.slice(1)
|
|
156
|
+
.filter((line) => !/^\s*(ENTRYPOINT|CMD)\b/i.test(line));
|
|
157
|
+
const python = input.python === undefined ? DEFAULT_PYTHON : input.python;
|
|
158
|
+
const hash = yield* sha256Object({
|
|
159
|
+
plan: plan.hash,
|
|
160
|
+
extra,
|
|
161
|
+
python,
|
|
162
|
+
builder: input.builderVersion,
|
|
163
|
+
gpu: input.gpu,
|
|
164
|
+
});
|
|
165
|
+
return {
|
|
166
|
+
plan,
|
|
167
|
+
command,
|
|
168
|
+
hash,
|
|
169
|
+
warnings: plan.warnings,
|
|
170
|
+
spec: { ...lowered, commands: [...commands, "ENTRYPOINT []"] } satisfies ImageBuildSpec,
|
|
171
|
+
python,
|
|
172
|
+
};
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Build a runtime's image: the base, a layer adding standalone Python
|
|
177
|
+
* (unless `python: false`), then the program.
|
|
178
|
+
*/
|
|
179
|
+
export const buildRuntimeImage = Effect.fn(function* (
|
|
180
|
+
appId: string,
|
|
181
|
+
planned: {
|
|
182
|
+
readonly plan: BuildPlan.BuildPlan;
|
|
183
|
+
readonly spec: ImageBuildSpec;
|
|
184
|
+
readonly python: string | false;
|
|
185
|
+
},
|
|
186
|
+
options: {
|
|
187
|
+
readonly builderVersion?: string;
|
|
188
|
+
readonly gpu?: string;
|
|
189
|
+
readonly note?: (message: string) => Effect.Effect<void>;
|
|
190
|
+
},
|
|
191
|
+
) {
|
|
192
|
+
const builderVersion = yield* builderVersionOf(options.builderVersion);
|
|
193
|
+
const buildOps = yield* ImageBuildOps;
|
|
194
|
+
let baseImageId: string | undefined;
|
|
195
|
+
if (planned.python !== false) {
|
|
196
|
+
const table =
|
|
197
|
+
builderVersion >= "2026.10" || builderVersion === "PREVIEW"
|
|
198
|
+
? PYTHON_STANDALONE["2026.10"]
|
|
199
|
+
: PYTHON_STANDALONE["2025.06"];
|
|
200
|
+
const release = table[planned.python];
|
|
201
|
+
if (release === undefined) {
|
|
202
|
+
return yield* new ModalDeployUnexpectedResponse({
|
|
203
|
+
message: `Python ${planned.python} isn't available for image builder ${builderVersion}; use one of ${Object.keys(table).join(", ")}`,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
const contextMountId = yield* lookupMount(
|
|
207
|
+
`python-build-standalone.${release[0]}.${release[1]}-gnu`,
|
|
208
|
+
);
|
|
209
|
+
baseImageId = yield* buildImage(
|
|
210
|
+
buildOps,
|
|
211
|
+
appId,
|
|
212
|
+
{
|
|
213
|
+
dockerTag: planned.plan.base,
|
|
214
|
+
commands: [
|
|
215
|
+
"COPY /python/. /usr/local",
|
|
216
|
+
"ENV TERMINFO_DIRS=/etc/terminfo:/lib/terminfo:/usr/share/terminfo:/usr/lib/terminfo",
|
|
217
|
+
],
|
|
218
|
+
contextMountId,
|
|
219
|
+
registry: undefined,
|
|
220
|
+
builderVersion,
|
|
221
|
+
},
|
|
222
|
+
{ note: options.note },
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
return yield* buildImage(
|
|
226
|
+
buildOps,
|
|
227
|
+
appId,
|
|
228
|
+
{
|
|
229
|
+
...planned.spec,
|
|
230
|
+
...(baseImageId !== undefined ? { baseImageId } : { dockerTag: planned.plan.base }),
|
|
231
|
+
gpu: options.gpu,
|
|
232
|
+
builderVersion,
|
|
233
|
+
},
|
|
234
|
+
{ note: options.note },
|
|
235
|
+
);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
/** Serializes publishes per App: each publish replaces the App's whole function map. */
|
|
239
|
+
const appLocks = /*@__PURE__*/ new Map<string, Semaphore.Semaphore>();
|
|
240
|
+
const lockFor = (appId: string) => {
|
|
241
|
+
let lock = appLocks.get(appId);
|
|
242
|
+
if (lock === undefined) {
|
|
243
|
+
lock = Semaphore.makeUnsafe(1);
|
|
244
|
+
appLocks.set(appId, lock);
|
|
245
|
+
}
|
|
246
|
+
return lock;
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Publish the App with `tag` pointing at `created` (or removed, when
|
|
251
|
+
* `created` is `undefined`), keeping every other function and class the
|
|
252
|
+
* App serves. Modal deploys an App as a whole, so a publish carries the full
|
|
253
|
+
* map read back from the App's current layout.
|
|
254
|
+
*
|
|
255
|
+
* A class function (one with a `classId`) is published as Modal's clients
|
|
256
|
+
* publish `@app.cls`: its service function under `<tag>.*` and its class
|
|
257
|
+
* under `<tag>`, where its containers look the class up.
|
|
258
|
+
*/
|
|
259
|
+
/** The service function name of the class `tag`, as Modal's clients name it. */
|
|
260
|
+
export const classTag = (tag: string) => `${tag}.*`;
|
|
261
|
+
|
|
262
|
+
export const publishFunction = (
|
|
263
|
+
target: { readonly appId: string; readonly appName: string | undefined },
|
|
264
|
+
tag: string,
|
|
265
|
+
created:
|
|
266
|
+
| {
|
|
267
|
+
readonly functionId: string;
|
|
268
|
+
readonly definitionId: string | undefined;
|
|
269
|
+
readonly classId?: string | undefined;
|
|
270
|
+
}
|
|
271
|
+
| undefined,
|
|
272
|
+
) =>
|
|
273
|
+
Semaphore.withPermits(
|
|
274
|
+
lockFor(target.appId),
|
|
275
|
+
1,
|
|
276
|
+
)(
|
|
277
|
+
Effect.gen(function* () {
|
|
278
|
+
const { appLayout } = yield* app.appGetLayout({ appId: target.appId });
|
|
279
|
+
const functionIds: Record<string, string> = {};
|
|
280
|
+
for (const [key, value] of Object.entries(appLayout?.functionIds ?? {})) {
|
|
281
|
+
if (value !== undefined && key !== tag && key !== classTag(tag)) functionIds[key] = value;
|
|
282
|
+
}
|
|
283
|
+
if (created !== undefined) {
|
|
284
|
+
functionIds[created.classId ? classTag(tag) : tag] = created.functionId;
|
|
285
|
+
}
|
|
286
|
+
const classIds: Record<string, string> = {};
|
|
287
|
+
for (const [key, value] of Object.entries(appLayout?.classIds ?? {})) {
|
|
288
|
+
if (value !== undefined && key !== tag) classIds[key] = value;
|
|
289
|
+
}
|
|
290
|
+
if (created?.classId) classIds[tag] = created.classId;
|
|
291
|
+
const published = new Set(Object.values(functionIds));
|
|
292
|
+
const definitionIds: Record<string, string> = {};
|
|
293
|
+
for (const object of appLayout?.objects ?? []) {
|
|
294
|
+
const definitionId = object.functionHandleMetadata?.definitionId;
|
|
295
|
+
if (object.objectId && definitionId && published.has(object.objectId)) {
|
|
296
|
+
definitionIds[object.objectId] = definitionId;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
if (created?.definitionId) definitionIds[created.functionId] = created.definitionId;
|
|
300
|
+
return yield* app.publishApp({
|
|
301
|
+
appId: target.appId,
|
|
302
|
+
name: target.appName,
|
|
303
|
+
appState: "APP_STATE_DEPLOYED",
|
|
304
|
+
functionIds,
|
|
305
|
+
classIds,
|
|
306
|
+
definitionIds,
|
|
307
|
+
});
|
|
308
|
+
}),
|
|
309
|
+
);
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Python module a `Modal.Function` or `Modal.Server` container imports.
|
|
3
|
+
*
|
|
4
|
+
* Modal boots every Function container with its Python entrypoint, which
|
|
5
|
+
* imports the function's module and looks the function up by name. This
|
|
6
|
+
* module is that function: it starts the bundled program (`ALCHEMY_COMMAND`,
|
|
7
|
+
* from the function's env secret) once per container and hands it work as
|
|
8
|
+
* HTTP on `PORT`:
|
|
9
|
+
*
|
|
10
|
+
* - `Rpc` (a class function) has one method per RPC method of the program
|
|
11
|
+
* (`ALCHEMY_MODAL_RPC_METHODS`, from the function's env secret); a call is
|
|
12
|
+
* sent to the program's `POST /__rpc__/<method>` with the positional
|
|
13
|
+
* arguments as a JSON array and the caller's key (the
|
|
14
|
+
* `__alchemy_rpc_key__` keyword argument) as `x-alchemy-rpc-key`, and
|
|
15
|
+
* returns the JSON result (a value, or an
|
|
16
|
+
* error envelope the caller fails with). A streamed result is read to its
|
|
17
|
+
* end and returned as its list of values;
|
|
18
|
+
* - `call` and `schedule` forward each input as `POST /__modal/call` or
|
|
19
|
+
* `POST /__modal/schedule` with a JSON body `{ "args": [...], "kwargs":
|
|
20
|
+
* {...} }`, and return the JSON response (an async program's calls, and
|
|
21
|
+
* schedule ticks);
|
|
22
|
+
* - `web` (a `WEBHOOK_TYPE_WEB_SERVER` function) starts the program, then
|
|
23
|
+
* Modal proxies its `*.modal.run` URL to `PORT`;
|
|
24
|
+
* - `Server` (`is_server`) starts the program in `@modal.enter`, then Modal
|
|
25
|
+
* routes the Server's URL to `PORT`.
|
|
26
|
+
*
|
|
27
|
+
* It's uploaded as a Mount (`/root/alchemy_modal.py`); the module name and
|
|
28
|
+
* its function names are part of every function definition, so changing
|
|
29
|
+
* them redeploys every function.
|
|
30
|
+
*/
|
|
31
|
+
export const ENTRYPOINT_MODULE = "alchemy_modal";
|
|
32
|
+
|
|
33
|
+
export const ENTRYPOINT_PATH = `/root/${ENTRYPOINT_MODULE}.py`;
|
|
34
|
+
|
|
35
|
+
/** Function implementations in {@link ENTRYPOINT_SOURCE}. */
|
|
36
|
+
export const ENTRYPOINT = {
|
|
37
|
+
call: "call",
|
|
38
|
+
schedule: "schedule",
|
|
39
|
+
web: "web",
|
|
40
|
+
server: "Server",
|
|
41
|
+
rpc: "Rpc",
|
|
42
|
+
} as const;
|
|
43
|
+
|
|
44
|
+
/** Env var listing a program's RPC methods (a JSON array), set at plan. */
|
|
45
|
+
export const RPC_METHODS_ENV = "ALCHEMY_MODAL_RPC_METHODS";
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Env var that turns on the program's `/__rpc__/*` routes. Set only where
|
|
49
|
+
* Modal authenticates every request: a class function's calls and a Server
|
|
50
|
+
* behind proxy auth. A public web endpoint never serves RPC.
|
|
51
|
+
*/
|
|
52
|
+
export const RPC_SERVE_ENV = "ALCHEMY_MODAL_SERVE_RPC";
|
|
53
|
+
|
|
54
|
+
/** A streamed RPC result, as {@link ENTRYPOINT_SOURCE} returns it: its values in order. */
|
|
55
|
+
export const RPC_STREAM_KEY = "~alchemy/modal/rpc-stream";
|
|
56
|
+
|
|
57
|
+
export const ENTRYPOINT_SOURCE = `"""Alchemy's entrypoint for a JavaScript program on Modal (generated, do not edit)."""
|
|
58
|
+
|
|
59
|
+
import base64
|
|
60
|
+
import http.client
|
|
61
|
+
import json
|
|
62
|
+
import os
|
|
63
|
+
import socket
|
|
64
|
+
import subprocess
|
|
65
|
+
import threading
|
|
66
|
+
import time
|
|
67
|
+
|
|
68
|
+
import modal
|
|
69
|
+
|
|
70
|
+
_PORT = int(os.environ.get("PORT", "8000"))
|
|
71
|
+
_lock = threading.Lock()
|
|
72
|
+
_process = None
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _start():
|
|
76
|
+
global _process
|
|
77
|
+
with _lock:
|
|
78
|
+
if _process is None or _process.poll() is not None:
|
|
79
|
+
_process = subprocess.Popen(json.loads(os.environ["ALCHEMY_COMMAND"]))
|
|
80
|
+
return _process
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _wait_until_listening(process, timeout=600.0):
|
|
84
|
+
deadline = time.monotonic() + timeout
|
|
85
|
+
while time.monotonic() < deadline:
|
|
86
|
+
if process.poll() is not None:
|
|
87
|
+
raise RuntimeError(f"The program exited with code {process.returncode} before listening on port {_PORT}")
|
|
88
|
+
try:
|
|
89
|
+
socket.create_connection(("127.0.0.1", _PORT), timeout=1).close()
|
|
90
|
+
return
|
|
91
|
+
except OSError:
|
|
92
|
+
time.sleep(0.05)
|
|
93
|
+
raise TimeoutError(f"The program didn't listen on port {_PORT} within {timeout:.0f} seconds")
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _json_default(value):
|
|
97
|
+
if isinstance(value, (bytes, bytearray, memoryview)):
|
|
98
|
+
return base64.b64encode(bytes(value)).decode("ascii")
|
|
99
|
+
raise TypeError(f"{type(value).__name__} isn't JSON-serializable")
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _forward(path, args, kwargs):
|
|
103
|
+
_wait_until_listening(_start())
|
|
104
|
+
body = json.dumps({"args": list(args), "kwargs": kwargs}, default=_json_default).encode()
|
|
105
|
+
headers = {"content-type": "application/json"}
|
|
106
|
+
try:
|
|
107
|
+
call_id = modal.current_function_call_id()
|
|
108
|
+
if call_id:
|
|
109
|
+
headers["x-modal-function-call-id"] = call_id
|
|
110
|
+
except Exception:
|
|
111
|
+
pass
|
|
112
|
+
connection = http.client.HTTPConnection("127.0.0.1", _PORT)
|
|
113
|
+
try:
|
|
114
|
+
connection.request("POST", path, body=body, headers=headers)
|
|
115
|
+
response = connection.getresponse()
|
|
116
|
+
data = response.read()
|
|
117
|
+
finally:
|
|
118
|
+
connection.close()
|
|
119
|
+
if response.status >= 400:
|
|
120
|
+
raise RuntimeError(f"{path} returned {response.status}: {data.decode(errors='replace')}")
|
|
121
|
+
if not data:
|
|
122
|
+
return None
|
|
123
|
+
if response.getheader("content-type", "").startswith("application/json"):
|
|
124
|
+
return json.loads(data)
|
|
125
|
+
return data.decode(errors="replace")
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _post(path, body, headers=None):
|
|
129
|
+
_wait_until_listening(_start())
|
|
130
|
+
connection = http.client.HTTPConnection("127.0.0.1", _PORT)
|
|
131
|
+
try:
|
|
132
|
+
connection.request("POST", path, body=body, headers={"content-type": "application/json", **(headers or {})})
|
|
133
|
+
response = connection.getresponse()
|
|
134
|
+
return response, response.read()
|
|
135
|
+
finally:
|
|
136
|
+
connection.close()
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _rpc(name):
|
|
140
|
+
def method(self, *args, **kwargs):
|
|
141
|
+
key = kwargs.pop("__alchemy_rpc_key__", None)
|
|
142
|
+
if kwargs:
|
|
143
|
+
raise TypeError(f"RPC method {name} takes positional arguments only")
|
|
144
|
+
body = json.dumps(list(args), default=_json_default).encode()
|
|
145
|
+
headers = {"x-alchemy-rpc-key": key} if isinstance(key, str) else {}
|
|
146
|
+
response, data = _post("/__rpc__/" + name, body, headers)
|
|
147
|
+
if response.status >= 400:
|
|
148
|
+
raise RuntimeError(f"RPC method {name} returned {response.status}: {data.decode(errors='replace')}")
|
|
149
|
+
if response.getheader("x-alchemy-rpc-stream") is not None:
|
|
150
|
+
lines = [json.loads(line) for line in data.decode().splitlines() if line]
|
|
151
|
+
return {"~alchemy/modal/rpc-stream": lines}
|
|
152
|
+
return json.loads(data) if data else None
|
|
153
|
+
|
|
154
|
+
method.__name__ = name
|
|
155
|
+
return modal.method()(method)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _rpc_enter(self):
|
|
159
|
+
_wait_until_listening(_start())
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
Rpc = type(
|
|
163
|
+
"Rpc",
|
|
164
|
+
(),
|
|
165
|
+
{
|
|
166
|
+
"start": modal.enter()(_rpc_enter),
|
|
167
|
+
**{name: _rpc(name) for name in json.loads(os.environ.get("ALCHEMY_MODAL_RPC_METHODS") or "[]")},
|
|
168
|
+
},
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def call(*args, **kwargs):
|
|
173
|
+
return _forward("/__modal/call", args, kwargs)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def schedule(*args, **kwargs):
|
|
177
|
+
return _forward("/__modal/schedule", args, kwargs)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def web():
|
|
181
|
+
_start()
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class Server:
|
|
185
|
+
@modal.enter()
|
|
186
|
+
def start(self):
|
|
187
|
+
_wait_until_listening(_start())
|
|
188
|
+
|
|
189
|
+
@modal.exit()
|
|
190
|
+
def stop(self):
|
|
191
|
+
process = _process
|
|
192
|
+
if process is not None and process.poll() is None:
|
|
193
|
+
process.terminate()
|
|
194
|
+
try:
|
|
195
|
+
process.wait(timeout=20)
|
|
196
|
+
except subprocess.TimeoutExpired:
|
|
197
|
+
process.kill()
|
|
198
|
+
`;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { safeHttpEffect } from "alchemy/Http";
|
|
2
|
+
import * as Output from "alchemy/Output";
|
|
3
|
+
import { serveRpc } from "alchemy/Rpc";
|
|
4
|
+
import { createHostRuntimeContext, type HostRuntimeContext } from "alchemy/Runtime/Process";
|
|
5
|
+
import * as Effect from "effect/Effect";
|
|
6
|
+
import { RPC_METHODS_ENV, RPC_SERVE_ENV } from "./Entrypoint.ts";
|
|
7
|
+
import { parseCallers, requireRpcCaller, RPC_CALLERS_ENV } from "./RpcAuth.ts";
|
|
8
|
+
|
|
9
|
+
/** The RPC methods of a program's shape: every function besides `fetch` and `run`. */
|
|
10
|
+
export const rpcMethodsOf = (shape: Record<string, unknown> | undefined) =>
|
|
11
|
+
Object.fromEntries(
|
|
12
|
+
Object.entries(shape ?? {}).filter(
|
|
13
|
+
([key, value]) => key !== "fetch" && key !== "run" && typeof value === "function",
|
|
14
|
+
),
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
/** The method names a function's env records (see {@link RPC_METHODS_ENV}). */
|
|
18
|
+
export const rpcMethodNamesOf = (env: Record<string, unknown> | undefined): string[] => {
|
|
19
|
+
const value = env?.[RPC_METHODS_ENV];
|
|
20
|
+
if (typeof value !== "string") return [];
|
|
21
|
+
try {
|
|
22
|
+
const names = JSON.parse(value) as unknown;
|
|
23
|
+
return Array.isArray(names) ? names.filter((name) => typeof name === "string") : [];
|
|
24
|
+
} catch {
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The runtime context of a `Modal.Function` or `Modal.Server` program. At
|
|
31
|
+
* plan it records the program's RPC methods in its env, so the Function's
|
|
32
|
+
* definition can name them. At run time it serves them on `/__rpc__/*`
|
|
33
|
+
* next to `fetch`, but only where the provider set {@link RPC_SERVE_ENV}
|
|
34
|
+
* (where Modal authenticates every request), and only to callers holding a
|
|
35
|
+
* key a binding issued (see `RpcAuth.ts`).
|
|
36
|
+
*/
|
|
37
|
+
export const createModalRuntimeContext =
|
|
38
|
+
(type: string) =>
|
|
39
|
+
(id: string): HostRuntimeContext => {
|
|
40
|
+
const base = createHostRuntimeContext(type)(id);
|
|
41
|
+
const serveBase = base.serve;
|
|
42
|
+
const serve: HostRuntimeContext["serve"] = (handler, options) =>
|
|
43
|
+
Effect.gen(function* () {
|
|
44
|
+
const methods = rpcMethodsOf(options?.shape);
|
|
45
|
+
const names = Object.keys(methods).sort();
|
|
46
|
+
if (!globalThis.__ALCHEMY_RUNTIME__ && names.length > 0) {
|
|
47
|
+
yield* base.set(RPC_METHODS_ENV, Output.literal(names));
|
|
48
|
+
}
|
|
49
|
+
const enabled = (yield* base.get<unknown>(RPC_SERVE_ENV)) !== undefined;
|
|
50
|
+
if (!enabled || names.length === 0) return yield* serveBase(handler, options);
|
|
51
|
+
const fallback = safeHttpEffect(handler);
|
|
52
|
+
const callers = parseCallers(yield* base.get<unknown>(RPC_CALLERS_ENV));
|
|
53
|
+
return yield* serveBase(
|
|
54
|
+
requireRpcCaller(callers, serveRpc(methods, fallback), fallback),
|
|
55
|
+
options,
|
|
56
|
+
);
|
|
57
|
+
}) as Effect.Effect<void, never, any>;
|
|
58
|
+
return Object.assign(base, { serve });
|
|
59
|
+
};
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import * as Binding from "alchemy/Binding";
|
|
2
|
+
import * as Output from "alchemy/Output";
|
|
3
|
+
import { Random } from "alchemy/Random";
|
|
4
|
+
import { rpcMethodOf } from "alchemy/Rpc";
|
|
5
|
+
import { CurrentRuntimeContext, sanitizeKey } from "alchemy/RuntimeContext";
|
|
6
|
+
import * as Effect from "effect/Effect";
|
|
7
|
+
import { HttpServerRequest } from "effect/http/HttpServerRequest";
|
|
8
|
+
import * as HttpServerResponse from "effect/http/HttpServerResponse";
|
|
9
|
+
import * as Redacted from "effect/Redacted";
|
|
10
|
+
|
|
11
|
+
/*
|
|
12
|
+
* Who may call a program's RPC methods. Every binding that calls them
|
|
13
|
+
* (`Modal.InvokeFunction`, `Modal.FetchServer`) gets its own random key: the
|
|
14
|
+
* caller keeps the key, the target keeps only its SHA-256, and each RPC call
|
|
15
|
+
* carries the key. A key opens one target for one caller, so a target can't
|
|
16
|
+
* replay what it receives to another target. Modal's own access control
|
|
17
|
+
* (proxy auth, environment roles) still applies underneath.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/** Env var on the target: the SHA-256 (hex) of each caller's key, as a JSON array. */
|
|
21
|
+
export const RPC_CALLERS_ENV = "ALCHEMY_MODAL_RPC_CALLERS";
|
|
22
|
+
|
|
23
|
+
/** Request header a Server's RPC call carries its key in. */
|
|
24
|
+
export const RPC_KEY_HEADER = "x-alchemy-rpc-key";
|
|
25
|
+
|
|
26
|
+
/** Keyword argument a Function's RPC call carries its key in (the entrypoint forwards it as {@link RPC_KEY_HEADER}). */
|
|
27
|
+
export const RPC_KEY_KWARG = "__alchemy_rpc_key__";
|
|
28
|
+
|
|
29
|
+
/** Env var on the caller holding its key for the target `targetId`. */
|
|
30
|
+
export const rpcKeyEnv = (targetId: string) => sanitizeKey(`ALCHEMY_MODAL_RPC_KEY_${targetId}`);
|
|
31
|
+
|
|
32
|
+
/** What a caller's binding attaches to the target. */
|
|
33
|
+
export interface RpcCallerBinding {
|
|
34
|
+
/** SHA-256 (hex) of the caller's key. */
|
|
35
|
+
readonly rpcCallers?: ReadonlyArray<string>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** SHA-256 of `text`, as hex. */
|
|
39
|
+
export const sha256Hex = (text: string) =>
|
|
40
|
+
Effect.promise(() => crypto.subtle.digest("SHA-256", new TextEncoder().encode(text))).pipe(
|
|
41
|
+
Effect.map((digest) =>
|
|
42
|
+
Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join(""),
|
|
43
|
+
),
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
/** The caller key hashes a target's bindings recorded, sorted. */
|
|
47
|
+
export const rpcCallersOf = (
|
|
48
|
+
bindings: ReadonlyArray<{ readonly data: RpcCallerBinding }>,
|
|
49
|
+
): string[] => [...new Set(bindings.flatMap((binding) => binding.data.rpcCallers ?? []))].sort();
|
|
50
|
+
|
|
51
|
+
type BindTarget = {
|
|
52
|
+
readonly LogicalId: string;
|
|
53
|
+
bind: (...args: unknown[]) => (data: RpcCallerBinding) => Effect.Effect<void>;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The caller half of an RPC binding. Yield it while the binding's layer is
|
|
58
|
+
* built; call the result with the target in the binding. At deploy it mints
|
|
59
|
+
* the key for this caller and target (`<host><target>RpcKey`), binds it into
|
|
60
|
+
* the host's env, and records its hash on the target. At run time it reads
|
|
61
|
+
* the key back, or `undefined` when the caller has none (a script).
|
|
62
|
+
*/
|
|
63
|
+
export const RpcCaller = Effect.gen(function* () {
|
|
64
|
+
// Deploy only: a host bundle never creates the key.
|
|
65
|
+
const Key = globalThis.__ALCHEMY_RUNTIME__ ? undefined : yield* Random;
|
|
66
|
+
return Effect.fn(function* (target: { readonly LogicalId: string }) {
|
|
67
|
+
// The host binding the target, read per call: one binding layer serves
|
|
68
|
+
// every host in the Stack, so a host captured when the layer was built
|
|
69
|
+
// would be the first one to build it.
|
|
70
|
+
const runtime = yield* CurrentRuntimeContext;
|
|
71
|
+
const env = rpcKeyEnv(target.LogicalId);
|
|
72
|
+
if (!globalThis.__ALCHEMY_RUNTIME__ && Key !== undefined && runtime !== undefined) {
|
|
73
|
+
const host = yield* Binding.Host;
|
|
74
|
+
if (host !== undefined) {
|
|
75
|
+
const key = yield* Key(`${host.LogicalId}${target.LogicalId}RpcKey`, {});
|
|
76
|
+
yield* runtime.set(env, key.text);
|
|
77
|
+
yield* (target as unknown as BindTarget).bind`Modal.RpcCaller(${host})`({
|
|
78
|
+
rpcCallers: [
|
|
79
|
+
key.text.pipe(
|
|
80
|
+
Output.mapEffect((text) => text.pipe(Redacted.value, sha256Hex)),
|
|
81
|
+
) as unknown as string,
|
|
82
|
+
],
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return Effect.gen(function* () {
|
|
87
|
+
if (runtime === undefined) return undefined;
|
|
88
|
+
const value = yield* runtime.get<string | Redacted.Redacted<string>>(env);
|
|
89
|
+
return value === undefined
|
|
90
|
+
? undefined
|
|
91
|
+
: Redacted.isRedacted(value)
|
|
92
|
+
? Redacted.value(value)
|
|
93
|
+
: value;
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Serve `rpc` only to callers whose key's hash is in {@link RPC_CALLERS_ENV};
|
|
100
|
+
* every other request goes to `fallback`. A call without a key, or with one
|
|
101
|
+
* no binding issued, gets `401`.
|
|
102
|
+
*/
|
|
103
|
+
export const requireRpcCaller = <A, E, R, A2, E2, R2>(
|
|
104
|
+
callers: ReadonlySet<string>,
|
|
105
|
+
rpc: Effect.Effect<A, E, R>,
|
|
106
|
+
fallback: Effect.Effect<A2, E2, R2>,
|
|
107
|
+
) =>
|
|
108
|
+
Effect.gen(function* () {
|
|
109
|
+
const request = yield* HttpServerRequest;
|
|
110
|
+
if (rpcMethodOf(request) === undefined) return yield* fallback;
|
|
111
|
+
const key = request.headers[RPC_KEY_HEADER];
|
|
112
|
+
if (typeof key !== "string" || key.length === 0 || !callers.has(yield* sha256Hex(key))) {
|
|
113
|
+
return HttpServerResponse.text("Unauthorized: this caller has no binding to the program", {
|
|
114
|
+
status: 401,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
return yield* rpc;
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
/** The caller key hashes in a target's env (see {@link RPC_CALLERS_ENV}). */
|
|
121
|
+
export const parseCallers = (value: unknown): ReadonlySet<string> => {
|
|
122
|
+
const list = typeof value === "string" ? safeParse(value) : value;
|
|
123
|
+
return new Set(Array.isArray(list) ? list.filter((item) => typeof item === "string") : []);
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const safeParse = (text: string): unknown => {
|
|
127
|
+
try {
|
|
128
|
+
return JSON.parse(text);
|
|
129
|
+
} catch {
|
|
130
|
+
return undefined;
|
|
131
|
+
}
|
|
132
|
+
};
|