alchemy 2.0.0-beta.65 → 2.0.0-beta.67
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/bin/exec.js +578 -224
- package/bin/exec.js.map +1 -1
- package/lib/AWS/ApiGateway/common.d.ts.map +1 -1
- package/lib/AWS/ApiGateway/common.js +8 -1
- package/lib/AWS/ApiGateway/common.js.map +1 -1
- package/lib/AWS/Bedrock/LanguageModel.d.ts +208 -0
- package/lib/AWS/Bedrock/LanguageModel.d.ts.map +1 -0
- package/lib/AWS/Bedrock/LanguageModel.js +508 -0
- package/lib/AWS/Bedrock/LanguageModel.js.map +1 -0
- package/lib/AWS/Bedrock/LanguageModelHttp.d.ts +10 -0
- package/lib/AWS/Bedrock/LanguageModelHttp.d.ts.map +1 -0
- package/lib/AWS/Bedrock/LanguageModelHttp.js +26 -0
- package/lib/AWS/Bedrock/LanguageModelHttp.js.map +1 -0
- package/lib/AWS/Bedrock/index.d.ts +2 -0
- package/lib/AWS/Bedrock/index.d.ts.map +1 -1
- package/lib/AWS/Bedrock/index.js +2 -0
- package/lib/AWS/Bedrock/index.js.map +1 -1
- package/lib/AWS/CloudFront/Distribution.d.ts +24 -0
- package/lib/AWS/CloudFront/Distribution.d.ts.map +1 -1
- package/lib/AWS/CloudFront/Distribution.js +89 -4
- package/lib/AWS/CloudFront/Distribution.js.map +1 -1
- package/lib/AWS/CostExplorer/AnomalyMonitor.d.ts +1 -1
- package/lib/AWS/CostExplorer/AnomalyMonitor.d.ts.map +1 -1
- package/lib/AWS/CostExplorer/AnomalySubscription.d.ts +1 -1
- package/lib/AWS/CostExplorer/AnomalySubscription.d.ts.map +1 -1
- package/lib/AWS/CostExplorer/BindingHttp.d.ts +3 -3
- package/lib/AWS/CostExplorer/BindingHttp.d.ts.map +1 -1
- package/lib/AWS/CostExplorer/CostCategory.d.ts +1 -1
- package/lib/AWS/CostExplorer/CostCategory.d.ts.map +1 -1
- package/lib/AWS/CostExplorer/GetAnomaliesHttp.d.ts +1 -1
- package/lib/AWS/CostExplorer/GetAnomaliesHttp.d.ts.map +1 -1
- package/lib/AWS/CostExplorer/GetApproximateUsageRecordsHttp.d.ts +1 -1
- package/lib/AWS/CostExplorer/GetApproximateUsageRecordsHttp.d.ts.map +1 -1
- package/lib/AWS/CostExplorer/GetCommitmentPurchaseAnalysisHttp.d.ts +1 -1
- package/lib/AWS/CostExplorer/GetCommitmentPurchaseAnalysisHttp.d.ts.map +1 -1
- package/lib/AWS/CostExplorer/GetCostAndUsageComparisonsHttp.d.ts +1 -1
- package/lib/AWS/CostExplorer/GetCostAndUsageComparisonsHttp.d.ts.map +1 -1
- package/lib/AWS/CostExplorer/GetCostAndUsageHttp.d.ts +1 -1
- package/lib/AWS/CostExplorer/GetCostAndUsageHttp.d.ts.map +1 -1
- package/lib/AWS/CostExplorer/GetCostAndUsageWithResourcesHttp.d.ts +1 -1
- package/lib/AWS/CostExplorer/GetCostAndUsageWithResourcesHttp.d.ts.map +1 -1
- package/lib/AWS/CostExplorer/GetCostCategoriesHttp.d.ts +1 -1
- package/lib/AWS/CostExplorer/GetCostCategoriesHttp.d.ts.map +1 -1
- package/lib/AWS/CostExplorer/GetCostComparisonDriversHttp.d.ts +1 -1
- package/lib/AWS/CostExplorer/GetCostComparisonDriversHttp.d.ts.map +1 -1
- package/lib/AWS/CostExplorer/GetCostForecastHttp.d.ts +1 -1
- package/lib/AWS/CostExplorer/GetCostForecastHttp.d.ts.map +1 -1
- package/lib/AWS/CostExplorer/GetDimensionValuesHttp.d.ts +1 -1
- package/lib/AWS/CostExplorer/GetDimensionValuesHttp.d.ts.map +1 -1
- package/lib/AWS/CostExplorer/GetReservationCoverageHttp.d.ts +1 -1
- package/lib/AWS/CostExplorer/GetReservationCoverageHttp.d.ts.map +1 -1
- package/lib/AWS/CostExplorer/GetReservationPurchaseRecommendationHttp.d.ts +1 -1
- package/lib/AWS/CostExplorer/GetReservationPurchaseRecommendationHttp.d.ts.map +1 -1
- package/lib/AWS/CostExplorer/GetReservationUtilizationHttp.d.ts +1 -1
- package/lib/AWS/CostExplorer/GetReservationUtilizationHttp.d.ts.map +1 -1
- package/lib/AWS/CostExplorer/GetRightsizingRecommendationHttp.d.ts +1 -1
- package/lib/AWS/CostExplorer/GetRightsizingRecommendationHttp.d.ts.map +1 -1
- package/lib/AWS/CostExplorer/GetSavingsPlanPurchaseRecommendationDetailsHttp.d.ts +1 -1
- package/lib/AWS/CostExplorer/GetSavingsPlanPurchaseRecommendationDetailsHttp.d.ts.map +1 -1
- package/lib/AWS/CostExplorer/GetSavingsPlansCoverageHttp.d.ts +1 -1
- package/lib/AWS/CostExplorer/GetSavingsPlansCoverageHttp.d.ts.map +1 -1
- package/lib/AWS/CostExplorer/GetSavingsPlansPurchaseRecommendationHttp.d.ts +1 -1
- package/lib/AWS/CostExplorer/GetSavingsPlansPurchaseRecommendationHttp.d.ts.map +1 -1
- package/lib/AWS/CostExplorer/GetSavingsPlansUtilizationDetailsHttp.d.ts +1 -1
- package/lib/AWS/CostExplorer/GetSavingsPlansUtilizationDetailsHttp.d.ts.map +1 -1
- package/lib/AWS/CostExplorer/GetSavingsPlansUtilizationHttp.d.ts +1 -1
- package/lib/AWS/CostExplorer/GetSavingsPlansUtilizationHttp.d.ts.map +1 -1
- package/lib/AWS/CostExplorer/GetTagsHttp.d.ts +1 -1
- package/lib/AWS/CostExplorer/GetTagsHttp.d.ts.map +1 -1
- package/lib/AWS/CostExplorer/GetUsageForecastHttp.d.ts +1 -1
- package/lib/AWS/CostExplorer/GetUsageForecastHttp.d.ts.map +1 -1
- package/lib/AWS/CostExplorer/ListCommitmentPurchaseAnalysesHttp.d.ts +1 -1
- package/lib/AWS/CostExplorer/ListCommitmentPurchaseAnalysesHttp.d.ts.map +1 -1
- package/lib/AWS/CostExplorer/ListCostAllocationTagBackfillHistoryHttp.d.ts +1 -1
- package/lib/AWS/CostExplorer/ListCostAllocationTagBackfillHistoryHttp.d.ts.map +1 -1
- package/lib/AWS/CostExplorer/ListCostAllocationTagsHttp.d.ts +1 -1
- package/lib/AWS/CostExplorer/ListCostAllocationTagsHttp.d.ts.map +1 -1
- package/lib/AWS/CostExplorer/ListCostCategoryResourceAssociationsHttp.d.ts +1 -1
- package/lib/AWS/CostExplorer/ListCostCategoryResourceAssociationsHttp.d.ts.map +1 -1
- package/lib/AWS/CostExplorer/ListSavingsPlansPurchaseRecommendationGenerationHttp.d.ts +1 -1
- package/lib/AWS/CostExplorer/ListSavingsPlansPurchaseRecommendationGenerationHttp.d.ts.map +1 -1
- package/lib/AWS/CostExplorer/ProvideAnomalyFeedbackHttp.d.ts +1 -1
- package/lib/AWS/CostExplorer/ProvideAnomalyFeedbackHttp.d.ts.map +1 -1
- package/lib/AWS/CostExplorer/StartCommitmentPurchaseAnalysisHttp.d.ts +1 -1
- package/lib/AWS/CostExplorer/StartCommitmentPurchaseAnalysisHttp.d.ts.map +1 -1
- package/lib/AWS/CostExplorer/StartCostAllocationTagBackfillHttp.d.ts +1 -1
- package/lib/AWS/CostExplorer/StartCostAllocationTagBackfillHttp.d.ts.map +1 -1
- package/lib/AWS/CostExplorer/StartSavingsPlansPurchaseRecommendationGenerationHttp.d.ts +1 -1
- package/lib/AWS/CostExplorer/StartSavingsPlansPurchaseRecommendationGenerationHttp.d.ts.map +1 -1
- package/lib/AWS/CostExplorer/UpdateCostAllocationTagsStatusHttp.d.ts +1 -1
- package/lib/AWS/CostExplorer/UpdateCostAllocationTagsStatusHttp.d.ts.map +1 -1
- package/lib/AWS/CostExplorer/common.d.ts +5 -3
- package/lib/AWS/CostExplorer/common.d.ts.map +1 -1
- package/lib/AWS/CostExplorer/common.js +16 -1
- package/lib/AWS/CostExplorer/common.js.map +1 -1
- package/lib/AWS/DataSync/Task.d.ts.map +1 -1
- package/lib/AWS/DataSync/Task.js +16 -2
- package/lib/AWS/DataSync/Task.js.map +1 -1
- package/lib/AWS/EC2/Vpc.d.ts.map +1 -1
- package/lib/AWS/EC2/Vpc.js +15 -2
- package/lib/AWS/EC2/Vpc.js.map +1 -1
- package/lib/AWS/EC2/hosted.d.ts.map +1 -1
- package/lib/AWS/EC2/hosted.js +10 -2
- package/lib/AWS/EC2/hosted.js.map +1 -1
- package/lib/AWS/ECR/Image.d.ts +132 -13
- package/lib/AWS/ECR/Image.d.ts.map +1 -1
- package/lib/AWS/ECR/ImageSource.d.ts.map +1 -1
- package/lib/AWS/ECR/ImageSource.js +10 -2
- package/lib/AWS/ECR/ImageSource.js.map +1 -1
- package/lib/AWS/ECR/Repository.d.ts +2 -0
- package/lib/AWS/ECR/Repository.d.ts.map +1 -1
- package/lib/AWS/ECR/Repository.js +28 -3
- package/lib/AWS/ECR/Repository.js.map +1 -1
- package/lib/AWS/ECS/Service.d.ts +27 -1
- package/lib/AWS/ECS/Service.d.ts.map +1 -1
- package/lib/AWS/ECS/Service.js +184 -22
- package/lib/AWS/ECS/Service.js.map +1 -1
- package/lib/AWS/ECS/Task.d.ts +32 -8
- package/lib/AWS/ECS/Task.d.ts.map +1 -1
- package/lib/AWS/ECS/Task.js +62 -29
- package/lib/AWS/ECS/Task.js.map +1 -1
- package/lib/AWS/EKS/Deployment.d.ts.map +1 -1
- package/lib/AWS/EKS/Deployment.js +10 -2
- package/lib/AWS/EKS/Deployment.js.map +1 -1
- package/lib/AWS/EKS/Job.d.ts.map +1 -1
- package/lib/AWS/EKS/Job.js +10 -2
- package/lib/AWS/EKS/Job.js.map +1 -1
- package/lib/AWS/Lambda/Function.d.ts +15 -0
- package/lib/AWS/Lambda/Function.d.ts.map +1 -1
- package/lib/AWS/Lambda/Function.js +67 -29
- package/lib/AWS/Lambda/Function.js.map +1 -1
- package/lib/AWS/Lambda/HttpServer.d.ts.map +1 -1
- package/lib/AWS/Lambda/HttpServer.js +7 -1
- package/lib/AWS/Lambda/HttpServer.js.map +1 -1
- package/lib/AWS/Lambda/LayerVersion.d.ts +176 -0
- package/lib/AWS/Lambda/LayerVersion.d.ts.map +1 -0
- package/lib/AWS/Lambda/LayerVersion.js +288 -0
- package/lib/AWS/Lambda/LayerVersion.js.map +1 -0
- package/lib/AWS/Lambda/MicrovmBundle.d.ts.map +1 -1
- package/lib/AWS/Lambda/MicrovmBundle.js +10 -2
- package/lib/AWS/Lambda/MicrovmBundle.js.map +1 -1
- package/lib/AWS/Lambda/index.d.ts +1 -0
- package/lib/AWS/Lambda/index.d.ts.map +1 -1
- package/lib/AWS/Lambda/index.js +1 -0
- package/lib/AWS/Lambda/index.js.map +1 -1
- package/lib/AWS/Providers.d.ts.map +1 -1
- package/lib/AWS/Providers.js +6 -1
- package/lib/AWS/Providers.js.map +1 -1
- package/lib/AWS/SES/ActiveReceiptRuleSet.d.ts +40 -0
- package/lib/AWS/SES/ActiveReceiptRuleSet.d.ts.map +1 -0
- package/lib/AWS/SES/ActiveReceiptRuleSet.js +58 -0
- package/lib/AWS/SES/ActiveReceiptRuleSet.js.map +1 -0
- package/lib/AWS/SES/ReceiptFilter.d.ts +67 -0
- package/lib/AWS/SES/ReceiptFilter.d.ts.map +1 -0
- package/lib/AWS/SES/ReceiptFilter.js +92 -0
- package/lib/AWS/SES/ReceiptFilter.js.map +1 -0
- package/lib/AWS/SES/ReceiptRule.d.ts +133 -0
- package/lib/AWS/SES/ReceiptRule.d.ts.map +1 -0
- package/lib/AWS/SES/ReceiptRule.js +178 -0
- package/lib/AWS/SES/ReceiptRule.js.map +1 -0
- package/lib/AWS/SES/ReceiptRuleSet.d.ts +55 -0
- package/lib/AWS/SES/ReceiptRuleSet.d.ts.map +1 -0
- package/lib/AWS/SES/ReceiptRuleSet.js +112 -0
- package/lib/AWS/SES/ReceiptRuleSet.js.map +1 -0
- package/lib/AWS/SES/SendBounce.d.ts +37 -0
- package/lib/AWS/SES/SendBounce.d.ts.map +1 -0
- package/lib/AWS/SES/SendBounce.js +3 -0
- package/lib/AWS/SES/SendBounce.js.map +1 -0
- package/lib/AWS/SES/SendBounceHttp.d.ts +4 -0
- package/lib/AWS/SES/SendBounceHttp.d.ts.map +1 -0
- package/lib/AWS/SES/SendBounceHttp.js +10 -0
- package/lib/AWS/SES/SendBounceHttp.js.map +1 -0
- package/lib/AWS/SES/index.d.ts +6 -0
- package/lib/AWS/SES/index.d.ts.map +1 -1
- package/lib/AWS/SES/index.js +6 -0
- package/lib/AWS/SES/index.js.map +1 -1
- package/lib/AWS/StateStore/State.d.ts +11 -0
- package/lib/AWS/StateStore/State.d.ts.map +1 -1
- package/lib/AWS/StateStore/State.js +117 -38
- package/lib/AWS/StateStore/State.js.map +1 -1
- package/lib/Apply.d.ts +37 -1
- package/lib/Apply.d.ts.map +1 -1
- package/lib/Apply.js +270 -194
- package/lib/Apply.js.map +1 -1
- package/lib/Axiom/Telemetry.d.ts +66 -0
- package/lib/Axiom/Telemetry.d.ts.map +1 -0
- package/lib/Axiom/Telemetry.js +69 -0
- package/lib/Axiom/Telemetry.js.map +1 -0
- package/lib/Axiom/index.d.ts +1 -0
- package/lib/Axiom/index.d.ts.map +1 -1
- package/lib/Axiom/index.js +1 -0
- package/lib/Axiom/index.js.map +1 -1
- package/lib/Bundle/Bundle.d.ts +1 -1
- package/lib/Bundle/Bundle.d.ts.map +1 -1
- package/lib/Bundle/Bundle.js +14 -6
- package/lib/Bundle/Bundle.js.map +1 -1
- package/lib/Bundle/BundleAnalyzerPlugin.d.ts +1 -1
- package/lib/Bundle/BundleAnalyzerPlugin.d.ts.map +1 -1
- package/lib/Bundle/BundleAnalyzerPlugin.js +10 -5
- package/lib/Bundle/BundleAnalyzerPlugin.js.map +1 -1
- package/lib/Bundle/PurePlugin.d.ts +1 -1
- package/lib/Bundle/PurePlugin.d.ts.map +1 -1
- package/lib/Bundle/PurePlugin.js +15 -7
- package/lib/Bundle/PurePlugin.js.map +1 -1
- package/lib/Cli/checkVersion.d.ts +11 -5
- package/lib/Cli/checkVersion.d.ts.map +1 -1
- package/lib/Cli/checkVersion.js +73 -12
- package/lib/Cli/checkVersion.js.map +1 -1
- package/lib/Cli/commands/_shared.d.ts.map +1 -1
- package/lib/Cli/commands/_shared.js +2 -1
- package/lib/Cli/commands/_shared.js.map +1 -1
- package/lib/Cli/commands/cloudflare.d.ts +1 -1
- package/lib/Cli/commands/deploy.d.ts.map +1 -1
- package/lib/Cli/commands/deploy.js +3 -11
- package/lib/Cli/commands/deploy.js.map +1 -1
- package/lib/Cli/commands/logs.d.ts.map +1 -1
- package/lib/Cli/commands/logs.js +3 -1
- package/lib/Cli/commands/logs.js.map +1 -1
- package/lib/Cli/commands/tail.d.ts.map +1 -1
- package/lib/Cli/commands/tail.js +3 -1
- package/lib/Cli/commands/tail.js.map +1 -1
- package/lib/Cli/main.d.ts.map +1 -1
- package/lib/Cli/main.js +5 -4
- package/lib/Cli/main.js.map +1 -1
- package/lib/Cloudflare/AI/Search.d.ts +4 -15
- package/lib/Cloudflare/AI/Search.d.ts.map +1 -1
- package/lib/Cloudflare/AI/Search.js +4 -6
- package/lib/Cloudflare/AI/Search.js.map +1 -1
- package/lib/Cloudflare/AI/SearchInstance.d.ts +7 -26
- package/lib/Cloudflare/AI/SearchInstance.d.ts.map +1 -1
- package/lib/Cloudflare/AI/SearchInstance.js +7 -26
- package/lib/Cloudflare/AI/SearchInstance.js.map +1 -1
- package/lib/Cloudflare/ApiToken/AccountApiToken.d.ts +3 -0
- package/lib/Cloudflare/ApiToken/AccountApiToken.d.ts.map +1 -1
- package/lib/Cloudflare/ApiToken/AccountApiToken.js +3 -0
- package/lib/Cloudflare/ApiToken/AccountApiToken.js.map +1 -1
- package/lib/Cloudflare/Auth/AuthProvider.d.ts +2 -1
- package/lib/Cloudflare/Auth/AuthProvider.d.ts.map +1 -1
- package/lib/Cloudflare/Auth/AuthProvider.js +8 -3
- package/lib/Cloudflare/Auth/AuthProvider.js.map +1 -1
- package/lib/Cloudflare/Auth/OAuthClient.d.ts +5 -0
- package/lib/Cloudflare/Auth/OAuthClient.d.ts.map +1 -1
- package/lib/Cloudflare/Auth/OAuthClient.js +50 -4
- package/lib/Cloudflare/Auth/OAuthClient.js.map +1 -1
- package/lib/Cloudflare/Containers/Container.d.ts +81 -3
- package/lib/Cloudflare/Containers/Container.d.ts.map +1 -1
- package/lib/Cloudflare/Containers/Container.js +70 -0
- package/lib/Cloudflare/Containers/Container.js.map +1 -1
- package/lib/Cloudflare/Containers/ContainerApplication.d.ts +22 -1
- package/lib/Cloudflare/Containers/ContainerApplication.d.ts.map +1 -1
- package/lib/Cloudflare/Containers/ContainerApplication.js +4 -1
- package/lib/Cloudflare/Containers/ContainerApplication.js.map +1 -1
- package/lib/Cloudflare/Containers/ContainerBundle.d.ts.map +1 -1
- package/lib/Cloudflare/Containers/ContainerBundle.js +10 -2
- package/lib/Cloudflare/Containers/ContainerBundle.js.map +1 -1
- package/lib/Cloudflare/Containers/ContainerProvider.d.ts.map +1 -1
- package/lib/Cloudflare/Containers/ContainerProvider.js +43 -0
- package/lib/Cloudflare/Containers/ContainerProvider.js.map +1 -1
- package/lib/Cloudflare/Credentials.d.ts +21 -1
- package/lib/Cloudflare/Credentials.d.ts.map +1 -1
- package/lib/Cloudflare/Credentials.js +52 -9
- package/lib/Cloudflare/Credentials.js.map +1 -1
- package/lib/Cloudflare/D1/ApplyMigrations.d.ts +30 -5
- package/lib/Cloudflare/D1/ApplyMigrations.d.ts.map +1 -1
- package/lib/Cloudflare/D1/ApplyMigrations.js +36 -26
- package/lib/Cloudflare/D1/ApplyMigrations.js.map +1 -1
- package/lib/Cloudflare/D1/Database.d.ts +18 -1
- package/lib/Cloudflare/D1/Database.d.ts.map +1 -1
- package/lib/Cloudflare/D1/Database.js +134 -2
- package/lib/Cloudflare/D1/Database.js.map +1 -1
- package/lib/Cloudflare/D1/LocalD1Gateway.d.ts +39 -0
- package/lib/Cloudflare/D1/LocalD1Gateway.d.ts.map +1 -0
- package/lib/Cloudflare/D1/LocalD1Gateway.js +100 -0
- package/lib/Cloudflare/D1/LocalD1Gateway.js.map +1 -0
- package/lib/Cloudflare/D1/QueryDatabaseHttpClient.d.ts +31 -0
- package/lib/Cloudflare/D1/QueryDatabaseHttpClient.d.ts.map +1 -1
- package/lib/Cloudflare/D1/QueryDatabaseHttpClient.js +13 -6
- package/lib/Cloudflare/D1/QueryDatabaseHttpClient.js.map +1 -1
- package/lib/Cloudflare/D1/QueryDatabaseLocal.d.ts.map +1 -1
- package/lib/Cloudflare/D1/QueryDatabaseLocal.js +21 -2
- package/lib/Cloudflare/D1/QueryDatabaseLocal.js.map +1 -1
- package/lib/Cloudflare/DNS/Record.d.ts +3 -1
- package/lib/Cloudflare/DNS/Record.d.ts.map +1 -1
- package/lib/Cloudflare/DNS/Record.js +23 -4
- package/lib/Cloudflare/DNS/Record.js.map +1 -1
- package/lib/Cloudflare/Email/Address.d.ts.map +1 -1
- package/lib/Cloudflare/Email/Address.js +15 -1
- package/lib/Cloudflare/Email/Address.js.map +1 -1
- package/lib/Cloudflare/Email/SendBinding.d.ts.map +1 -1
- package/lib/Cloudflare/Email/SendBinding.js +1 -0
- package/lib/Cloudflare/Email/SendBinding.js.map +1 -1
- package/lib/Cloudflare/Email/SendEmail.d.ts +12 -0
- package/lib/Cloudflare/Email/SendEmail.d.ts.map +1 -1
- package/lib/Cloudflare/Email/SendEmail.js +1 -0
- package/lib/Cloudflare/Email/SendEmail.js.map +1 -1
- package/lib/Cloudflare/Flagship/App.d.ts.map +1 -1
- package/lib/Cloudflare/Flagship/App.js +8 -3
- package/lib/Cloudflare/Flagship/App.js.map +1 -1
- package/lib/Cloudflare/KV/LocalKVGateway.d.ts +15 -0
- package/lib/Cloudflare/KV/LocalKVGateway.d.ts.map +1 -0
- package/lib/Cloudflare/KV/LocalKVGateway.js +64 -0
- package/lib/Cloudflare/KV/LocalKVGateway.js.map +1 -0
- package/lib/Cloudflare/KV/Namespace.d.ts +9 -1
- package/lib/Cloudflare/KV/Namespace.d.ts.map +1 -1
- package/lib/Cloudflare/KV/Namespace.js +45 -1
- package/lib/Cloudflare/KV/Namespace.js.map +1 -1
- package/lib/Cloudflare/KV/NamespaceLocal.d.ts +16 -5
- package/lib/Cloudflare/KV/NamespaceLocal.d.ts.map +1 -1
- package/lib/Cloudflare/KV/NamespaceLocal.js +21 -4
- package/lib/Cloudflare/KV/NamespaceLocal.js.map +1 -1
- package/lib/Cloudflare/KV/ReadNamespaceLocal.d.ts.map +1 -1
- package/lib/Cloudflare/KV/ReadNamespaceLocal.js +5 -1
- package/lib/Cloudflare/KV/ReadNamespaceLocal.js.map +1 -1
- package/lib/Cloudflare/KV/ReadWriteNamespaceLocal.d.ts.map +1 -1
- package/lib/Cloudflare/KV/ReadWriteNamespaceLocal.js +5 -1
- package/lib/Cloudflare/KV/ReadWriteNamespaceLocal.js.map +1 -1
- package/lib/Cloudflare/KV/WriteNamespaceLocal.d.ts.map +1 -1
- package/lib/Cloudflare/KV/WriteNamespaceLocal.js +5 -1
- package/lib/Cloudflare/KV/WriteNamespaceLocal.js.map +1 -1
- package/lib/Cloudflare/LocalGateway.d.ts +25 -0
- package/lib/Cloudflare/LocalGateway.d.ts.map +1 -0
- package/lib/Cloudflare/LocalGateway.js +70 -0
- package/lib/Cloudflare/LocalGateway.js.map +1 -0
- package/lib/Cloudflare/LocalRuntime.d.ts +14 -1
- package/lib/Cloudflare/LocalRuntime.d.ts.map +1 -1
- package/lib/Cloudflare/LocalRuntime.js +16 -1
- package/lib/Cloudflare/LocalRuntime.js.map +1 -1
- package/lib/Cloudflare/Logs.d.ts.map +1 -1
- package/lib/Cloudflare/Logs.js +18 -6
- package/lib/Cloudflare/Logs.js.map +1 -1
- package/lib/Cloudflare/Providers.d.ts +1 -1
- package/lib/Cloudflare/Providers.d.ts.map +1 -1
- package/lib/Cloudflare/Providers.js +6 -2
- package/lib/Cloudflare/Providers.js.map +1 -1
- package/lib/Cloudflare/Queues/Consumer.d.ts +16 -3
- package/lib/Cloudflare/Queues/Consumer.d.ts.map +1 -1
- package/lib/Cloudflare/Queues/Consumer.js +51 -3
- package/lib/Cloudflare/Queues/Consumer.js.map +1 -1
- package/lib/Cloudflare/Queues/Queue.d.ts +4 -3
- package/lib/Cloudflare/Queues/Queue.d.ts.map +1 -1
- package/lib/Cloudflare/Queues/Queue.js +7 -17
- package/lib/Cloudflare/Queues/Queue.js.map +1 -1
- package/lib/Cloudflare/Queues/QueueBinding.d.ts.map +1 -1
- package/lib/Cloudflare/Queues/QueueBinding.js +19 -0
- package/lib/Cloudflare/Queues/QueueBinding.js.map +1 -1
- package/lib/Cloudflare/Queues/QueueShim.d.ts +55 -0
- package/lib/Cloudflare/Queues/QueueShim.d.ts.map +1 -0
- package/lib/Cloudflare/Queues/QueueShim.js +177 -0
- package/lib/Cloudflare/Queues/QueueShim.js.map +1 -0
- package/lib/Cloudflare/R2/Bucket.d.ts +14 -1
- package/lib/Cloudflare/R2/Bucket.d.ts.map +1 -1
- package/lib/Cloudflare/R2/Bucket.js +53 -1
- package/lib/Cloudflare/R2/Bucket.js.map +1 -1
- package/lib/Cloudflare/R2/BucketBinding.d.ts +10 -0
- package/lib/Cloudflare/R2/BucketBinding.d.ts.map +1 -1
- package/lib/Cloudflare/R2/BucketBinding.js +14 -3
- package/lib/Cloudflare/R2/BucketBinding.js.map +1 -1
- package/lib/Cloudflare/R2/BucketLocal.d.ts +13 -6
- package/lib/Cloudflare/R2/BucketLocal.d.ts.map +1 -1
- package/lib/Cloudflare/R2/BucketLocal.js +19 -6
- package/lib/Cloudflare/R2/BucketLocal.js.map +1 -1
- package/lib/Cloudflare/R2/LocalR2Gateway.d.ts +15 -0
- package/lib/Cloudflare/R2/LocalR2Gateway.d.ts.map +1 -0
- package/lib/Cloudflare/R2/LocalR2Gateway.js +65 -0
- package/lib/Cloudflare/R2/LocalR2Gateway.js.map +1 -0
- package/lib/Cloudflare/R2/ReadBucketLocal.d.ts.map +1 -1
- package/lib/Cloudflare/R2/ReadBucketLocal.js +5 -1
- package/lib/Cloudflare/R2/ReadBucketLocal.js.map +1 -1
- package/lib/Cloudflare/R2/ReadWriteBucketLocal.d.ts.map +1 -1
- package/lib/Cloudflare/R2/ReadWriteBucketLocal.js +5 -1
- package/lib/Cloudflare/R2/ReadWriteBucketLocal.js.map +1 -1
- package/lib/Cloudflare/R2/WriteBucketLocal.d.ts.map +1 -1
- package/lib/Cloudflare/R2/WriteBucketLocal.js +5 -1
- package/lib/Cloudflare/R2/WriteBucketLocal.js.map +1 -1
- package/lib/Cloudflare/Ruleset/AccountEntrypoint.d.ts.map +1 -1
- package/lib/Cloudflare/Ruleset/AccountEntrypoint.js +5 -1
- package/lib/Cloudflare/Ruleset/AccountEntrypoint.js.map +1 -1
- package/lib/Cloudflare/Ruleset/Ruleset.d.ts.map +1 -1
- package/lib/Cloudflare/Ruleset/Ruleset.js +19 -6
- package/lib/Cloudflare/Ruleset/Ruleset.js.map +1 -1
- package/lib/Cloudflare/Rum/Site.js +10 -2
- package/lib/Cloudflare/Rum/Site.js.map +1 -1
- package/lib/Cloudflare/StateStore/Api.d.ts +31 -2
- package/lib/Cloudflare/StateStore/Api.js +1 -1
- package/lib/Cloudflare/StateStore/Api.js.map +1 -1
- package/lib/Cloudflare/StateStore/State.d.ts +1 -1
- package/lib/Cloudflare/StateStore/State.d.ts.map +1 -1
- package/lib/Cloudflare/Tunnel/Tunnel.d.ts.map +1 -1
- package/lib/Cloudflare/Tunnel/Tunnel.js +20 -1
- package/lib/Cloudflare/Tunnel/Tunnel.js.map +1 -1
- package/lib/Cloudflare/Website/StaticSite.d.ts +34 -7
- package/lib/Cloudflare/Website/StaticSite.d.ts.map +1 -1
- package/lib/Cloudflare/Website/StaticSite.js +108 -24
- package/lib/Cloudflare/Website/StaticSite.js.map +1 -1
- package/lib/Cloudflare/Website/Vite.d.ts +33 -19
- package/lib/Cloudflare/Website/Vite.d.ts.map +1 -1
- package/lib/Cloudflare/Website/Vite.js +33 -19
- package/lib/Cloudflare/Website/Vite.js.map +1 -1
- package/lib/Cloudflare/Workers/Compatibility.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/Compatibility.js +26 -7
- package/lib/Cloudflare/Workers/Compatibility.js.map +1 -1
- package/lib/Cloudflare/Workers/DurableObject.d.ts +22 -0
- package/lib/Cloudflare/Workers/DurableObject.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/DurableObject.js +22 -0
- package/lib/Cloudflare/Workers/DurableObject.js.map +1 -1
- package/lib/Cloudflare/Workers/DurableObjectBridge.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/DurableObjectBridge.js +20 -5
- package/lib/Cloudflare/Workers/DurableObjectBridge.js.map +1 -1
- package/lib/Cloudflare/Workers/InferEnv.d.ts +4 -2
- package/lib/Cloudflare/Workers/InferEnv.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/LocalWorkerProvider.d.ts +7 -3
- package/lib/Cloudflare/Workers/LocalWorkerProvider.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/LocalWorkerProvider.js +483 -298
- package/lib/Cloudflare/Workers/LocalWorkerProvider.js.map +1 -1
- package/lib/Cloudflare/Workers/Worker.d.ts +612 -13
- package/lib/Cloudflare/Workers/Worker.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/Worker.js +222 -1
- package/lib/Cloudflare/Workers/Worker.js.map +1 -1
- package/lib/Cloudflare/Workers/WorkerAsyncBindings.d.ts +13 -1
- package/lib/Cloudflare/Workers/WorkerAsyncBindings.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/WorkerAsyncBindings.js +171 -21
- package/lib/Cloudflare/Workers/WorkerAsyncBindings.js.map +1 -1
- package/lib/Cloudflare/Workers/WorkerBinding.d.ts +130 -28
- package/lib/Cloudflare/Workers/WorkerBinding.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/WorkerBinding.js.map +1 -1
- package/lib/Cloudflare/Workers/WorkerBridge.d.ts +6 -2
- package/lib/Cloudflare/Workers/WorkerBridge.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/WorkerBridge.js +18 -2
- package/lib/Cloudflare/Workers/WorkerBridge.js.map +1 -1
- package/lib/Cloudflare/Workers/WorkerBundle.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/WorkerBundle.js +4 -1
- package/lib/Cloudflare/Workers/WorkerBundle.js.map +1 -1
- package/lib/Cloudflare/Workers/WorkerProvider.d.ts +186 -11
- package/lib/Cloudflare/Workers/WorkerProvider.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/WorkerProvider.js +1765 -236
- package/lib/Cloudflare/Workers/WorkerProvider.js.map +1 -1
- package/lib/Cloudflare/Workflows/WorkflowBridge.d.ts +7 -1
- package/lib/Cloudflare/Workflows/WorkflowBridge.d.ts.map +1 -1
- package/lib/Cloudflare/Workflows/WorkflowBridge.js +14 -4
- package/lib/Cloudflare/Workflows/WorkflowBridge.js.map +1 -1
- package/lib/Command/Build.d.ts +2 -2
- package/lib/Command/Build.d.ts.map +1 -1
- package/lib/Command/Build.js +4 -7
- package/lib/Command/Build.js.map +1 -1
- package/lib/Command/Command.d.ts +34 -4
- package/lib/Command/Command.d.ts.map +1 -1
- package/lib/Command/Command.js +125 -30
- package/lib/Command/Command.js.map +1 -1
- package/lib/Command/Dev.d.ts +2 -2
- package/lib/Command/Dev.d.ts.map +1 -1
- package/lib/Command/Dev.js +51 -73
- package/lib/Command/Dev.js.map +1 -1
- package/lib/Command/Exec.d.ts +11 -2
- package/lib/Command/Exec.d.ts.map +1 -1
- package/lib/Command/Exec.js +9 -0
- package/lib/Command/Exec.js.map +1 -1
- package/lib/Command/Memo.d.ts +9 -1
- package/lib/Command/Memo.d.ts.map +1 -1
- package/lib/Command/Memo.js +14 -2
- package/lib/Command/Memo.js.map +1 -1
- package/lib/Command/Providers.d.ts +1 -1
- package/lib/Command/Providers.d.ts.map +1 -1
- package/lib/Command/Redaction.d.ts +10 -0
- package/lib/Command/Redaction.d.ts.map +1 -0
- package/lib/Command/Redaction.js +119 -0
- package/lib/Command/Redaction.js.map +1 -0
- package/lib/Deploy.d.ts +2 -1
- package/lib/Deploy.d.ts.map +1 -1
- package/lib/Destroy.d.ts +2 -1
- package/lib/Destroy.d.ts.map +1 -1
- package/lib/Destroy.js +1 -9
- package/lib/Destroy.js.map +1 -1
- package/lib/Docker/Container.d.ts +15 -1
- package/lib/Docker/Container.d.ts.map +1 -1
- package/lib/Docker/Container.js +34 -11
- package/lib/Docker/Container.js.map +1 -1
- package/lib/Docker/Context.d.ts +84 -0
- package/lib/Docker/Context.d.ts.map +1 -0
- package/lib/Docker/Context.js +145 -0
- package/lib/Docker/Context.js.map +1 -0
- package/lib/Docker/Docker.d.ts +190 -14
- package/lib/Docker/Docker.d.ts.map +1 -1
- package/lib/Docker/Docker.js +149 -28
- package/lib/Docker/Docker.js.map +1 -1
- package/lib/Docker/Image.d.ts +12 -0
- package/lib/Docker/Image.d.ts.map +1 -1
- package/lib/Docker/Image.js +24 -7
- package/lib/Docker/Image.js.map +1 -1
- package/lib/Docker/Network.d.ts +7 -0
- package/lib/Docker/Network.d.ts.map +1 -1
- package/lib/Docker/Network.js +16 -7
- package/lib/Docker/Network.js.map +1 -1
- package/lib/Docker/Providers.d.ts +1 -1
- package/lib/Docker/Providers.d.ts.map +1 -1
- package/lib/Docker/Providers.js +13 -1
- package/lib/Docker/Providers.js.map +1 -1
- package/lib/Docker/RemoteImage.d.ts +12 -0
- package/lib/Docker/RemoteImage.d.ts.map +1 -1
- package/lib/Docker/RemoteImage.js +22 -8
- package/lib/Docker/RemoteImage.js.map +1 -1
- package/lib/Docker/Service.d.ts +445 -0
- package/lib/Docker/Service.d.ts.map +1 -0
- package/lib/Docker/Service.js +578 -0
- package/lib/Docker/Service.js.map +1 -0
- package/lib/Docker/ServiceImage.d.ts +78 -0
- package/lib/Docker/ServiceImage.d.ts.map +1 -0
- package/lib/Docker/ServiceImage.js +232 -0
- package/lib/Docker/ServiceImage.js.map +1 -0
- package/lib/Docker/Swarm.d.ts +117 -0
- package/lib/Docker/Swarm.d.ts.map +1 -0
- package/lib/Docker/Swarm.js +149 -0
- package/lib/Docker/Swarm.js.map +1 -0
- package/lib/Docker/Volume.d.ts +11 -0
- package/lib/Docker/Volume.d.ts.map +1 -1
- package/lib/Docker/Volume.js +21 -6
- package/lib/Docker/Volume.js.map +1 -1
- package/lib/Docker/index.d.ts +3 -0
- package/lib/Docker/index.d.ts.map +1 -1
- package/lib/Docker/index.js +3 -0
- package/lib/Docker/index.js.map +1 -1
- package/lib/Http.d.ts +8 -1
- package/lib/Http.d.ts.map +1 -1
- package/lib/Http.js +7 -2
- package/lib/Http.js.map +1 -1
- package/lib/Local/LocalProvider.d.ts +182 -0
- package/lib/Local/LocalProvider.d.ts.map +1 -0
- package/lib/Local/LocalProvider.js +226 -0
- package/lib/Local/LocalProvider.js.map +1 -0
- package/lib/Local/ProviderLayer.d.ts +43 -2
- package/lib/Local/ProviderLayer.d.ts.map +1 -1
- package/lib/Local/ProviderLayer.js +80 -2
- package/lib/Local/ProviderLayer.js.map +1 -1
- package/lib/Local/RpcProvider.d.ts +7 -0
- package/lib/Local/RpcProvider.d.ts.map +1 -1
- package/lib/Local/RpcProvider.js +8 -3
- package/lib/Local/RpcProvider.js.map +1 -1
- package/lib/Local/RpcProviderProxy.d.ts.map +1 -1
- package/lib/Local/RpcProviderProxy.js +3 -0
- package/lib/Local/RpcProviderProxy.js.map +1 -1
- package/lib/Local/RpcServer.d.ts +8 -1
- package/lib/Local/RpcServer.d.ts.map +1 -1
- package/lib/Local/RpcServer.js +4 -1
- package/lib/Local/RpcServer.js.map +1 -1
- package/lib/Plan.d.ts +34 -0
- package/lib/Plan.d.ts.map +1 -1
- package/lib/Plan.js +127 -40
- package/lib/Plan.js.map +1 -1
- package/lib/Planetscale/AuthProvider.d.ts.map +1 -1
- package/lib/Planetscale/AuthProvider.js +6 -2
- package/lib/Planetscale/AuthProvider.js.map +1 -1
- package/lib/Planetscale/Branch.d.ts.map +1 -1
- package/lib/Planetscale/Branch.js +36 -6
- package/lib/Planetscale/Branch.js.map +1 -1
- package/lib/Planetscale/MySQL/MySQLDatabase.d.ts.map +1 -1
- package/lib/Planetscale/MySQL/MySQLDatabase.js +31 -6
- package/lib/Planetscale/MySQL/MySQLDatabase.js.map +1 -1
- package/lib/Planetscale/OAuthClient.d.ts +7 -1
- package/lib/Planetscale/OAuthClient.d.ts.map +1 -1
- package/lib/Planetscale/OAuthClient.js +51 -4
- package/lib/Planetscale/OAuthClient.js.map +1 -1
- package/lib/Planetscale/Postgres/PostgresDatabase.d.ts.map +1 -1
- package/lib/Planetscale/Postgres/PostgresDatabase.js +30 -5
- package/lib/Planetscale/Postgres/PostgresDatabase.js.map +1 -1
- package/lib/Platform.d.ts.map +1 -1
- package/lib/Platform.js +25 -14
- package/lib/Platform.js.map +1 -1
- package/lib/Prisma/App.d.ts +93 -0
- package/lib/Prisma/App.d.ts.map +1 -0
- package/lib/Prisma/App.js +251 -0
- package/lib/Prisma/App.js.map +1 -0
- package/lib/Prisma/AuthProvider.d.ts +28 -0
- package/lib/Prisma/AuthProvider.d.ts.map +1 -0
- package/lib/Prisma/AuthProvider.js +131 -0
- package/lib/Prisma/AuthProvider.js.map +1 -0
- package/lib/Prisma/Branch.d.ts +96 -0
- package/lib/Prisma/Branch.d.ts.map +1 -0
- package/lib/Prisma/Branch.js +236 -0
- package/lib/Prisma/Branch.js.map +1 -0
- package/lib/Prisma/Client.d.ts +178 -0
- package/lib/Prisma/Client.d.ts.map +1 -0
- package/lib/Prisma/Client.js +562 -0
- package/lib/Prisma/Client.js.map +1 -0
- package/lib/Prisma/Compute.d.ts +585 -0
- package/lib/Prisma/Compute.d.ts.map +1 -0
- package/lib/Prisma/Compute.js +1545 -0
- package/lib/Prisma/Compute.js.map +1 -0
- package/lib/Prisma/ComputeArchive.d.ts +63 -0
- package/lib/Prisma/ComputeArchive.d.ts.map +1 -0
- package/lib/Prisma/ComputeArchive.js +227 -0
- package/lib/Prisma/ComputeArchive.js.map +1 -0
- package/lib/Prisma/ComputeBuild.d.ts +106 -0
- package/lib/Prisma/ComputeBuild.d.ts.map +1 -0
- package/lib/Prisma/ComputeBuild.js +987 -0
- package/lib/Prisma/ComputeBuild.js.map +1 -0
- package/lib/Prisma/ComputeLifecycle.d.ts +84 -0
- package/lib/Prisma/ComputeLifecycle.d.ts.map +1 -0
- package/lib/Prisma/ComputeLifecycle.js +186 -0
- package/lib/Prisma/ComputeLifecycle.js.map +1 -0
- package/lib/Prisma/Connect.d.ts +122 -0
- package/lib/Prisma/Connect.d.ts.map +1 -0
- package/lib/Prisma/Connect.js +187 -0
- package/lib/Prisma/Connect.js.map +1 -0
- package/lib/Prisma/Connection.d.ts +219 -0
- package/lib/Prisma/Connection.d.ts.map +1 -0
- package/lib/Prisma/Connection.js +360 -0
- package/lib/Prisma/Connection.js.map +1 -0
- package/lib/Prisma/CustomDomain.d.ts +91 -0
- package/lib/Prisma/CustomDomain.d.ts.map +1 -0
- package/lib/Prisma/CustomDomain.js +184 -0
- package/lib/Prisma/CustomDomain.js.map +1 -0
- package/lib/Prisma/Database.d.ts +222 -0
- package/lib/Prisma/Database.d.ts.map +1 -0
- package/lib/Prisma/Database.js +418 -0
- package/lib/Prisma/Database.js.map +1 -0
- package/lib/Prisma/Deployment.d.ts +141 -0
- package/lib/Prisma/Deployment.d.ts.map +1 -0
- package/lib/Prisma/Deployment.js +353 -0
- package/lib/Prisma/Deployment.js.map +1 -0
- package/lib/Prisma/EnvironmentVariable.d.ts +105 -0
- package/lib/Prisma/EnvironmentVariable.d.ts.map +1 -0
- package/lib/Prisma/EnvironmentVariable.js +229 -0
- package/lib/Prisma/EnvironmentVariable.js.map +1 -0
- package/lib/Prisma/Internal/AppIdentity.d.ts +10 -0
- package/lib/Prisma/Internal/AppIdentity.d.ts.map +1 -0
- package/lib/Prisma/Internal/AppIdentity.js +6 -0
- package/lib/Prisma/Internal/AppIdentity.js.map +1 -0
- package/lib/Prisma/Internal/AppPromotion.d.ts +19 -0
- package/lib/Prisma/Internal/AppPromotion.d.ts.map +1 -0
- package/lib/Prisma/Internal/AppPromotion.js +107 -0
- package/lib/Prisma/Internal/AppPromotion.js.map +1 -0
- package/lib/Prisma/Internal/ArchivePlatform.d.ts +42 -0
- package/lib/Prisma/Internal/ArchivePlatform.d.ts.map +1 -0
- package/lib/Prisma/Internal/ArchivePlatform.js +214 -0
- package/lib/Prisma/Internal/ArchivePlatform.js.map +1 -0
- package/lib/Prisma/Internal/ArtifactFile.d.ts +58 -0
- package/lib/Prisma/Internal/ArtifactFile.d.ts.map +1 -0
- package/lib/Prisma/Internal/ArtifactFile.js +134 -0
- package/lib/Prisma/Internal/ArtifactFile.js.map +1 -0
- package/lib/Prisma/Internal/ArtifactUpload.d.ts +5 -0
- package/lib/Prisma/Internal/ArtifactUpload.d.ts.map +1 -0
- package/lib/Prisma/Internal/ArtifactUpload.js +51 -0
- package/lib/Prisma/Internal/ArtifactUpload.js.map +1 -0
- package/lib/Prisma/Internal/BundlePaths.d.ts +4 -0
- package/lib/Prisma/Internal/BundlePaths.d.ts.map +1 -0
- package/lib/Prisma/Internal/BundlePaths.js +16 -0
- package/lib/Prisma/Internal/BundlePaths.js.map +1 -0
- package/lib/Prisma/Internal/CleanupFailure.d.ts +8 -0
- package/lib/Prisma/Internal/CleanupFailure.d.ts.map +1 -0
- package/lib/Prisma/Internal/CleanupFailure.js +8 -0
- package/lib/Prisma/Internal/CleanupFailure.js.map +1 -0
- package/lib/Prisma/Internal/DatabaseSecrets.d.ts +35 -0
- package/lib/Prisma/Internal/DatabaseSecrets.d.ts.map +1 -0
- package/lib/Prisma/Internal/DatabaseSecrets.js +85 -0
- package/lib/Prisma/Internal/DatabaseSecrets.js.map +1 -0
- package/lib/Prisma/Internal/DeploymentActions.d.ts +5 -0
- package/lib/Prisma/Internal/DeploymentActions.d.ts.map +1 -0
- package/lib/Prisma/Internal/DeploymentActions.js +16 -0
- package/lib/Prisma/Internal/DeploymentActions.js.map +1 -0
- package/lib/Prisma/Internal/DeploymentIdentity.d.ts +8 -0
- package/lib/Prisma/Internal/DeploymentIdentity.d.ts.map +1 -0
- package/lib/Prisma/Internal/DeploymentIdentity.js +12 -0
- package/lib/Prisma/Internal/DeploymentIdentity.js.map +1 -0
- package/lib/Prisma/Internal/DeploymentObserve.d.ts +3 -0
- package/lib/Prisma/Internal/DeploymentObserve.d.ts.map +1 -0
- package/lib/Prisma/Internal/DeploymentObserve.js +2 -0
- package/lib/Prisma/Internal/DeploymentObserve.js.map +1 -0
- package/lib/Prisma/Internal/EnvName.d.ts +7 -0
- package/lib/Prisma/Internal/EnvName.d.ts.map +1 -0
- package/lib/Prisma/Internal/EnvName.js +23 -0
- package/lib/Prisma/Internal/EnvName.js.map +1 -0
- package/lib/Prisma/Internal/HttpClient.d.ts +21 -0
- package/lib/Prisma/Internal/HttpClient.d.ts.map +1 -0
- package/lib/Prisma/Internal/HttpClient.js +23 -0
- package/lib/Prisma/Internal/HttpClient.js.map +1 -0
- package/lib/Prisma/Operations.d.ts +143 -0
- package/lib/Prisma/Operations.d.ts.map +1 -0
- package/lib/Prisma/Operations.js +75 -0
- package/lib/Prisma/Operations.js.map +1 -0
- package/lib/Prisma/Postgres.d.ts +25 -0
- package/lib/Prisma/Postgres.d.ts.map +1 -0
- package/lib/Prisma/Postgres.js +24 -0
- package/lib/Prisma/Postgres.js.map +1 -0
- package/lib/Prisma/PostgresOrigin.d.ts +31 -0
- package/lib/Prisma/PostgresOrigin.d.ts.map +1 -0
- package/lib/Prisma/PostgresOrigin.js +30 -0
- package/lib/Prisma/PostgresOrigin.js.map +1 -0
- package/lib/Prisma/PrismaDevDatabase.d.ts +25 -0
- package/lib/Prisma/PrismaDevDatabase.d.ts.map +1 -0
- package/lib/Prisma/PrismaDevDatabase.js +280 -0
- package/lib/Prisma/PrismaDevDatabase.js.map +1 -0
- package/lib/Prisma/PrismaEnvironment.d.ts +14 -0
- package/lib/Prisma/PrismaEnvironment.d.ts.map +1 -0
- package/lib/Prisma/PrismaEnvironment.js +50 -0
- package/lib/Prisma/PrismaEnvironment.js.map +1 -0
- package/lib/Prisma/PrismaLogs.d.ts +42 -0
- package/lib/Prisma/PrismaLogs.d.ts.map +1 -0
- package/lib/Prisma/PrismaLogs.js +318 -0
- package/lib/Prisma/PrismaLogs.js.map +1 -0
- package/lib/Prisma/Project.d.ts +123 -0
- package/lib/Prisma/Project.d.ts.map +1 -0
- package/lib/Prisma/Project.js +366 -0
- package/lib/Prisma/Project.js.map +1 -0
- package/lib/Prisma/Providers.d.ts +63 -0
- package/lib/Prisma/Providers.d.ts.map +1 -0
- package/lib/Prisma/Providers.js +306 -0
- package/lib/Prisma/Providers.js.map +1 -0
- package/lib/Prisma/Refs.d.ts +19 -0
- package/lib/Prisma/Refs.d.ts.map +1 -0
- package/lib/Prisma/Refs.js +25 -0
- package/lib/Prisma/Refs.js.map +1 -0
- package/lib/Prisma/SourceRepository.d.ts +127 -0
- package/lib/Prisma/SourceRepository.d.ts.map +1 -0
- package/lib/Prisma/SourceRepository.js +239 -0
- package/lib/Prisma/SourceRepository.js.map +1 -0
- package/lib/Prisma/Types.d.ts +539 -0
- package/lib/Prisma/Types.d.ts.map +1 -0
- package/lib/Prisma/Types.js +13 -0
- package/lib/Prisma/Types.js.map +1 -0
- package/lib/Prisma/index.d.ts +25 -0
- package/lib/Prisma/index.d.ts.map +1 -0
- package/lib/Prisma/index.js +24 -0
- package/lib/Prisma/index.js.map +1 -0
- package/lib/Provider.d.ts +57 -5
- package/lib/Provider.d.ts.map +1 -1
- package/lib/Provider.js +54 -30
- package/lib/Provider.js.map +1 -1
- package/lib/ProviderMode.d.ts +93 -0
- package/lib/ProviderMode.d.ts.map +1 -0
- package/lib/ProviderMode.js +77 -0
- package/lib/ProviderMode.js.map +1 -0
- package/lib/Resource.d.ts +8 -0
- package/lib/Resource.d.ts.map +1 -1
- package/lib/Resource.js +31 -0
- package/lib/Resource.js.map +1 -1
- package/lib/RuntimeContext.d.ts +7 -0
- package/lib/RuntimeContext.d.ts.map +1 -1
- package/lib/RuntimeContext.js.map +1 -1
- package/lib/Server/Process.d.ts +8 -0
- package/lib/Server/Process.d.ts.map +1 -1
- package/lib/Server/Process.js +29 -0
- package/lib/Server/Process.js.map +1 -1
- package/lib/State/InMemoryState.d.ts.map +1 -1
- package/lib/State/InMemoryState.js +2 -0
- package/lib/State/InMemoryState.js.map +1 -1
- package/lib/State/ResourceState.d.ts +18 -0
- package/lib/State/ResourceState.d.ts.map +1 -1
- package/lib/Sync.d.ts.map +1 -1
- package/lib/Sync.js +8 -2
- package/lib/Sync.js.map +1 -1
- package/lib/Telemetry.d.ts +150 -0
- package/lib/Telemetry.d.ts.map +1 -0
- package/lib/Telemetry.js +512 -0
- package/lib/Telemetry.js.map +1 -0
- package/lib/Test/Alchemy.d.ts.map +1 -1
- package/lib/Test/Alchemy.js +3 -1
- package/lib/Test/Alchemy.js.map +1 -1
- package/lib/Test/Core.d.ts +2 -2
- package/lib/Test/Core.d.ts.map +1 -1
- package/lib/Test/Core.js +1 -8
- package/lib/Test/Core.js.map +1 -1
- package/lib/Util/Clank.d.ts +11 -6
- package/lib/Util/Clank.d.ts.map +1 -1
- package/lib/Util/Clank.js +22 -9
- package/lib/Util/Clank.js.map +1 -1
- package/lib/Util/zip.d.ts +11 -0
- package/lib/Util/zip.d.ts.map +1 -1
- package/lib/Util/zip.js +30 -0
- package/lib/Util/zip.js.map +1 -1
- package/lib/index.d.ts +3 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +3 -0
- package/lib/index.js.map +1 -1
- package/package.json +46 -13
- package/src/AWS/ApiGateway/common.ts +9 -0
- package/src/AWS/Bedrock/LanguageModel.ts +883 -0
- package/src/AWS/Bedrock/LanguageModelHttp.ts +36 -0
- package/src/AWS/Bedrock/index.ts +2 -0
- package/src/AWS/CloudFront/Distribution.ts +116 -6
- package/src/AWS/CostExplorer/common.ts +23 -2
- package/src/AWS/DataSync/Task.ts +30 -15
- package/src/AWS/EC2/Vpc.ts +35 -21
- package/src/AWS/EC2/hosted.ts +10 -2
- package/src/AWS/ECR/ImageSource.ts +10 -2
- package/src/AWS/ECR/Repository.ts +39 -4
- package/src/AWS/ECS/Service.ts +322 -22
- package/src/AWS/ECS/Task.ts +82 -32
- package/src/AWS/EKS/Deployment.ts +10 -2
- package/src/AWS/EKS/Job.ts +10 -2
- package/src/AWS/Lambda/Function.ts +101 -43
- package/src/AWS/Lambda/HttpServer.ts +7 -1
- package/src/AWS/Lambda/LayerVersion.ts +490 -0
- package/src/AWS/Lambda/MicrovmBundle.ts +10 -2
- package/src/AWS/Lambda/index.ts +1 -0
- package/src/AWS/Providers.ts +10 -0
- package/src/AWS/SES/ActiveReceiptRuleSet.ts +88 -0
- package/src/AWS/SES/ReceiptFilter.ts +169 -0
- package/src/AWS/SES/ReceiptRule.ts +300 -0
- package/src/AWS/SES/ReceiptRuleSet.ts +164 -0
- package/src/AWS/SES/SendBounce.ts +44 -0
- package/src/AWS/SES/SendBounceHttp.ts +13 -0
- package/src/AWS/SES/index.ts +6 -0
- package/src/AWS/StateStore/State.ts +159 -42
- package/src/Apply.ts +199 -83
- package/src/Axiom/Telemetry.ts +119 -0
- package/src/Axiom/index.ts +1 -0
- package/src/Bundle/Bundle.ts +17 -7
- package/src/Bundle/BundleAnalyzerPlugin.ts +9 -4
- package/src/Bundle/PurePlugin.ts +19 -9
- package/src/Cli/checkVersion.ts +102 -16
- package/src/Cli/commands/_shared.ts +2 -0
- package/src/Cli/commands/deploy.ts +3 -14
- package/src/Cli/commands/logs.ts +6 -1
- package/src/Cli/commands/tail.ts +6 -1
- package/src/Cli/main.ts +5 -4
- package/src/Cloudflare/AI/Search.ts +4 -18
- package/src/Cloudflare/AI/SearchInstance.ts +7 -26
- package/src/Cloudflare/ApiToken/AccountApiToken.ts +3 -0
- package/src/Cloudflare/Auth/AuthProvider.ts +17 -3
- package/src/Cloudflare/Auth/OAuthClient.ts +58 -2
- package/src/Cloudflare/Containers/Container.ts +92 -3
- package/src/Cloudflare/Containers/ContainerApplication.ts +31 -5
- package/src/Cloudflare/Containers/ContainerBundle.ts +10 -2
- package/src/Cloudflare/Containers/ContainerProvider.ts +62 -0
- package/src/Cloudflare/Credentials.ts +69 -9
- package/src/Cloudflare/D1/ApplyMigrations.ts +72 -77
- package/src/Cloudflare/D1/Database.ts +169 -2
- package/src/Cloudflare/D1/LocalD1Gateway.ts +150 -0
- package/src/Cloudflare/D1/QueryDatabaseHttpClient.ts +49 -11
- package/src/Cloudflare/D1/QueryDatabaseLocal.ts +44 -2
- package/src/Cloudflare/DNS/Record.ts +30 -4
- package/src/Cloudflare/Email/Address.ts +18 -1
- package/src/Cloudflare/Email/SendBinding.ts +1 -0
- package/src/Cloudflare/Email/SendEmail.ts +13 -0
- package/src/Cloudflare/Flagship/App.ts +18 -6
- package/src/Cloudflare/KV/LocalKVGateway.ts +86 -0
- package/src/Cloudflare/KV/Namespace.ts +47 -1
- package/src/Cloudflare/KV/NamespaceLocal.ts +29 -6
- package/src/Cloudflare/KV/ReadNamespaceLocal.ts +5 -1
- package/src/Cloudflare/KV/ReadWriteNamespaceLocal.ts +5 -1
- package/src/Cloudflare/KV/WriteNamespaceLocal.ts +5 -1
- package/src/Cloudflare/LocalGateway.ts +86 -0
- package/src/Cloudflare/LocalRuntime.ts +21 -1
- package/src/Cloudflare/Logs.ts +58 -28
- package/src/Cloudflare/Providers.ts +4 -2
- package/src/Cloudflare/Queues/Consumer.ts +70 -2
- package/src/Cloudflare/Queues/Queue.ts +7 -16
- package/src/Cloudflare/Queues/QueueBinding.ts +21 -0
- package/src/Cloudflare/Queues/QueueShim.ts +193 -0
- package/src/Cloudflare/R2/Bucket.ts +55 -1
- package/src/Cloudflare/R2/BucketBinding.ts +17 -3
- package/src/Cloudflare/R2/BucketLocal.ts +25 -8
- package/src/Cloudflare/R2/LocalR2Gateway.ts +84 -0
- package/src/Cloudflare/R2/ReadBucketLocal.ts +7 -1
- package/src/Cloudflare/R2/ReadWriteBucketLocal.ts +7 -1
- package/src/Cloudflare/R2/WriteBucketLocal.ts +7 -1
- package/src/Cloudflare/Ruleset/AccountEntrypoint.ts +5 -1
- package/src/Cloudflare/Ruleset/Ruleset.ts +20 -6
- package/src/Cloudflare/Rum/Site.ts +11 -4
- package/src/Cloudflare/StateStore/Api.ts +1 -1
- package/src/Cloudflare/Tunnel/Tunnel.ts +23 -1
- package/src/Cloudflare/Website/StaticSite.ts +117 -28
- package/src/Cloudflare/Website/Vite.ts +33 -19
- package/src/Cloudflare/Workers/Compatibility.ts +27 -7
- package/src/Cloudflare/Workers/DurableObject.ts +22 -0
- package/src/Cloudflare/Workers/DurableObjectBridge.ts +29 -10
- package/src/Cloudflare/Workers/InferEnv.ts +74 -59
- package/src/Cloudflare/Workers/LocalWorkerProvider.ts +619 -401
- package/src/Cloudflare/Workers/Worker.ts +676 -28
- package/src/Cloudflare/Workers/WorkerAsyncBindings.ts +209 -25
- package/src/Cloudflare/Workers/WorkerBinding.ts +67 -3
- package/src/Cloudflare/Workers/WorkerBridge.ts +26 -4
- package/src/Cloudflare/Workers/WorkerBundle.ts +6 -1
- package/src/Cloudflare/Workers/WorkerProvider.ts +2269 -277
- package/src/Cloudflare/Workflows/WorkflowBridge.ts +31 -13
- package/src/Command/Build.ts +8 -8
- package/src/Command/Command.ts +217 -42
- package/src/Command/Dev.ts +79 -107
- package/src/Command/Exec.ts +11 -2
- package/src/Command/Memo.ts +27 -3
- package/src/Command/Redaction.ts +181 -0
- package/src/Destroy.ts +1 -10
- package/src/Docker/Container.ts +55 -15
- package/src/Docker/Context.ts +236 -0
- package/src/Docker/Docker.ts +393 -43
- package/src/Docker/Image.ts +28 -7
- package/src/Docker/Network.ts +38 -14
- package/src/Docker/Providers.ts +16 -1
- package/src/Docker/RemoteImage.ts +23 -7
- package/src/Docker/Service.ts +1119 -0
- package/src/Docker/ServiceImage.ts +331 -0
- package/src/Docker/Swarm.ts +226 -0
- package/src/Docker/Volume.ts +25 -6
- package/src/Docker/index.ts +3 -0
- package/src/Http.ts +15 -2
- package/src/Local/LocalProvider.ts +518 -0
- package/src/Local/ProviderLayer.ts +113 -11
- package/src/Local/RpcProvider.ts +10 -5
- package/src/Local/RpcProviderProxy.ts +4 -1
- package/src/Local/RpcServer.ts +20 -2
- package/src/Plan.ts +182 -54
- package/src/Planetscale/AuthProvider.ts +15 -2
- package/src/Planetscale/Branch.ts +39 -6
- package/src/Planetscale/MySQL/MySQLDatabase.ts +34 -6
- package/src/Planetscale/OAuthClient.ts +59 -3
- package/src/Planetscale/Postgres/PostgresDatabase.ts +33 -5
- package/src/Platform.ts +29 -17
- package/src/Prisma/App.ts +433 -0
- package/src/Prisma/AuthProvider.ts +260 -0
- package/src/Prisma/Branch.ts +417 -0
- package/src/Prisma/Client.ts +1337 -0
- package/src/Prisma/Compute.ts +2878 -0
- package/src/Prisma/ComputeArchive.ts +432 -0
- package/src/Prisma/ComputeBuild.ts +1652 -0
- package/src/Prisma/ComputeLifecycle.ts +391 -0
- package/src/Prisma/Connect.ts +433 -0
- package/src/Prisma/Connection.ts +643 -0
- package/src/Prisma/CustomDomain.ts +373 -0
- package/src/Prisma/Database.ts +850 -0
- package/src/Prisma/Deployment.ts +661 -0
- package/src/Prisma/EnvironmentVariable.ts +417 -0
- package/src/Prisma/Internal/AppIdentity.ts +19 -0
- package/src/Prisma/Internal/AppPromotion.ts +172 -0
- package/src/Prisma/Internal/ArchivePlatform.ts +320 -0
- package/src/Prisma/Internal/ArtifactFile.ts +242 -0
- package/src/Prisma/Internal/ArtifactUpload.ts +86 -0
- package/src/Prisma/Internal/BundlePaths.ts +28 -0
- package/src/Prisma/Internal/CleanupFailure.ts +17 -0
- package/src/Prisma/Internal/DatabaseSecrets.ts +175 -0
- package/src/Prisma/Internal/DeploymentActions.ts +59 -0
- package/src/Prisma/Internal/DeploymentIdentity.ts +25 -0
- package/src/Prisma/Internal/DeploymentObserve.ts +6 -0
- package/src/Prisma/Internal/EnvName.ts +24 -0
- package/src/Prisma/Internal/HttpClient.ts +30 -0
- package/src/Prisma/Operations.ts +259 -0
- package/src/Prisma/Postgres.ts +25 -0
- package/src/Prisma/PostgresOrigin.ts +52 -0
- package/src/Prisma/PrismaDevDatabase.ts +395 -0
- package/src/Prisma/PrismaEnvironment.ts +89 -0
- package/src/Prisma/PrismaLogs.ts +434 -0
- package/src/Prisma/Project.ts +687 -0
- package/src/Prisma/Providers.ts +471 -0
- package/src/Prisma/Refs.ts +69 -0
- package/src/Prisma/SourceRepository.ts +455 -0
- package/src/Prisma/Types.ts +611 -0
- package/src/Prisma/index.ts +24 -0
- package/src/Provider.ts +155 -39
- package/src/ProviderMode.ts +123 -0
- package/src/Resource.ts +46 -0
- package/src/RuntimeContext.ts +7 -0
- package/src/Server/Process.ts +33 -0
- package/src/State/InMemoryState.ts +2 -0
- package/src/State/ResourceState.ts +18 -0
- package/src/Sync.ts +14 -2
- package/src/Telemetry.ts +736 -0
- package/src/Test/Alchemy.ts +3 -1
- package/src/Test/Core.ts +1 -8
- package/src/Util/Clank.ts +37 -11
- package/src/Util/zip.ts +39 -0
- package/src/index.ts +3 -0
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import * as durableObjectsApi from "@distilled.cloud/cloudflare/durable-objects";
|
|
2
|
+
import * as rulesets from "@distilled.cloud/cloudflare/rulesets";
|
|
2
3
|
import * as workers from "@distilled.cloud/cloudflare/workers";
|
|
3
4
|
import * as wfp from "@distilled.cloud/cloudflare/workers-for-platforms";
|
|
4
5
|
import * as zones from "@distilled.cloud/cloudflare/zones";
|
|
6
|
+
import * as Config from "effect/Config";
|
|
5
7
|
import * as Data from "effect/Data";
|
|
6
8
|
import * as Effect from "effect/Effect";
|
|
9
|
+
import * as Layer from "effect/Layer";
|
|
10
|
+
import * as Option from "effect/Option";
|
|
7
11
|
import * as Path from "effect/Path";
|
|
8
12
|
import * as Predicate from "effect/Predicate";
|
|
9
13
|
import * as Redacted from "effect/Redacted";
|
|
10
14
|
import * as Schedule from "effect/Schedule";
|
|
11
15
|
import * as Stream from "effect/Stream";
|
|
16
|
+
import { isHttpClientError } from "effect/unstable/http/HttpClientError";
|
|
12
17
|
import * as crypto from "node:crypto";
|
|
13
18
|
import { Unowned } from "../../AdoptPolicy.js";
|
|
14
19
|
import * as Artifacts from "../../Artifacts.js";
|
|
@@ -18,16 +23,18 @@ import * as ProviderLayer from "../../Local/ProviderLayer.js";
|
|
|
18
23
|
import * as Provider from "../../Provider.js";
|
|
19
24
|
import {} from "../../Resource.js";
|
|
20
25
|
import { Stack } from "../../Stack.js";
|
|
26
|
+
import { cachedFunction } from "../../Util/cached-function.js";
|
|
21
27
|
import { sha256Object } from "../../Util/sha256.js";
|
|
22
28
|
import { CloudflareEnvironment } from "../CloudflareEnvironment.js";
|
|
29
|
+
import { localRuntimeServices } from "../LocalRuntime.js";
|
|
23
30
|
import { CloudflareLogs } from "../Logs.js";
|
|
24
|
-
import {
|
|
31
|
+
import { resolveZoneId } from "../Zone/lookup.js";
|
|
25
32
|
import { mergeAssetsConfigFiles, readAssets, readAssetsConfigFiles, uploadAssets, } from "./Assets.js";
|
|
26
33
|
import { getCompatibility } from "./Compatibility.js";
|
|
27
34
|
import { isDurableObjectExport } from "./DurableObject.js";
|
|
28
35
|
import { LocalWorkerProvider } from "./LocalWorkerProvider.js";
|
|
29
|
-
import { Worker, } from "./Worker.js";
|
|
30
|
-
import { getCacheBinding, getCronBindings } from "./WorkerAsyncBindings.js";
|
|
36
|
+
import { isSelfUrl, Worker, } from "./Worker.js";
|
|
37
|
+
import { getCacheBinding, getCronBindings, isContainerDecl, } from "./WorkerAsyncBindings.js";
|
|
31
38
|
import { isPythonMain, readPythonWorkerBundle } from "./PythonWorkerBundle.js";
|
|
32
39
|
import { readPrebuiltWorkerBundle, WorkerBundle } from "./WorkerBundle.js";
|
|
33
40
|
import { isWorkerLoader } from "./WorkerLoader.js";
|
|
@@ -90,6 +97,298 @@ export const resolveNamespaceName = (namespace) => {
|
|
|
90
97
|
return namespace;
|
|
91
98
|
return namespace.name;
|
|
92
99
|
};
|
|
100
|
+
/**
|
|
101
|
+
* A Worker's `version` configuration is invalid — a prop that can't be
|
|
102
|
+
* combined with `version.parent` (script-level settings belong to the
|
|
103
|
+
* parent), a locally-hosted Durable Object / Workflow class on a version
|
|
104
|
+
* worker, an out-of-range `traffic`, or a gradual rollout that requires
|
|
105
|
+
* changes the versions API can't carry (assets, DO migrations).
|
|
106
|
+
*/
|
|
107
|
+
export class WorkerVersionConfigError extends Data.TaggedError("WorkerVersionConfigError") {
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Resolve the parent script *name* from a resolved `version.parent` prop or
|
|
111
|
+
* persisted props. The engine resolves a passed {@link Worker} (or
|
|
112
|
+
* `Worker.ref(...)`) to its Attributes object — possibly stables-only during
|
|
113
|
+
* planning, but `workerName` is always a stable — so the value is either the
|
|
114
|
+
* script name string, that attributes object, or `undefined`.
|
|
115
|
+
*
|
|
116
|
+
* @internal
|
|
117
|
+
*/
|
|
118
|
+
export const resolveVersionParentName = (version) => {
|
|
119
|
+
const parent = version?.parent;
|
|
120
|
+
if (parent == null)
|
|
121
|
+
return undefined;
|
|
122
|
+
if (typeof parent === "string")
|
|
123
|
+
return parent;
|
|
124
|
+
const workerName = parent.workerName;
|
|
125
|
+
return typeof workerName === "string" ? workerName : undefined;
|
|
126
|
+
};
|
|
127
|
+
/**
|
|
128
|
+
* The traffic percentage a *self-owned* Worker's new version should receive,
|
|
129
|
+
* or `undefined` for the default full cutover. Only a `version` prop without
|
|
130
|
+
* a `parent` participates — version workers handle traffic separately.
|
|
131
|
+
*
|
|
132
|
+
* @internal
|
|
133
|
+
*/
|
|
134
|
+
const getSelfRolloutTraffic = (news) => {
|
|
135
|
+
if (!news.version || news.version.parent != null)
|
|
136
|
+
return undefined;
|
|
137
|
+
const traffic = news.version.traffic;
|
|
138
|
+
return traffic === undefined || traffic >= 100 ? undefined : traffic;
|
|
139
|
+
};
|
|
140
|
+
const validateTraffic = (traffic) => traffic !== undefined &&
|
|
141
|
+
(!Number.isFinite(traffic) || traffic < 0 || traffic > 100)
|
|
142
|
+
? Effect.fail(new WorkerVersionConfigError({
|
|
143
|
+
message: `version.traffic must be a percentage between 0 and 100, got ${traffic}`,
|
|
144
|
+
}))
|
|
145
|
+
: Effect.void;
|
|
146
|
+
/** The request header Cloudflare hashes to pin a request to a version. */
|
|
147
|
+
const AFFINITY_HEADER = "Cloudflare-Workers-Version-Key";
|
|
148
|
+
// Cookie / header names are interpolated into a double-quoted Rules-language
|
|
149
|
+
// string literal — restrict them to the token characters real-world names
|
|
150
|
+
// use so a name can never terminate the literal or smuggle expression text.
|
|
151
|
+
const AFFINITY_NAME_PATTERN = /^[A-Za-z0-9_.-]+$/;
|
|
152
|
+
/**
|
|
153
|
+
* Validate `version.affinity` and normalize it to its key source: exactly
|
|
154
|
+
* one of `cookie` / `header` / `key`, or `ip: true` alone; `ip` combines
|
|
155
|
+
* with `cookie` / `header` as the absent-source fallback.
|
|
156
|
+
*
|
|
157
|
+
* @internal exported for unit testing.
|
|
158
|
+
*/
|
|
159
|
+
export const resolveVersionAffinity = (affinity) => Effect.gen(function* () {
|
|
160
|
+
const declared = [
|
|
161
|
+
...(affinity.cookie !== undefined ? ["cookie"] : []),
|
|
162
|
+
...(affinity.header !== undefined ? ["header"] : []),
|
|
163
|
+
...(affinity.key !== undefined ? ["key"] : []),
|
|
164
|
+
];
|
|
165
|
+
if (declared.length > 1) {
|
|
166
|
+
return yield* Effect.fail(new WorkerVersionConfigError({
|
|
167
|
+
message: `version.affinity accepts exactly one key source, got ${declared.join(" and ")}. Combine sources with a raw \`key\` expression instead.`,
|
|
168
|
+
}));
|
|
169
|
+
}
|
|
170
|
+
if (declared.length === 0 && affinity.ip !== true) {
|
|
171
|
+
return yield* Effect.fail(new WorkerVersionConfigError({
|
|
172
|
+
message: "version.affinity requires a key source: set `cookie`, `header`, `key`, or `ip: true`.",
|
|
173
|
+
}));
|
|
174
|
+
}
|
|
175
|
+
if (affinity.key !== undefined && affinity.ip === true) {
|
|
176
|
+
return yield* Effect.fail(new WorkerVersionConfigError({
|
|
177
|
+
message: "version.affinity: `ip` is the fallback for an absent `cookie`/`header` — a raw `key` expression has no absence condition to fall back from. Fold `ip.src` into the expression instead.",
|
|
178
|
+
}));
|
|
179
|
+
}
|
|
180
|
+
for (const [prop, name] of [
|
|
181
|
+
["cookie", affinity.cookie],
|
|
182
|
+
["header", affinity.header],
|
|
183
|
+
]) {
|
|
184
|
+
if (name !== undefined && !AFFINITY_NAME_PATTERN.test(name)) {
|
|
185
|
+
return yield* Effect.fail(new WorkerVersionConfigError({
|
|
186
|
+
message: `version.affinity.${prop} '${name}' is not a valid ${prop} name: expected only letters, digits, '_', '.', and '-'.`,
|
|
187
|
+
}));
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
const source = affinity.cookie !== undefined
|
|
191
|
+
? { kind: "cookie", name: affinity.cookie }
|
|
192
|
+
: affinity.header !== undefined
|
|
193
|
+
? // Rules-language header map keys are lowercase.
|
|
194
|
+
{ kind: "header", name: affinity.header.toLowerCase() }
|
|
195
|
+
: affinity.key !== undefined
|
|
196
|
+
? { kind: "key", expression: affinity.key }
|
|
197
|
+
: { kind: "ip" };
|
|
198
|
+
return {
|
|
199
|
+
source,
|
|
200
|
+
ipFallback: affinity.ip === true &&
|
|
201
|
+
(source.kind === "cookie" || source.kind === "header"),
|
|
202
|
+
};
|
|
203
|
+
});
|
|
204
|
+
/**
|
|
205
|
+
* The `http.host` clause scoping a zone's affinity rules to the Worker's
|
|
206
|
+
* own hostnames, so unrelated zone traffic — and other Workers' rollouts
|
|
207
|
+
* on the same zone — never get this Worker's version key.
|
|
208
|
+
*
|
|
209
|
+
* @internal exported for unit testing.
|
|
210
|
+
*/
|
|
211
|
+
export const affinityHostExpression = (hosts) => {
|
|
212
|
+
const dedupe = (values) => [...new Set(values)].sort();
|
|
213
|
+
const exact = dedupe(hosts.filter((h) => !h.wildcard).map((h) => h.host));
|
|
214
|
+
const wild = dedupe(hosts.filter((h) => h.wildcard).map((h) => h.host));
|
|
215
|
+
const clauses = [
|
|
216
|
+
...(exact.length === 1
|
|
217
|
+
? [`http.host eq "${exact[0]}"`]
|
|
218
|
+
: exact.length > 1
|
|
219
|
+
? [`http.host in {${exact.map((h) => `"${h}"`).join(" ")}}`]
|
|
220
|
+
: []),
|
|
221
|
+
...wild.map((h) => `http.host wildcard "${h}"`),
|
|
222
|
+
];
|
|
223
|
+
return clauses.length === 1 ? clauses[0] : `(${clauses.join(" or ")})`;
|
|
224
|
+
};
|
|
225
|
+
const affinityRulePrefix = (scriptName) => `alchemy:worker:${scriptName}:affinity`;
|
|
226
|
+
/**
|
|
227
|
+
* The transform rules pinning one zone's traffic: a primary rule filling
|
|
228
|
+
* the version-key header from the configured source, plus — for
|
|
229
|
+
* `cookie`/`header` sources with `ip: true` — a fallback rule keying
|
|
230
|
+
* requests that lack the source by client IP.
|
|
231
|
+
*
|
|
232
|
+
* @internal exported for unit testing.
|
|
233
|
+
*/
|
|
234
|
+
export const buildAffinityZoneRules = (scriptName, affinity, hosts) => {
|
|
235
|
+
const prefix = affinityRulePrefix(scriptName);
|
|
236
|
+
const hostExpr = affinityHostExpression(hosts);
|
|
237
|
+
const { source } = affinity;
|
|
238
|
+
if (source.kind === "ip" || source.kind === "key") {
|
|
239
|
+
return [
|
|
240
|
+
{
|
|
241
|
+
description: `${prefix}:key`,
|
|
242
|
+
expression: hostExpr,
|
|
243
|
+
value: source.kind === "ip" ? "to_string(ip.src)" : source.expression,
|
|
244
|
+
},
|
|
245
|
+
];
|
|
246
|
+
}
|
|
247
|
+
const field = source.kind === "cookie"
|
|
248
|
+
? `http.request.cookies["${source.name}"]`
|
|
249
|
+
: `http.request.headers["${source.name}"]`;
|
|
250
|
+
return [
|
|
251
|
+
{
|
|
252
|
+
description: `${prefix}:key`,
|
|
253
|
+
expression: `${hostExpr} and len(${field}) > 0`,
|
|
254
|
+
value: `${field}[0]`,
|
|
255
|
+
},
|
|
256
|
+
...(affinity.ipFallback
|
|
257
|
+
? [
|
|
258
|
+
{
|
|
259
|
+
// An absent cookie/header is a *missing* value in the Rules
|
|
260
|
+
// language, and every comparison on missing evaluates false —
|
|
261
|
+
// `len(field) == 0` can never match. `not (len(field) > 0)`
|
|
262
|
+
// is the complement that does: missing → false → `not` → true.
|
|
263
|
+
description: `${prefix}:ip`,
|
|
264
|
+
expression: `${hostExpr} and not (len(${field}) > 0)`,
|
|
265
|
+
value: "to_string(ip.src)",
|
|
266
|
+
},
|
|
267
|
+
]
|
|
268
|
+
: []),
|
|
269
|
+
];
|
|
270
|
+
};
|
|
271
|
+
/**
|
|
272
|
+
* A Worker's `domain` configuration is invalid — a hostname appears in more
|
|
273
|
+
* than one role (name/aliases/redirects), or a redirect targets itself.
|
|
274
|
+
*/
|
|
275
|
+
export class WorkerDomainConfigError extends Data.TaggedError("WorkerDomainConfigError") {
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Resolve the `workersDev` prop to its full shape. `true` / omitted means
|
|
279
|
+
* "default workers.dev behavior" (stable URL + version previews), `false`
|
|
280
|
+
* disables both, and the object form fills unset toggles with `true`.
|
|
281
|
+
*
|
|
282
|
+
* @internal exported for unit testing.
|
|
283
|
+
*/
|
|
284
|
+
export const resolveWorkersDev = (workersDev) => {
|
|
285
|
+
if (workersDev === undefined || workersDev === true) {
|
|
286
|
+
return { enabled: true, previewsEnabled: true };
|
|
287
|
+
}
|
|
288
|
+
if (workersDev === false) {
|
|
289
|
+
return { enabled: false, previewsEnabled: false };
|
|
290
|
+
}
|
|
291
|
+
return {
|
|
292
|
+
enabled: workersDev.enabled ?? true,
|
|
293
|
+
previewsEnabled: workersDev.previewsEnabled ?? true,
|
|
294
|
+
};
|
|
295
|
+
};
|
|
296
|
+
// Convert non-ASCII hostnames (emoji, IDN, etc.) to punycode so the
|
|
297
|
+
// Cloudflare API receives the form it stores domains in. `new URL(...)`
|
|
298
|
+
// does IDNA via WHATWG URL parsing — `📦.alchemy.run` → `xn--5z8h.alchemy.run`.
|
|
299
|
+
const toPunycode = (hostname) => {
|
|
300
|
+
try {
|
|
301
|
+
return new URL(`https://${hostname}`).hostname;
|
|
302
|
+
}
|
|
303
|
+
catch {
|
|
304
|
+
return hostname;
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
/**
|
|
308
|
+
* Resolve the `domain` prop to its full shape — a bare string is shorthand
|
|
309
|
+
* for `{ name }`. Hostnames are punycode-normalized and de-duplicated;
|
|
310
|
+
* a hostname may only play one role, so aliases/redirects that repeat the
|
|
311
|
+
* canonical name (or each other) fail with a typed error.
|
|
312
|
+
*
|
|
313
|
+
* @internal exported for unit testing.
|
|
314
|
+
*/
|
|
315
|
+
export const resolveWorkerDomain = (
|
|
316
|
+
// `string[]` is the pre-redesign prop shape — it can still reach us from
|
|
317
|
+
// persisted `olds` written by older providers (read's classification).
|
|
318
|
+
domain) => Effect.gen(function* () {
|
|
319
|
+
if (domain === undefined || domain === null)
|
|
320
|
+
return undefined;
|
|
321
|
+
// Legacy array form: the first hostname was the primary custom domain
|
|
322
|
+
// (`url = domains[0]` back then), the rest map to aliases. A legacy
|
|
323
|
+
// empty array was the explicit detach-all — no domain.
|
|
324
|
+
const config = typeof domain === "string"
|
|
325
|
+
? { name: domain }
|
|
326
|
+
: Array.isArray(domain)
|
|
327
|
+
? domain.length > 0
|
|
328
|
+
? { name: domain[0], aliases: domain.slice(1) }
|
|
329
|
+
: undefined
|
|
330
|
+
: domain;
|
|
331
|
+
if (config === undefined)
|
|
332
|
+
return undefined;
|
|
333
|
+
const name = toPunycode(config.name);
|
|
334
|
+
const aliases = Array.from(new Set((config.aliases ?? []).map(toPunycode)));
|
|
335
|
+
const redirects = Array.from(new Set((config.redirects ?? []).map(toPunycode)));
|
|
336
|
+
const overlap = [
|
|
337
|
+
...aliases.filter((h) => h === name),
|
|
338
|
+
...redirects.filter((h) => h === name || aliases.includes(h)),
|
|
339
|
+
];
|
|
340
|
+
if (overlap.length > 0) {
|
|
341
|
+
return yield* Effect.fail(new WorkerDomainConfigError({
|
|
342
|
+
message: `Each hostname may play only one role in a Worker's domain config; ${[...new Set(overlap)].map((h) => `'${h}'`).join(", ")} appears in more than one of name/aliases/redirects.`,
|
|
343
|
+
}));
|
|
344
|
+
}
|
|
345
|
+
return { name, aliases, redirects };
|
|
346
|
+
});
|
|
347
|
+
const isWorkersDevHostname = (hostname) => hostname.endsWith(".workers.dev");
|
|
348
|
+
// Hostnames that only appear in local-dev state (the dev server's
|
|
349
|
+
// localhost/LAN URLs), never as attachable custom domains.
|
|
350
|
+
const isLocalDevHostname = (hostname) => hostname === "localhost" ||
|
|
351
|
+
hostname === "::1" ||
|
|
352
|
+
/^\d+\.\d+\.\d+\.\d+$/.test(hostname);
|
|
353
|
+
const urlHostname = (url) => {
|
|
354
|
+
try {
|
|
355
|
+
return new URL(url).hostname;
|
|
356
|
+
}
|
|
357
|
+
catch {
|
|
358
|
+
return url;
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
/**
|
|
362
|
+
* The *custom domain* hostnames recorded in a Worker's persisted legacy
|
|
363
|
+
* `domains` state — minus the workers.dev (stable or preview) and local-dev
|
|
364
|
+
* entries that shared the list in older formats.
|
|
365
|
+
*
|
|
366
|
+
* @internal exported for unit testing.
|
|
367
|
+
*/
|
|
368
|
+
export const stateCustomDomains = (domains) => normalizeStateDomains(domains).filter((hostname) => !isWorkersDevHostname(hostname) && !isLocalDevHostname(hostname));
|
|
369
|
+
/**
|
|
370
|
+
* The Worker's persisted domain configuration: the `domain` attribute for
|
|
371
|
+
* state written by the current format, else re-derived from the legacy
|
|
372
|
+
* `domains` list (first custom hostname = canonical name, rest = aliases;
|
|
373
|
+
* legacy state had no redirects).
|
|
374
|
+
*
|
|
375
|
+
* @internal exported for unit testing.
|
|
376
|
+
*/
|
|
377
|
+
export const stateWorkerDomain = (output) => {
|
|
378
|
+
const state = output;
|
|
379
|
+
const domain = state?.domain;
|
|
380
|
+
if (domain && typeof domain.name === "string") {
|
|
381
|
+
return {
|
|
382
|
+
name: domain.name,
|
|
383
|
+
aliases: (domain.aliases ?? []).filter((h) => typeof h === "string"),
|
|
384
|
+
redirects: (domain.redirects ?? []).filter((h) => typeof h === "string"),
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
const legacy = stateCustomDomains(state?.domains);
|
|
388
|
+
return legacy.length > 0
|
|
389
|
+
? { name: legacy[0], aliases: legacy.slice(1), redirects: [] }
|
|
390
|
+
: undefined;
|
|
391
|
+
};
|
|
93
392
|
// Workers for Platforms "user workers" live inside a dispatch namespace and
|
|
94
393
|
// use a parallel family of script endpoints (`/workers/dispatch/namespaces/
|
|
95
394
|
// :namespace/scripts/...`). The request/response shapes are identical to the
|
|
@@ -149,6 +448,15 @@ const isBindingTargetNotFound = (e) => e._tag === "SecretsStoreBindingNotFound"
|
|
|
149
448
|
e._tag === "DispatchNamespaceNotFound" ||
|
|
150
449
|
e._tag === "MtlsCertificateNotFound";
|
|
151
450
|
const bindingTargetNotFoundRetrySchedule = () => Schedule.max([Schedule.fixed("2 seconds"), Schedule.recurs(10)]);
|
|
451
|
+
/**
|
|
452
|
+
* Script PUT is an idempotent upsert, so a pure transport failure (the
|
|
453
|
+
* request died before any response — e.g. Cloudflare closing a keep-alive
|
|
454
|
+
* socket that idled while slow upstream resources provisioned earlier in the
|
|
455
|
+
* deploy) is safe to replay. Errors that carry a response are real API
|
|
456
|
+
* verdicts and are NOT retried here.
|
|
457
|
+
*/
|
|
458
|
+
const isScriptPutTransportError = (e) => isHttpClientError(e) && e.reason._tag === "TransportError";
|
|
459
|
+
const retryableScriptPut = (e) => isBindingTargetNotFound(e) || isScriptPutTransportError(e);
|
|
152
460
|
/**
|
|
153
461
|
* Upsert a Worker script, routing to the dispatch-namespace endpoint when
|
|
154
462
|
* `dispatchNamespace` is set. The metadata/files contract is identical, and
|
|
@@ -168,7 +476,7 @@ const putWorkerScript = (params) => Effect.gen(function* () {
|
|
|
168
476
|
files: params.files,
|
|
169
477
|
})
|
|
170
478
|
.pipe(Effect.retry({
|
|
171
|
-
while:
|
|
479
|
+
while: retryableScriptPut,
|
|
172
480
|
schedule: bindingTargetNotFoundRetrySchedule(),
|
|
173
481
|
}));
|
|
174
482
|
}
|
|
@@ -180,7 +488,7 @@ const putWorkerScript = (params) => Effect.gen(function* () {
|
|
|
180
488
|
files: params.files,
|
|
181
489
|
})
|
|
182
490
|
.pipe(Effect.retry({
|
|
183
|
-
while:
|
|
491
|
+
while: retryableScriptPut,
|
|
184
492
|
schedule: bindingTargetNotFoundRetrySchedule(),
|
|
185
493
|
}));
|
|
186
494
|
});
|
|
@@ -202,23 +510,70 @@ const deleteWorkerScript = (accountId, scriptName, dispatchNamespace) => Effect.
|
|
|
202
510
|
return yield* workers.deleteScript({ accountId, scriptName, force: true });
|
|
203
511
|
});
|
|
204
512
|
/**
|
|
205
|
-
* Normalize a Worker's persisted `domains` state to
|
|
206
|
-
*
|
|
207
|
-
* `{ id, hostname, zoneId }` object; beta.45+
|
|
208
|
-
* strings
|
|
209
|
-
*
|
|
210
|
-
*
|
|
211
|
-
*
|
|
212
|
-
*
|
|
513
|
+
* Normalize a Worker's persisted *legacy* `domains` state to bare
|
|
514
|
+
* hostnames. Alchemy <= beta.44 stored each custom domain as a
|
|
515
|
+
* `{ id, hostname, zoneId }` object; beta.45+ stored `https://<hostname>`
|
|
516
|
+
* URL strings (with the workers.dev URL mixed in); current state stores the
|
|
517
|
+
* `domain` config object instead of a `domains` list. All legacy
|
|
518
|
+
* generations coerce to hostnames so the diff never throws on older state
|
|
519
|
+
* (#546). Entries that fit no generation are dropped rather than turned
|
|
520
|
+
* into a bogus hostname that would skew the diff.
|
|
213
521
|
*
|
|
214
522
|
* @internal exported for unit testing.
|
|
215
523
|
*/
|
|
216
524
|
export const normalizeStateDomains = (domains) => (domains ?? []).flatMap((u) => {
|
|
217
|
-
if (typeof u === "string")
|
|
218
|
-
|
|
525
|
+
if (typeof u === "string") {
|
|
526
|
+
if (u.includes("://")) {
|
|
527
|
+
try {
|
|
528
|
+
return [new URL(u).hostname];
|
|
529
|
+
}
|
|
530
|
+
catch {
|
|
531
|
+
return [];
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
return u.length > 0 ? [u] : [];
|
|
535
|
+
}
|
|
219
536
|
const hostname = u?.hostname;
|
|
220
|
-
return typeof hostname === "string" ? [
|
|
537
|
+
return typeof hostname === "string" ? [hostname] : [];
|
|
221
538
|
});
|
|
539
|
+
/**
|
|
540
|
+
* Custom domains Alchemy is responsible for on this Worker — either declared
|
|
541
|
+
* on props (`domain`) or already persisted as non-`workers.dev` URLs in state.
|
|
542
|
+
* Used by `read` to skip `listDomains` when the surface is unmanaged (#926).
|
|
543
|
+
*
|
|
544
|
+
* @internal exported for unit testing.
|
|
545
|
+
*/
|
|
546
|
+
export const shouldObserveWorkerDomains = (olds, output) => olds?.domain !== undefined || stateWorkerDomain(output) !== undefined;
|
|
547
|
+
/**
|
|
548
|
+
* Zone routes Alchemy is responsible for on this Worker. Used by `read` to
|
|
549
|
+
* skip account-wide zone/route fan-out when the surface is unmanaged (#926).
|
|
550
|
+
*
|
|
551
|
+
* @internal exported for unit testing.
|
|
552
|
+
*/
|
|
553
|
+
export const shouldObserveWorkerRoutes = (olds, output) => olds?.routes !== undefined || (output?.routes?.length ?? 0) > 0;
|
|
554
|
+
/**
|
|
555
|
+
* Cron triggers Alchemy is responsible for on this Worker. Used by `read` to
|
|
556
|
+
* skip `getScriptSchedule` when the surface is unmanaged (#926). Effect-native
|
|
557
|
+
* `cron()` bindings persist into `output.crons` after the first reconcile, so
|
|
558
|
+
* subsequent reads still observe them.
|
|
559
|
+
*
|
|
560
|
+
* @internal exported for unit testing.
|
|
561
|
+
*/
|
|
562
|
+
export const shouldObserveWorkerCrons = (olds, output) => olds?.crons !== undefined || (output?.crons?.length ?? 0) > 0;
|
|
563
|
+
/**
|
|
564
|
+
* Optional override for the account's stable `workers.dev` subdomain
|
|
565
|
+
* (`<subdomain>` in `https://<script>.<subdomain>.workers.dev`). When set,
|
|
566
|
+
* Worker URL construction skips `GET /accounts/{id}/workers/subdomain`.
|
|
567
|
+
*/
|
|
568
|
+
const CLOUDFLARE_WORKERS_SUBDOMAIN = Config.string("CLOUDFLARE_WORKERS_SUBDOMAIN").pipe(Config.map((value) => value.trim()), Config.option);
|
|
569
|
+
const WORKERS_SUBDOMAIN_PATTERN = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
|
|
570
|
+
/**
|
|
571
|
+
* Max concurrent `GET /zones/{id}/workers/routes` calls when observing the
|
|
572
|
+
* routes attached to a Worker. Route listing is per-zone, so this fans out
|
|
573
|
+
* with the number of zones the Worker has routes in; unbounded fan-out here
|
|
574
|
+
* is what trips Cloudflare account-level 429 / code 971 throttling (#926).
|
|
575
|
+
*/
|
|
576
|
+
const WORKER_ROUTE_LIST_CONCURRENCY = 8;
|
|
222
577
|
/**
|
|
223
578
|
* Deeply materialize an arbitrary value into a JSON-stable shape for hashing:
|
|
224
579
|
* unwrap `Redacted` secrets by value, ISO-stringify `Date`s, keep plain
|
|
@@ -289,8 +644,9 @@ const workerAssetConfigForHash = (assets) => {
|
|
|
289
644
|
* the bundle/vite/asset-content hashes, so a change to e.g. a compatibility
|
|
290
645
|
* flag or observability config planned as a noop and silently never deployed.
|
|
291
646
|
*/
|
|
292
|
-
const resolveWorkerMetadataHash = ({ props, bindings, accountId, stack, }) => resolveMetadataHashValue({
|
|
647
|
+
const resolveWorkerMetadataHash = ({ props, bindings, accountId, stack, selfUrl, }) => resolveMetadataHashValue({
|
|
293
648
|
accountId,
|
|
649
|
+
selfUrl,
|
|
294
650
|
stack: { name: stack.name, stage: stack.stage },
|
|
295
651
|
compatibility: getCompatibility(props),
|
|
296
652
|
env: props.env,
|
|
@@ -304,13 +660,30 @@ const resolveWorkerMetadataHash = ({ props, bindings, accountId, stack, }) => re
|
|
|
304
660
|
logpush: props.logpush,
|
|
305
661
|
observability: props.observability,
|
|
306
662
|
placement: props.placement,
|
|
307
|
-
subdomain: props.subdomain,
|
|
308
663
|
tags: props.tags,
|
|
309
|
-
|
|
664
|
+
workersDev: resolveWorkersDev(props.workersDev),
|
|
665
|
+
// Reduce `version.parent` to the parent's script name: the resolved
|
|
666
|
+
// parent is a full attributes object whose *other* fields (hash, url,
|
|
667
|
+
// ...) change on every parent deploy, which would spuriously re-version
|
|
668
|
+
// this Worker even though nothing about the version itself changed.
|
|
669
|
+
version: props.version
|
|
670
|
+
? {
|
|
671
|
+
parent: resolveVersionParentName(props.version),
|
|
672
|
+
traffic: props.version.traffic,
|
|
673
|
+
alias: props.version.alias,
|
|
674
|
+
affinity: props.version.affinity,
|
|
675
|
+
message: props.version.message,
|
|
676
|
+
tag: props.version.tag,
|
|
677
|
+
}
|
|
678
|
+
: undefined,
|
|
310
679
|
}).pipe(Effect.flatMap((metadata) => sha256Object({ metadata })));
|
|
311
|
-
export const WorkerProvider = () => ProviderLayer.
|
|
680
|
+
export const WorkerProvider = () => ProviderLayer.dual(Worker, {
|
|
312
681
|
live: () => LiveWorkerProvider(),
|
|
313
|
-
local
|
|
682
|
+
// The local runtime deps (workerd, WorkerProxy, LocalRuntimeState)
|
|
683
|
+
// compose INTO the local variant so a live deploy only constructs them
|
|
684
|
+
// if the local provider is actually demanded (e.g. deleting a local
|
|
685
|
+
// dev worker's state row). See ProviderLayer.dual.
|
|
686
|
+
local: () => LocalWorkerProvider().pipe(Layer.provide(localRuntimeServices())),
|
|
314
687
|
});
|
|
315
688
|
export const LiveWorkerProvider = () => Provider.effect(Worker, Effect.gen(function* () {
|
|
316
689
|
const path = yield* Path.Path;
|
|
@@ -329,41 +702,53 @@ export const LiveWorkerProvider = () => Provider.effect(Worker, Effect.gen(funct
|
|
|
329
702
|
// const deleteDomain = yield* workers.deleteDomain;
|
|
330
703
|
// const listZones = yield* zones.listZones;
|
|
331
704
|
const telemetry = yield* CloudflareLogs;
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
705
|
+
// Account subdomain is invariant for the life of a provider layer —
|
|
706
|
+
// memoize so N Workers (or repeated plan/deploy reads) don't each hit
|
|
707
|
+
// GET /accounts/{id}/workers/subdomain and trip Cloudflare's 429/971
|
|
708
|
+
// throttle (#926). `CLOUDFLARE_WORKERS_SUBDOMAIN` bypasses the lookup
|
|
709
|
+
// entirely when the operator already knows the value.
|
|
710
|
+
const getAccountSubdomain = yield* cachedFunction((accountId) => Effect.gen(function* () {
|
|
711
|
+
const configured = yield* CLOUDFLARE_WORKERS_SUBDOMAIN;
|
|
712
|
+
if (Option.isSome(configured) && configured.value !== "") {
|
|
713
|
+
if (!WORKERS_SUBDOMAIN_PATTERN.test(configured.value)) {
|
|
714
|
+
return yield* Effect.die(new Error(`Invalid CLOUDFLARE_WORKERS_SUBDOMAIN "${configured.value}": ` +
|
|
715
|
+
"expected a workers.dev subdomain label (lowercase letters, digits, hyphens)"));
|
|
716
|
+
}
|
|
717
|
+
return configured.value;
|
|
718
|
+
}
|
|
719
|
+
const result = yield* workers.getSubdomain({ accountId });
|
|
720
|
+
return result.subdomain;
|
|
721
|
+
}));
|
|
722
|
+
// Converge the script's workers.dev settings via `POST /subdomain`.
|
|
723
|
+
// The two toggles are independent on the Cloudflare API: `enabled`
|
|
724
|
+
// drives the stable `<name>.<account>.workers.dev` URL and
|
|
725
|
+
// `previews_enabled` the per-version preview URLs.
|
|
726
|
+
const setWorkerSubdomain = Effect.fn(function* (name, desired) {
|
|
344
727
|
const { accountId } = yield* yield* CloudflareEnvironment;
|
|
345
728
|
return yield* workers.createScriptSubdomain({
|
|
346
729
|
accountId,
|
|
347
730
|
scriptName: name,
|
|
348
|
-
enabled,
|
|
349
|
-
previewsEnabled:
|
|
731
|
+
enabled: desired.enabled,
|
|
732
|
+
previewsEnabled: desired.previewsEnabled,
|
|
350
733
|
});
|
|
351
734
|
});
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
//
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
735
|
+
const normalizeCrons = (crons) => Array.from(new Set(crons ?? []));
|
|
736
|
+
const hasSelfUrlBinding = (bindings) => bindings.some((b) => (b.data.bindings ?? []).some((item) => item.type === "self_url"));
|
|
737
|
+
// Resolve the URL this Worker will be served at — the same formula that
|
|
738
|
+
// produces the `url` attribute (first custom domain in user order, else
|
|
739
|
+
// the workers.dev URL) — usable BEFORE the script upload, so `Worker.URL`
|
|
740
|
+
// bindings can be lowered into plain_text bindings and VITE_*-prefixed
|
|
741
|
+
// env entries can be inlined into the client bundle at build time.
|
|
742
|
+
const resolveSelfUrl = Effect.fn(function* (name, props, accountId) {
|
|
743
|
+
const domain = yield* resolveWorkerDomain(props.domain);
|
|
744
|
+
if (domain) {
|
|
745
|
+
return `https://${domain.name}`;
|
|
358
746
|
}
|
|
359
|
-
|
|
360
|
-
return
|
|
747
|
+
if (!resolveWorkersDev(props.workersDev).enabled) {
|
|
748
|
+
return yield* Effect.die(`Worker "${name}" binds its own URL (Worker.URL) but has none: the stable workers.dev URL is disabled (workersDev) and no custom domain is configured.`);
|
|
361
749
|
}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
? []
|
|
365
|
-
: Array.from(new Set((Array.isArray(domain) ? domain : [domain]).map(toPunycode)));
|
|
366
|
-
const normalizeCrons = (crons) => Array.from(new Set(crons ?? []));
|
|
750
|
+
return `https://${name}.${yield* getAccountSubdomain(accountId)}.workers.dev`;
|
|
751
|
+
});
|
|
367
752
|
const getWorkerCrons = Effect.fn(function* (scriptName) {
|
|
368
753
|
const { accountId } = yield* yield* CloudflareEnvironment;
|
|
369
754
|
return yield* workers
|
|
@@ -519,6 +904,227 @@ export const LiveWorkerProvider = () => Provider.effect(Worker, Effect.gen(funct
|
|
|
519
904
|
});
|
|
520
905
|
return applied;
|
|
521
906
|
});
|
|
907
|
+
// The preview URL of the Worker's *current* version:
|
|
908
|
+
// `https://<version-prefix>-<name>.<subdomain>.workers.dev`, where the
|
|
909
|
+
// prefix is the first 8 characters of the version id (read from the
|
|
910
|
+
// latest deployment's majority version). Resolves to `undefined` when
|
|
911
|
+
// no deployment is visible yet — callers treat the preview URL as
|
|
912
|
+
// best-effort. Only consulted in previews-only mode
|
|
913
|
+
// (`workersDev: { enabled: false, previewsEnabled: true }`), where it
|
|
914
|
+
// is the Worker's only workers.dev surface.
|
|
915
|
+
const getPreviewUrl = Effect.fn(function* (scriptName, accountSubdomain) {
|
|
916
|
+
const { accountId } = yield* yield* CloudflareEnvironment;
|
|
917
|
+
const deployments = yield* workers
|
|
918
|
+
.listScriptDeployments({ accountId, scriptName })
|
|
919
|
+
.pipe(Effect.map((response) => response.deployments ?? []), Effect.catch(() => Effect.succeed([])));
|
|
920
|
+
const latest = [...deployments].sort((a, b) => b.createdOn.localeCompare(a.createdOn))[0];
|
|
921
|
+
const version = latest?.versions?.reduce((max, candidate) => max === undefined || candidate.percentage > max.percentage
|
|
922
|
+
? candidate
|
|
923
|
+
: max, undefined);
|
|
924
|
+
if (!version?.versionId)
|
|
925
|
+
return undefined;
|
|
926
|
+
return `https://${version.versionId.slice(0, 8)}-${scriptName}.${accountSubdomain}.workers.dev`;
|
|
927
|
+
});
|
|
928
|
+
/**
|
|
929
|
+
* Assemble every URL a deployed, script-owning Worker serves at, most
|
|
930
|
+
* significant first — the first entry becomes the `url` attribute:
|
|
931
|
+
*
|
|
932
|
+
* 1. `https://<domain.name>` — the canonical custom domain
|
|
933
|
+
* 2. aliases, in declared order
|
|
934
|
+
* 3. the stable workers.dev URL (`workersDev.enabled`)
|
|
935
|
+
* 4. preview URLs of the version this deploy uploaded (gradual
|
|
936
|
+
* rollouts), or — in previews-only mode — the current version's
|
|
937
|
+
* preview URL, which is then the only workers.dev surface
|
|
938
|
+
*
|
|
939
|
+
* Redirect hostnames never appear: they don't serve the Worker.
|
|
940
|
+
* (Version workers don't use this — their `urls` are their preview
|
|
941
|
+
* URLs, assembled in `putWorkerVersion`.)
|
|
942
|
+
*/
|
|
943
|
+
const computeWorkerUrls = Effect.fn(function* (params) {
|
|
944
|
+
const { workersDev, domain, scriptName } = params;
|
|
945
|
+
const urls = [];
|
|
946
|
+
if (domain) {
|
|
947
|
+
urls.push(`https://${domain.name}`, ...domain.aliases.map((hostname) => `https://${hostname}`));
|
|
948
|
+
}
|
|
949
|
+
if (workersDev.enabled || workersDev.previewsEnabled) {
|
|
950
|
+
const { accountId } = yield* yield* CloudflareEnvironment;
|
|
951
|
+
const accountSubdomain = yield* getAccountSubdomain(accountId);
|
|
952
|
+
if (workersDev.enabled) {
|
|
953
|
+
urls.push(`https://${scriptName}.${accountSubdomain}.workers.dev`);
|
|
954
|
+
}
|
|
955
|
+
if (workersDev.previewsEnabled) {
|
|
956
|
+
if (params.uploadedVersionId !== undefined) {
|
|
957
|
+
if (params.uploadedVersionAlias !== undefined) {
|
|
958
|
+
urls.push(`https://${params.uploadedVersionAlias}-${scriptName}.${accountSubdomain}.workers.dev`);
|
|
959
|
+
}
|
|
960
|
+
urls.push(`https://${params.uploadedVersionId.split("-")[0]}-${scriptName}.${accountSubdomain}.workers.dev`);
|
|
961
|
+
}
|
|
962
|
+
else if (!workersDev.enabled) {
|
|
963
|
+
const previewUrl = yield* getPreviewUrl(scriptName, accountSubdomain);
|
|
964
|
+
if (previewUrl)
|
|
965
|
+
urls.push(previewUrl);
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
return urls;
|
|
970
|
+
});
|
|
971
|
+
/**
|
|
972
|
+
* Converge the redirect rules for a Worker's `domain.redirects` in
|
|
973
|
+
* each affected zone's `http_request_dynamic_redirect` phase
|
|
974
|
+
* entrypoint. Rules are identified by a
|
|
975
|
+
* `alchemy:worker:<script>:redirect:<host>` description; only our own
|
|
976
|
+
* rules are added/removed — every other rule in the shared entrypoint
|
|
977
|
+
* ruleset passes through untouched. Zones that previously held our
|
|
978
|
+
* rules but no longer should (removed redirects, removed domain) are
|
|
979
|
+
* cleaned the same way.
|
|
980
|
+
*/
|
|
981
|
+
const reconcileRedirectRules = Effect.fn(function* (params) {
|
|
982
|
+
const prefix = `alchemy:worker:${params.scriptName}:redirect:`;
|
|
983
|
+
const desired = params.domain?.redirects ?? [];
|
|
984
|
+
const targetName = params.domain?.name;
|
|
985
|
+
// Zones to touch: every zone hosting a desired redirect, plus every
|
|
986
|
+
// zone that hosted one before (so removals converge). Hostnames
|
|
987
|
+
// whose zone we can't resolve (e.g. the domain was already
|
|
988
|
+
// detached) are skipped — their rules become unreachable dead
|
|
989
|
+
// config at worst, never a failed destroy.
|
|
990
|
+
const zoneIds = new Set();
|
|
991
|
+
for (const hostname of [...desired, ...params.previousRedirects]) {
|
|
992
|
+
const zoneId = params.zoneIdByHostname.get(hostname);
|
|
993
|
+
if (zoneId)
|
|
994
|
+
zoneIds.add(zoneId);
|
|
995
|
+
}
|
|
996
|
+
if (zoneIds.size === 0)
|
|
997
|
+
return;
|
|
998
|
+
for (const zoneId of zoneIds) {
|
|
999
|
+
const entrypoint = yield* rulesets
|
|
1000
|
+
.getPhasForZone({
|
|
1001
|
+
zoneId,
|
|
1002
|
+
rulesetPhase: "http_request_dynamic_redirect",
|
|
1003
|
+
})
|
|
1004
|
+
.pipe(Effect.catch(() => Effect.succeed(undefined)));
|
|
1005
|
+
const existingRules = entrypoint?.rules ?? [];
|
|
1006
|
+
const ourDesired = desired
|
|
1007
|
+
.filter((hostname) => params.zoneIdByHostname.get(hostname) === zoneId)
|
|
1008
|
+
.map((hostname) => ({
|
|
1009
|
+
action: "redirect",
|
|
1010
|
+
expression: `http.host eq "${hostname}"`,
|
|
1011
|
+
description: `${prefix}${hostname}`,
|
|
1012
|
+
enabled: true,
|
|
1013
|
+
actionParameters: {
|
|
1014
|
+
fromValue: {
|
|
1015
|
+
statusCode: 301,
|
|
1016
|
+
preserveQueryString: true,
|
|
1017
|
+
targetUrl: {
|
|
1018
|
+
expression: `concat("https://${targetName}", http.request.uri.path)`,
|
|
1019
|
+
},
|
|
1020
|
+
},
|
|
1021
|
+
},
|
|
1022
|
+
}));
|
|
1023
|
+
const isOurs = (rule) => (rule.description ?? "").startsWith(prefix);
|
|
1024
|
+
const ourExisting = existingRules.filter(isOurs);
|
|
1025
|
+
const converged = ourExisting.length === ourDesired.length &&
|
|
1026
|
+
ourDesired.every((rule) => ourExisting.some((existing) => existing.description === rule.description &&
|
|
1027
|
+
existing.expression === rule.expression));
|
|
1028
|
+
if (converged)
|
|
1029
|
+
continue;
|
|
1030
|
+
// Pass foreign rules through untouched (minus the read-only
|
|
1031
|
+
// fields the PUT schema doesn't accept) and replace our own set.
|
|
1032
|
+
const foreign = existingRules
|
|
1033
|
+
.filter((rule) => !isOurs(rule))
|
|
1034
|
+
.map((rule) => {
|
|
1035
|
+
const { lastUpdated: _lastUpdated, version: _version, ...rest } = rule;
|
|
1036
|
+
return rest;
|
|
1037
|
+
});
|
|
1038
|
+
yield* rulesets.putPhasForZone({
|
|
1039
|
+
zoneId,
|
|
1040
|
+
rulesetPhase: "http_request_dynamic_redirect",
|
|
1041
|
+
rules: [...foreign, ...ourDesired],
|
|
1042
|
+
});
|
|
1043
|
+
}
|
|
1044
|
+
});
|
|
1045
|
+
/**
|
|
1046
|
+
* Converge the version-affinity transform rules (`version.affinity`)
|
|
1047
|
+
* in each serving zone's `http_request_late_transform` phase
|
|
1048
|
+
* entrypoint: `rewrite` rules that fill the
|
|
1049
|
+
* `Cloudflare-Workers-Version-Key` header from the configured source,
|
|
1050
|
+
* scoped to the Worker's hostnames in that zone. Rules are identified
|
|
1051
|
+
* by an `alchemy:worker:<script>:affinity` description prefix; only
|
|
1052
|
+
* our own rules are added/updated/removed — every other rule in the
|
|
1053
|
+
* shared entrypoint passes through untouched. Zones that previously
|
|
1054
|
+
* held our rules but no longer should (affinity removed, zone no
|
|
1055
|
+
* longer served) are cleaned the same way. Returns the zone ids now
|
|
1056
|
+
* holding rules, for persistence in `affinityZoneIds`.
|
|
1057
|
+
*/
|
|
1058
|
+
const reconcileAffinityRules = Effect.fn(function* (params) {
|
|
1059
|
+
const prefix = affinityRulePrefix(params.scriptName);
|
|
1060
|
+
const zoneIds = new Set([
|
|
1061
|
+
...(params.desired?.hostsByZone.keys() ?? []),
|
|
1062
|
+
...params.previousZoneIds,
|
|
1063
|
+
]);
|
|
1064
|
+
for (const zoneId of zoneIds) {
|
|
1065
|
+
const hosts = params.desired?.hostsByZone.get(zoneId);
|
|
1066
|
+
const ourDesired = params.desired !== undefined && hosts !== undefined
|
|
1067
|
+
? buildAffinityZoneRules(params.scriptName, params.desired.affinity, hosts)
|
|
1068
|
+
: [];
|
|
1069
|
+
const entrypoint = yield* rulesets
|
|
1070
|
+
.getPhasForZone({
|
|
1071
|
+
zoneId,
|
|
1072
|
+
rulesetPhase: "http_request_late_transform",
|
|
1073
|
+
})
|
|
1074
|
+
.pipe(Effect.catch(() => Effect.succeed(undefined)));
|
|
1075
|
+
const existingRules = entrypoint?.rules ?? [];
|
|
1076
|
+
const isOurs = (rule) => (rule.description ?? "").startsWith(prefix);
|
|
1077
|
+
// The header-value expression of an existing rewrite rule, for
|
|
1078
|
+
// convergence comparison. `headers` is an untyped wire record:
|
|
1079
|
+
// `{ "<Header-Name>": { operation, expression | value } }`.
|
|
1080
|
+
const existingValue = (rule) => {
|
|
1081
|
+
const headers = "actionParameters" in rule
|
|
1082
|
+
? rule.actionParameters?.headers
|
|
1083
|
+
: undefined;
|
|
1084
|
+
const header = headers?.[AFFINITY_HEADER];
|
|
1085
|
+
return typeof header?.expression === "string"
|
|
1086
|
+
? header.expression
|
|
1087
|
+
: undefined;
|
|
1088
|
+
};
|
|
1089
|
+
const ourExisting = existingRules.filter(isOurs);
|
|
1090
|
+
const converged = ourExisting.length === ourDesired.length &&
|
|
1091
|
+
ourDesired.every((rule) => ourExisting.some((existing) => existing.description === rule.description &&
|
|
1092
|
+
existing.expression === rule.expression &&
|
|
1093
|
+
existingValue(existing) === rule.value));
|
|
1094
|
+
if (converged)
|
|
1095
|
+
continue;
|
|
1096
|
+
// Pass foreign rules through untouched (minus the read-only
|
|
1097
|
+
// fields the PUT schema doesn't accept) and replace our own set.
|
|
1098
|
+
const foreign = existingRules
|
|
1099
|
+
.filter((rule) => !isOurs(rule))
|
|
1100
|
+
.map((rule) => {
|
|
1101
|
+
const { lastUpdated: _lastUpdated, version: _version, ...rest } = rule;
|
|
1102
|
+
return rest;
|
|
1103
|
+
});
|
|
1104
|
+
yield* rulesets.putPhasForZone({
|
|
1105
|
+
zoneId,
|
|
1106
|
+
rulesetPhase: "http_request_late_transform",
|
|
1107
|
+
rules: [
|
|
1108
|
+
...foreign,
|
|
1109
|
+
...ourDesired.map((rule) => ({
|
|
1110
|
+
action: "rewrite",
|
|
1111
|
+
description: rule.description,
|
|
1112
|
+
enabled: true,
|
|
1113
|
+
expression: rule.expression,
|
|
1114
|
+
actionParameters: {
|
|
1115
|
+
headers: {
|
|
1116
|
+
[AFFINITY_HEADER]: {
|
|
1117
|
+
operation: "set",
|
|
1118
|
+
expression: rule.value,
|
|
1119
|
+
},
|
|
1120
|
+
},
|
|
1121
|
+
},
|
|
1122
|
+
})),
|
|
1123
|
+
],
|
|
1124
|
+
});
|
|
1125
|
+
}
|
|
1126
|
+
return [...(params.desired?.hostsByZone.keys() ?? [])].sort();
|
|
1127
|
+
});
|
|
522
1128
|
const routeKey = (route) => `${route.zoneId}:${route.pattern}`;
|
|
523
1129
|
// Derive a concrete hostname inside the zone from a route pattern so
|
|
524
1130
|
// zone inference can walk the DNS label hierarchy. A wildcard label
|
|
@@ -572,19 +1178,30 @@ export const LiveWorkerProvider = () => Provider.effect(Worker, Effect.gen(funct
|
|
|
572
1178
|
}
|
|
573
1179
|
const routesByZone = Effect.all(uniqueZoneIds.map((zoneId) => workers.listRoutes({ zoneId }).pipe(Effect.map((response) => (response.result ?? []).flatMap((route) => route.id && route.pattern && route.script === scriptName
|
|
574
1180
|
? [{ id: route.id, pattern: route.pattern, zoneId }]
|
|
575
|
-
: [])), Effect.catch(() => Effect.succeed([])))),
|
|
1181
|
+
: [])), Effect.catch(() => Effect.succeed([])))),
|
|
1182
|
+
// Bounded: this issues one request per zone, so a Worker with routes
|
|
1183
|
+
// spread across many zones would otherwise burst the account's whole
|
|
1184
|
+
// API budget in a single tick (#926).
|
|
1185
|
+
{ concurrency: WORKER_ROUTE_LIST_CONCURRENCY });
|
|
576
1186
|
return Effect.map(routesByZone, (routes) => routes.flat());
|
|
577
1187
|
};
|
|
578
|
-
// Observe
|
|
579
|
-
//
|
|
580
|
-
//
|
|
581
|
-
//
|
|
582
|
-
//
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
1188
|
+
// Observe the routes attached to `scriptName` in the zones Alchemy
|
|
1189
|
+
// already associates with this Worker.
|
|
1190
|
+
//
|
|
1191
|
+
// This used to enumerate every zone on the account and then list routes
|
|
1192
|
+
// in each one. Routes are zone-scoped with no account-level enumeration
|
|
1193
|
+
// API, so that costs O(zones) requests on *every* Worker read — on an
|
|
1194
|
+
// account with a few hundred zones a couple of plan/deploy cycles
|
|
1195
|
+
// exhausts Cloudflare's per-user API budget and unrelated calls start
|
|
1196
|
+
// coming back 429 / code 971 (#926).
|
|
1197
|
+
//
|
|
1198
|
+
// `reconcileRoutes` already scopes itself to the zones implied by
|
|
1199
|
+
// `desired ∪ previous` rather than sweeping the account, so `read` now
|
|
1200
|
+
// uses the same bounded zone set: the zones of the routes already
|
|
1201
|
+
// recorded in state. The blind spot this introduces — a route added out
|
|
1202
|
+
// of band in a zone this Worker has never had a route in — is the one
|
|
1203
|
+
// `reconcileRoutes` already has.
|
|
1204
|
+
const readWorkerRoutes = (scriptName, knownRoutes) => listWorkerRoutesInZones(scriptName, (knownRoutes ?? []).map((route) => route.zoneId));
|
|
588
1205
|
// Converge the zone routes attached to `scriptName` to `desired`.
|
|
589
1206
|
// Observed cloud state (not `previous`) is the diff baseline —
|
|
590
1207
|
// `previous` only contributes zone ids so routes moved out of a zone
|
|
@@ -800,7 +1417,7 @@ export const LiveWorkerProvider = () => Provider.effect(Worker, Effect.gen(funct
|
|
|
800
1417
|
extraOptions: props.build,
|
|
801
1418
|
})).pipe(Artifacts.cached("build"));
|
|
802
1419
|
const hashScript = (script) => Effect.sync(() => crypto.createHash("sha256").update(script).digest("hex"));
|
|
803
|
-
const viteBuild = Effect.fn(function* (props) {
|
|
1420
|
+
const viteBuild = Effect.fn(function* (props, selfUrl) {
|
|
804
1421
|
const compatibility = getCompatibility(props);
|
|
805
1422
|
// Loaded lazily: `./Vite.ts` pulls in `@distilled.cloud/cloudflare-vite-plugin`
|
|
806
1423
|
// (~0.5s), which is only needed for vite-based workers at build time —
|
|
@@ -814,18 +1431,37 @@ export const LiveWorkerProvider = () => Provider.effect(Worker, Effect.gen(funct
|
|
|
814
1431
|
: Redacted.isRedacted(value) &&
|
|
815
1432
|
typeof Redacted.value(value) === "string"
|
|
816
1433
|
? Redacted.value(value)
|
|
817
|
-
: //
|
|
818
|
-
//
|
|
819
|
-
//
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
1434
|
+
: // `Worker.URL` (bare tag or called) — resolved to
|
|
1435
|
+
// this Worker's own URL. The bare tag is
|
|
1436
|
+
// Effect-shaped, so check before `Effect.isEffect`.
|
|
1437
|
+
isSelfUrl(value)
|
|
1438
|
+
? selfUrl
|
|
1439
|
+
: // A `WorkerLoader` is a real Effect that also carries
|
|
1440
|
+
// the `~alchemy/Kind` marker — it is a binding, not a
|
|
1441
|
+
// runnable env value. Check it before `Effect.isEffect`
|
|
1442
|
+
// so we don't execute it as an inlined env entry.
|
|
1443
|
+
isWorkerLoader(value)
|
|
1444
|
+
? undefined
|
|
1445
|
+
: // A `Cloudflare.Container` declaration is likewise
|
|
1446
|
+
// Effect-shaped but is a binding (DO namespace +
|
|
1447
|
+
// ContainerApplication) — yielding it would resolve
|
|
1448
|
+
// the started-instance tag, which only exists inside
|
|
1449
|
+
// a Durable Object (#997).
|
|
1450
|
+
isContainerDecl(value)
|
|
1451
|
+
? undefined
|
|
1452
|
+
: Effect.isEffect(value)
|
|
1453
|
+
? yield* value
|
|
1454
|
+
: undefined,
|
|
826
1455
|
];
|
|
827
1456
|
})))).filter(([_, value]) => value !== undefined)), {
|
|
828
|
-
|
|
1457
|
+
// A relative `vite.main` is documented to resolve from the Vite
|
|
1458
|
+
// root. The rolldown plugin resolves the worker entry with no
|
|
1459
|
+
// importer (i.e. against `process.cwd()`), which breaks when the
|
|
1460
|
+
// deploy runs from a different directory (e.g. a monorepo infra
|
|
1461
|
+
// package) — absolutize before handing it over (#796).
|
|
1462
|
+
main: props.vite?.main
|
|
1463
|
+
? path.resolve(props.vite.rootDir ?? process.cwd(), props.vite.main)
|
|
1464
|
+
: undefined,
|
|
829
1465
|
compatibilityDate: compatibility.date,
|
|
830
1466
|
compatibilityFlags: compatibility.flags,
|
|
831
1467
|
viteEnvironments: props.vite?.viteEnvironments,
|
|
@@ -888,7 +1524,25 @@ export const LiveWorkerProvider = () => Provider.effect(Worker, Effect.gen(funct
|
|
|
888
1524
|
};
|
|
889
1525
|
}
|
|
890
1526
|
if (props.vite) {
|
|
891
|
-
return yield* viteBuild(props);
|
|
1527
|
+
return yield* viteBuild(props, opts.selfUrl);
|
|
1528
|
+
}
|
|
1529
|
+
// Assets-only Worker: no entry module at all. The script PUT goes
|
|
1530
|
+
// out with no modules and no main_module — Cloudflare's asset
|
|
1531
|
+
// layer serves every request and applies `notFoundHandling`
|
|
1532
|
+
// (including SPA fallback) itself, exactly like Wrangler's
|
|
1533
|
+
// assets-only deploys.
|
|
1534
|
+
if (props.main === undefined) {
|
|
1535
|
+
if (!props.assets) {
|
|
1536
|
+
return yield* Effect.die(new Error(`Worker "${id}" has no main, script, or assets. Provide an entry module (main / script) or an assets directory to deploy an assets-only Worker.`));
|
|
1537
|
+
}
|
|
1538
|
+
return {
|
|
1539
|
+
assets: opts.skipAssetsRead
|
|
1540
|
+
? undefined
|
|
1541
|
+
: yield* prepareAssets(props.assets),
|
|
1542
|
+
bundle: undefined,
|
|
1543
|
+
input: undefined,
|
|
1544
|
+
additionalWorkspaces: undefined,
|
|
1545
|
+
};
|
|
892
1546
|
}
|
|
893
1547
|
const [assets, bundle] = yield* Effect.all([
|
|
894
1548
|
opts.skipAssetsRead
|
|
@@ -923,6 +1577,428 @@ export const LiveWorkerProvider = () => Provider.effect(Worker, Effect.gen(funct
|
|
|
923
1577
|
const { directory, hash, ...config } = assets;
|
|
924
1578
|
return { directory, config, hash, skip: hash === output?.hash?.assets };
|
|
925
1579
|
};
|
|
1580
|
+
/**
|
|
1581
|
+
* Append the standard Alchemy runtime bindings plus the user's `env`
|
|
1582
|
+
* entries (routed by shape: `Redacted` → secret_text, string →
|
|
1583
|
+
* plain_text, everything else → json) to a metadata binding list.
|
|
1584
|
+
* Shared between the full script upload and the version upload.
|
|
1585
|
+
*/
|
|
1586
|
+
const appendAlchemyAndEnvBindings = (metadataBindings, news, accountId, workerName) => {
|
|
1587
|
+
metadataBindings.push({
|
|
1588
|
+
type: "plain_text",
|
|
1589
|
+
name: "ALCHEMY_PHASE",
|
|
1590
|
+
text: "runtime",
|
|
1591
|
+
}, {
|
|
1592
|
+
type: "plain_text",
|
|
1593
|
+
name: "ALCHEMY_WORKER_NAME",
|
|
1594
|
+
text: workerName,
|
|
1595
|
+
}, {
|
|
1596
|
+
type: "plain_text",
|
|
1597
|
+
name: "ALCHEMY_STACK_NAME",
|
|
1598
|
+
text: stack.name,
|
|
1599
|
+
}, {
|
|
1600
|
+
type: "plain_text",
|
|
1601
|
+
name: "ALCHEMY_STAGE",
|
|
1602
|
+
text: stack.stage,
|
|
1603
|
+
}, {
|
|
1604
|
+
type: "plain_text",
|
|
1605
|
+
name: "ALCHEMY_CLOUDFLARE_ACCOUNT_ID",
|
|
1606
|
+
text: accountId,
|
|
1607
|
+
});
|
|
1608
|
+
// Add environment variables as metadata bindings
|
|
1609
|
+
if (news.env) {
|
|
1610
|
+
for (const [key, value] of Object.entries(news.env)) {
|
|
1611
|
+
if (value === undefined)
|
|
1612
|
+
continue;
|
|
1613
|
+
if (metadataBindings.some((b) => b.name === key))
|
|
1614
|
+
continue;
|
|
1615
|
+
if (Redacted.isRedacted(value)) {
|
|
1616
|
+
const unredacted = Redacted.value(value);
|
|
1617
|
+
metadataBindings.push({
|
|
1618
|
+
type: "secret_text",
|
|
1619
|
+
name: key,
|
|
1620
|
+
text: typeof unredacted === "string"
|
|
1621
|
+
? unredacted
|
|
1622
|
+
: JSON.stringify(unredacted),
|
|
1623
|
+
});
|
|
1624
|
+
}
|
|
1625
|
+
else if (typeof value === "string") {
|
|
1626
|
+
metadataBindings.push({
|
|
1627
|
+
type: "plain_text",
|
|
1628
|
+
name: key,
|
|
1629
|
+
text: value,
|
|
1630
|
+
});
|
|
1631
|
+
}
|
|
1632
|
+
else {
|
|
1633
|
+
metadataBindings.push({
|
|
1634
|
+
type: "json",
|
|
1635
|
+
name: key,
|
|
1636
|
+
json: value,
|
|
1637
|
+
});
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
};
|
|
1642
|
+
/**
|
|
1643
|
+
* Create a deployment routing `traffic`% to `versionId`, with the
|
|
1644
|
+
* remainder staying on the currently-live version (the
|
|
1645
|
+
* highest-percentage version of the script's latest deployment).
|
|
1646
|
+
* `traffic >= 100` — or a script with no other live version to split
|
|
1647
|
+
* against — deploys the new version at 100%.
|
|
1648
|
+
*/
|
|
1649
|
+
const deployVersionTraffic = Effect.fn(function* (params) {
|
|
1650
|
+
const { accountId, scriptName, versionId, traffic } = params;
|
|
1651
|
+
const split = yield* Effect.gen(function* () {
|
|
1652
|
+
if (traffic >= 100) {
|
|
1653
|
+
return [{ versionId, percentage: 100 }];
|
|
1654
|
+
}
|
|
1655
|
+
const { deployments } = yield* workers.listScriptDeployments({
|
|
1656
|
+
accountId,
|
|
1657
|
+
scriptName,
|
|
1658
|
+
});
|
|
1659
|
+
// Deployments are returned newest-first; the live version is the
|
|
1660
|
+
// highest-percentage version of the most recent deployment.
|
|
1661
|
+
const stable = deployments[0]?.versions
|
|
1662
|
+
.filter((v) => v.versionId !== versionId)
|
|
1663
|
+
.sort((a, b) => b.percentage - a.percentage)[0];
|
|
1664
|
+
if (!stable) {
|
|
1665
|
+
// Nothing to split against (first deployment of this script).
|
|
1666
|
+
return [{ versionId, percentage: 100 }];
|
|
1667
|
+
}
|
|
1668
|
+
return [
|
|
1669
|
+
{ versionId, percentage: traffic },
|
|
1670
|
+
{ versionId: stable.versionId, percentage: 100 - traffic },
|
|
1671
|
+
];
|
|
1672
|
+
});
|
|
1673
|
+
const deployment = yield* workers.createScriptDeployment({
|
|
1674
|
+
accountId,
|
|
1675
|
+
scriptName,
|
|
1676
|
+
strategy: "percentage",
|
|
1677
|
+
versions: split,
|
|
1678
|
+
annotations: params.message
|
|
1679
|
+
? { workersMessage: params.message }
|
|
1680
|
+
: undefined,
|
|
1681
|
+
});
|
|
1682
|
+
return deployment.id;
|
|
1683
|
+
});
|
|
1684
|
+
/**
|
|
1685
|
+
* Resolve the preview-URL alias for a version worker: the
|
|
1686
|
+
* user-provided `version.alias` (validated against Cloudflare's
|
|
1687
|
+
* naming rules), or an alias derived from the stack, stage, and
|
|
1688
|
+
* logical id — stable across deploys, so the aliased preview URL
|
|
1689
|
+
* (`<alias>-<script>.<subdomain>.workers.dev`) is a durable link
|
|
1690
|
+
* that always points at the latest uploaded version. Returns
|
|
1691
|
+
* `undefined` when the parent's script name leaves no room in the
|
|
1692
|
+
* 63-character DNS label and no explicit alias was given.
|
|
1693
|
+
*/
|
|
1694
|
+
const resolveVersionAlias = Effect.fn(function* (id, news, parentName) {
|
|
1695
|
+
// `<alias>-<script name>` is a single DNS label (≤ 63 chars).
|
|
1696
|
+
const budget = 63 - parentName.length - 1;
|
|
1697
|
+
const userAlias = news.version?.alias;
|
|
1698
|
+
if (userAlias !== undefined) {
|
|
1699
|
+
if (!/^[a-z][a-z0-9-]*$/.test(userAlias)) {
|
|
1700
|
+
return yield* Effect.fail(new WorkerVersionConfigError({
|
|
1701
|
+
message: `version.alias '${userAlias}' is invalid: aliases must start with a lowercase letter and contain only lowercase letters, digits, and dashes.`,
|
|
1702
|
+
}));
|
|
1703
|
+
}
|
|
1704
|
+
if (userAlias.length > budget) {
|
|
1705
|
+
return yield* Effect.fail(new WorkerVersionConfigError({
|
|
1706
|
+
message: `version.alias '${userAlias}' is too long: '<alias>-${parentName}' must fit in a 63-character DNS label, leaving ${Math.max(budget, 0)} characters for the alias.`,
|
|
1707
|
+
}));
|
|
1708
|
+
}
|
|
1709
|
+
return userAlias;
|
|
1710
|
+
}
|
|
1711
|
+
if (budget < 4) {
|
|
1712
|
+
return undefined;
|
|
1713
|
+
}
|
|
1714
|
+
// Deterministic per (stack, stage, id) — survives updates AND
|
|
1715
|
+
// replacements so the aliased preview URL never moves. A short
|
|
1716
|
+
// hash disambiguates different stacks/stages whose readable
|
|
1717
|
+
// prefixes collide after truncation.
|
|
1718
|
+
const hash = yield* Effect.sync(() => crypto
|
|
1719
|
+
.createHash("sha256")
|
|
1720
|
+
.update(`${stack.name}/${stack.stage}/${id}`)
|
|
1721
|
+
.digest("hex")
|
|
1722
|
+
.slice(0, 6));
|
|
1723
|
+
const readable = `${stack.stage}-${id}`
|
|
1724
|
+
.toLowerCase()
|
|
1725
|
+
.replace(/[^a-z0-9-]+/g, "-")
|
|
1726
|
+
.replace(/^-+|-+$/g, "");
|
|
1727
|
+
const alias = [readable, hash]
|
|
1728
|
+
.filter((part) => part.length > 0)
|
|
1729
|
+
.join("-")
|
|
1730
|
+
.slice(0, budget)
|
|
1731
|
+
.replace(/-+$/, "");
|
|
1732
|
+
return /^[a-z]/.test(alias) ? alias : `v${alias}`.slice(0, budget);
|
|
1733
|
+
});
|
|
1734
|
+
/**
|
|
1735
|
+
* The parent's workers.dev preview settings + account subdomain,
|
|
1736
|
+
* fetched once per version reconcile. Preview URLs (aliased and
|
|
1737
|
+
* versioned) only serve when the parent has previews enabled.
|
|
1738
|
+
*/
|
|
1739
|
+
const resolveVersionPreviewContext = Effect.fn(function* (accountId, scriptName) {
|
|
1740
|
+
const subdomain = yield* workers
|
|
1741
|
+
.getScriptSubdomain({ accountId, scriptName })
|
|
1742
|
+
.pipe(Effect.orElseSucceed(() => ({
|
|
1743
|
+
enabled: false,
|
|
1744
|
+
previewsEnabled: false,
|
|
1745
|
+
})));
|
|
1746
|
+
const accountSubdomain = yield* getAccountSubdomain(accountId);
|
|
1747
|
+
return {
|
|
1748
|
+
previewsEnabled: subdomain.previewsEnabled === true,
|
|
1749
|
+
accountSubdomain,
|
|
1750
|
+
};
|
|
1751
|
+
});
|
|
1752
|
+
/**
|
|
1753
|
+
* Reject props that can't ride along on a *version worker*
|
|
1754
|
+
* (`version.parent` set). A version carries only code, bindings, and
|
|
1755
|
+
* compatibility settings; everything script-level belongs to the
|
|
1756
|
+
* parent and must not be mutated from a version resource. Locally
|
|
1757
|
+
* hosted Durable Object / Workflow classes are rejected because their
|
|
1758
|
+
* migrations would apply to the parent script when the version
|
|
1759
|
+
* deploys.
|
|
1760
|
+
*/
|
|
1761
|
+
const validateVersionWorkerProps = Effect.fn(function* (news, bindings, parentName) {
|
|
1762
|
+
const forbidden = [
|
|
1763
|
+
["name", news.name],
|
|
1764
|
+
["assets", news.assets],
|
|
1765
|
+
["namespace", news.namespace],
|
|
1766
|
+
["crons", news.crons],
|
|
1767
|
+
["domain", news.domain],
|
|
1768
|
+
["routes", news.routes],
|
|
1769
|
+
["tags", news.tags],
|
|
1770
|
+
["logpush", news.logpush],
|
|
1771
|
+
["observability", news.observability],
|
|
1772
|
+
["placement", news.placement],
|
|
1773
|
+
["limits", news.limits],
|
|
1774
|
+
["workersDev", news.workersDev],
|
|
1775
|
+
["vite", news.vite],
|
|
1776
|
+
].flatMap(([key, value]) => (value !== undefined ? [key] : []));
|
|
1777
|
+
if (forbidden.length > 0) {
|
|
1778
|
+
return yield* Effect.fail(new WorkerVersionConfigError({
|
|
1779
|
+
message: `version.parent uploads a version of '${parentName}' — script-level settings belong to the parent Worker and cannot be set here: ${forbidden.join(", ")}. ` +
|
|
1780
|
+
`Remove ${forbidden.length === 1 ? "this prop" : "these props"} or configure ${forbidden.length === 1 ? "it" : "them"} on the parent.`,
|
|
1781
|
+
}));
|
|
1782
|
+
}
|
|
1783
|
+
yield* validateTraffic(news.version?.traffic);
|
|
1784
|
+
const cronBindings = getCronBindings(bindings);
|
|
1785
|
+
if (cronBindings.length > 0) {
|
|
1786
|
+
return yield* Effect.fail(new WorkerVersionConfigError({
|
|
1787
|
+
message: `Cron Triggers are script-level settings and cannot be registered from a version of '${parentName}'. Configure crons on the parent Worker.`,
|
|
1788
|
+
}));
|
|
1789
|
+
}
|
|
1790
|
+
// Any locally-hosted DO class (a durable_object_namespace binding
|
|
1791
|
+
// without a foreign scriptName) or DO/Workflow export would require
|
|
1792
|
+
// migrations, which the versions API can't carry — and which would
|
|
1793
|
+
// mutate the parent's namespaces.
|
|
1794
|
+
const hostedClasses = getDurableObjectBindings(bindings, parentName);
|
|
1795
|
+
const exportedClasses = Object.keys(news.exports ?? {});
|
|
1796
|
+
if (hostedClasses.length > 0 || exportedClasses.length > 0) {
|
|
1797
|
+
return yield* Effect.fail(new WorkerVersionConfigError({
|
|
1798
|
+
message: `A version of '${parentName}' cannot host Durable Object or Workflow classes (${[
|
|
1799
|
+
...new Set([
|
|
1800
|
+
...hostedClasses.map((c) => c.className),
|
|
1801
|
+
...exportedClasses,
|
|
1802
|
+
]),
|
|
1803
|
+
].join(", ")}): class migrations apply to the parent script. Host the classes on the parent Worker and reference them cross-script instead.`,
|
|
1804
|
+
}));
|
|
1805
|
+
}
|
|
1806
|
+
});
|
|
1807
|
+
/**
|
|
1808
|
+
* Reconcile a *version worker*: upload this Worker's code + bindings
|
|
1809
|
+
* as an immutable version of the parent's script (no script of its
|
|
1810
|
+
* own), then optionally shift `version.traffic`% of the parent's
|
|
1811
|
+
* traffic to it. Idempotent in the reconciler sense: every run
|
|
1812
|
+
* converges cloud state to "the parent's script has a version with
|
|
1813
|
+
* exactly this content, receiving exactly this traffic" — re-running
|
|
1814
|
+
* with changed content simply uploads the next immutable version.
|
|
1815
|
+
*/
|
|
1816
|
+
const putWorkerVersion = Effect.fn(function* (id, news, bindings, session, output) {
|
|
1817
|
+
const { accountId } = yield* yield* CloudflareEnvironment;
|
|
1818
|
+
const version = news.version;
|
|
1819
|
+
const parentName = resolveVersionParentName(version);
|
|
1820
|
+
if (parentName === undefined) {
|
|
1821
|
+
return yield* Effect.fail(new WorkerVersionConfigError({
|
|
1822
|
+
message: `version.parent did not resolve to a Worker script name. Pass a Worker (e.g. \`yield* Cloudflare.Worker.ref(id, { stage })\`) or a literal script name.`,
|
|
1823
|
+
}));
|
|
1824
|
+
}
|
|
1825
|
+
yield* validateVersionWorkerProps(news, bindings, parentName);
|
|
1826
|
+
const traffic = version.traffic ?? 0;
|
|
1827
|
+
// Resolve the alias and preview context BEFORE the upload: the
|
|
1828
|
+
// aliased preview URL (`<alias>-<script>.<subdomain>.workers.dev`)
|
|
1829
|
+
// is deterministic ahead of time — unlike the versioned URL, whose
|
|
1830
|
+
// prefix comes from the server-assigned version id — which is what
|
|
1831
|
+
// lets a `Worker.URL` (self_url) binding be baked into the
|
|
1832
|
+
// version's own bindings.
|
|
1833
|
+
const alias = yield* resolveVersionAlias(id, news, parentName);
|
|
1834
|
+
const { previewsEnabled, accountSubdomain } = yield* resolveVersionPreviewContext(accountId, parentName);
|
|
1835
|
+
const aliasedUrl = alias !== undefined && previewsEnabled
|
|
1836
|
+
? `https://${alias}-${parentName}.${accountSubdomain}.workers.dev`
|
|
1837
|
+
: undefined;
|
|
1838
|
+
const selfUrl = hasSelfUrlBinding(bindings) ? aliasedUrl : undefined;
|
|
1839
|
+
if (hasSelfUrlBinding(bindings) && selfUrl === undefined) {
|
|
1840
|
+
return yield* Effect.fail(new WorkerVersionConfigError({
|
|
1841
|
+
message: previewsEnabled
|
|
1842
|
+
? `A version of '${parentName}' binds its own URL (Worker.URL), but no preview alias fits: '<alias>-${parentName}' must stay within a 63-character DNS label. Set a short version.alias or shorten the parent's name.`
|
|
1843
|
+
: `A version of '${parentName}' binds its own URL (Worker.URL), but the parent's workers.dev previews are disabled — a version's URL is its aliased preview URL. Enable the parent's workers.dev subdomain (url: true, the default).`,
|
|
1844
|
+
}));
|
|
1845
|
+
}
|
|
1846
|
+
yield* Effect.logInfo(`Cloudflare Worker version: preparing bundle for ${parentName} (from ${id})`);
|
|
1847
|
+
const { bundle, hash: preparedHash } = yield* prepareAssetsAndBundle(id, news, { skipAssetsRead: true });
|
|
1848
|
+
const metadataHash = yield* resolveWorkerMetadataHash({
|
|
1849
|
+
props: news,
|
|
1850
|
+
bindings,
|
|
1851
|
+
accountId,
|
|
1852
|
+
stack: { name: stack.name, stage: stack.stage },
|
|
1853
|
+
selfUrl,
|
|
1854
|
+
});
|
|
1855
|
+
const hash = {
|
|
1856
|
+
...preparedHash,
|
|
1857
|
+
metadata: metadataHash,
|
|
1858
|
+
};
|
|
1859
|
+
// Lower the `Worker.URL` sentinel into the aliased preview URL —
|
|
1860
|
+
// same lowering `putWorker` performs, with the alias standing in
|
|
1861
|
+
// for the script's own URL.
|
|
1862
|
+
const metadataBindings = bindings.flatMap((b) => (b.data.bindings ?? []).map((item) => item.type === "self_url"
|
|
1863
|
+
? { type: "plain_text", name: item.name, text: selfUrl }
|
|
1864
|
+
: item));
|
|
1865
|
+
appendAlchemyAndEnvBindings(metadataBindings, news, accountId, parentName);
|
|
1866
|
+
const compatibility = getCompatibility(news);
|
|
1867
|
+
yield* session.note(`Uploading version of ${parentName} ...`);
|
|
1868
|
+
const created = yield* workers
|
|
1869
|
+
.createScriptVersion({
|
|
1870
|
+
accountId,
|
|
1871
|
+
scriptName: parentName,
|
|
1872
|
+
metadata: {
|
|
1873
|
+
mainModule: bundle.main,
|
|
1874
|
+
bindings: metadataBindings,
|
|
1875
|
+
compatibilityDate: compatibility.date,
|
|
1876
|
+
compatibilityFlags: compatibility.flags,
|
|
1877
|
+
cache: news.cache ?? getCacheBinding(bindings),
|
|
1878
|
+
annotations: alias !== undefined ||
|
|
1879
|
+
version.message !== undefined ||
|
|
1880
|
+
version.tag !== undefined
|
|
1881
|
+
? {
|
|
1882
|
+
workersAlias: alias,
|
|
1883
|
+
workersMessage: version.message,
|
|
1884
|
+
workersTag: version.tag,
|
|
1885
|
+
}
|
|
1886
|
+
: undefined,
|
|
1887
|
+
},
|
|
1888
|
+
files: bundle.files,
|
|
1889
|
+
})
|
|
1890
|
+
.pipe(Effect.retry({
|
|
1891
|
+
while: isBindingTargetNotFound,
|
|
1892
|
+
schedule: bindingTargetNotFoundRetrySchedule(),
|
|
1893
|
+
}), Effect.catchTag("WorkerNotFound", () => Effect.fail(new WorkerVersionConfigError({
|
|
1894
|
+
message: `version.parent script '${parentName}' does not exist. Deploy the parent Worker first (or check the referenced stage/stack).`,
|
|
1895
|
+
}))));
|
|
1896
|
+
const versionId = created.id ?? undefined;
|
|
1897
|
+
if (versionId === undefined) {
|
|
1898
|
+
return yield* Effect.fail(new WorkerVersionConfigError({
|
|
1899
|
+
message: `Cloudflare did not return a version id for the uploaded version of '${parentName}'.`,
|
|
1900
|
+
}));
|
|
1901
|
+
}
|
|
1902
|
+
let deploymentId;
|
|
1903
|
+
if (traffic > 0) {
|
|
1904
|
+
yield* session.note(`Deploying version at ${traffic}% of ${parentName}'s traffic ...`);
|
|
1905
|
+
deploymentId = yield* deployVersionTraffic({
|
|
1906
|
+
accountId,
|
|
1907
|
+
scriptName: parentName,
|
|
1908
|
+
versionId,
|
|
1909
|
+
traffic,
|
|
1910
|
+
message: version.message,
|
|
1911
|
+
});
|
|
1912
|
+
}
|
|
1913
|
+
// Version-affinity transform rules (`version.affinity`): a canary
|
|
1914
|
+
// rides the *parent's* zone traffic, so the rules land on the
|
|
1915
|
+
// parent's zones — observed custom-domain attachments plus, when
|
|
1916
|
+
// `parent` was passed as a Worker resource, its recorded routes.
|
|
1917
|
+
const previousAffinityZoneIds = output?.affinityZoneIds ?? [];
|
|
1918
|
+
let affinityZoneIds;
|
|
1919
|
+
if (version.affinity !== undefined || previousAffinityZoneIds.length) {
|
|
1920
|
+
const resolvedAffinity = version.affinity !== undefined
|
|
1921
|
+
? yield* resolveVersionAffinity(version.affinity)
|
|
1922
|
+
: undefined;
|
|
1923
|
+
let hostsByZone;
|
|
1924
|
+
if (resolvedAffinity !== undefined) {
|
|
1925
|
+
hostsByZone = new Map();
|
|
1926
|
+
const addHost = (zoneId, host) => {
|
|
1927
|
+
const hosts = hostsByZone.get(zoneId) ?? [];
|
|
1928
|
+
hosts.push(host);
|
|
1929
|
+
hostsByZone.set(zoneId, hosts);
|
|
1930
|
+
};
|
|
1931
|
+
// A `version.parent` passed as a Worker resource resolves to
|
|
1932
|
+
// the parent's Attributes at reconcile time (see
|
|
1933
|
+
// resolveVersionParentName) — its recorded routes and domain
|
|
1934
|
+
// tell us the zones the parent serves on.
|
|
1935
|
+
const parentAttrs = typeof version.parent === "object" && version.parent !== null
|
|
1936
|
+
? version.parent
|
|
1937
|
+
: undefined;
|
|
1938
|
+
const redirectHosts = new Set(parentAttrs?.domain?.redirects ?? []);
|
|
1939
|
+
const attached = yield* workers
|
|
1940
|
+
.listDomains({ accountId, service: parentName })
|
|
1941
|
+
.pipe(Effect.map((r) => r.result ?? []));
|
|
1942
|
+
for (const d of attached) {
|
|
1943
|
+
if (!d.hostname || !d.zoneId || redirectHosts.has(d.hostname)) {
|
|
1944
|
+
continue;
|
|
1945
|
+
}
|
|
1946
|
+
addHost(d.zoneId, { host: d.hostname, wildcard: false });
|
|
1947
|
+
}
|
|
1948
|
+
for (const route of parentAttrs?.routes ?? []) {
|
|
1949
|
+
const host = route.pattern.split("/")[0];
|
|
1950
|
+
if (!host || host.includes('"'))
|
|
1951
|
+
continue;
|
|
1952
|
+
addHost(route.zoneId, { host, wildcard: host.includes("*") });
|
|
1953
|
+
}
|
|
1954
|
+
if (hostsByZone.size === 0) {
|
|
1955
|
+
return yield* Effect.fail(new WorkerVersionConfigError({
|
|
1956
|
+
message: `version.affinity pins users via a zone Transform Rule setting the ${AFFINITY_HEADER} header, which only sees zone traffic — the parent '${parentName}' has no custom domains (or recorded zone routes) to place the rule on. ` +
|
|
1957
|
+
`Give the parent a \`domain\` or zone \`routes\`, or pass the parent as a Worker resource so its routes are visible here.`,
|
|
1958
|
+
}));
|
|
1959
|
+
}
|
|
1960
|
+
yield* session.note("Reconciling version-affinity rules ...");
|
|
1961
|
+
}
|
|
1962
|
+
const placed = yield* reconcileAffinityRules({
|
|
1963
|
+
scriptName: parentName,
|
|
1964
|
+
desired: resolvedAffinity !== undefined && hostsByZone !== undefined
|
|
1965
|
+
? { affinity: resolvedAffinity, hostsByZone }
|
|
1966
|
+
: undefined,
|
|
1967
|
+
previousZoneIds: previousAffinityZoneIds,
|
|
1968
|
+
});
|
|
1969
|
+
affinityZoneIds = placed.length > 0 ? placed : undefined;
|
|
1970
|
+
}
|
|
1971
|
+
// The aliased URL is primary (`url`): it is stable across deploys,
|
|
1972
|
+
// re-pointing at each newly uploaded version. The per-version URL
|
|
1973
|
+
// (prefixed by the version id) rides along in `urls`.
|
|
1974
|
+
const versionedUrl = previewsEnabled
|
|
1975
|
+
? `https://${versionId.split("-")[0]}-${parentName}.${accountSubdomain}.workers.dev`
|
|
1976
|
+
: undefined;
|
|
1977
|
+
const urls = [
|
|
1978
|
+
...(aliasedUrl ? [aliasedUrl] : []),
|
|
1979
|
+
...(versionedUrl ? [versionedUrl] : []),
|
|
1980
|
+
];
|
|
1981
|
+
return {
|
|
1982
|
+
workerId: parentName,
|
|
1983
|
+
workerName: parentName,
|
|
1984
|
+
namespace: undefined,
|
|
1985
|
+
logpush: undefined,
|
|
1986
|
+
url: urls[0],
|
|
1987
|
+
urls,
|
|
1988
|
+
domain: undefined,
|
|
1989
|
+
tags: undefined,
|
|
1990
|
+
durableObjectNamespaces: {},
|
|
1991
|
+
accountId,
|
|
1992
|
+
routes: [],
|
|
1993
|
+
crons: [],
|
|
1994
|
+
versionOf: parentName,
|
|
1995
|
+
versionId,
|
|
1996
|
+
versionAlias: alias,
|
|
1997
|
+
deploymentId,
|
|
1998
|
+
affinityZoneIds,
|
|
1999
|
+
hash,
|
|
2000
|
+
};
|
|
2001
|
+
});
|
|
926
2002
|
const putWorker = Effect.fn(function* (id, news, bindings, olds, output, session, existingSettings) {
|
|
927
2003
|
const { accountId } = yield* yield* CloudflareEnvironment;
|
|
928
2004
|
// Prefer the deployed name: regenerating would target a different
|
|
@@ -933,6 +2009,22 @@ export const LiveWorkerProvider = () => Provider.effect(Worker, Effect.gen(funct
|
|
|
933
2009
|
// account-level script. The put/settings calls switch endpoints and
|
|
934
2010
|
// the subdomain / custom-domain / cron reconciliation is skipped.
|
|
935
2011
|
const dispatchNamespace = resolveNamespaceName(news?.namespace);
|
|
2012
|
+
yield* validateTraffic(news.version?.traffic);
|
|
2013
|
+
if (news.version !== undefined && dispatchNamespace) {
|
|
2014
|
+
return yield* Effect.fail(new WorkerVersionConfigError({
|
|
2015
|
+
message: `Workers for Platforms user workers do not support versions or gradual deployments — remove the version prop from '${name}'.`,
|
|
2016
|
+
}));
|
|
2017
|
+
}
|
|
2018
|
+
// Resolve the Worker's own URL up front when a `Worker.URL` binding
|
|
2019
|
+
// is present: the value must exist before the bundle is built (Vite
|
|
2020
|
+
// inlines VITE_*-prefixed env into the client bundle) and before the
|
|
2021
|
+
// metadata bindings are assembled (the `self_url` sentinel lowers
|
|
2022
|
+
// into a plain_text binding below).
|
|
2023
|
+
const selfUrl = hasSelfUrlBinding(bindings)
|
|
2024
|
+
? dispatchNamespace
|
|
2025
|
+
? yield* Effect.die(`Worker "${name}" binds its own URL (Worker.URL), but a dispatch-namespace user worker is invoked via dynamic dispatch and has no URL of its own.`)
|
|
2026
|
+
: yield* resolveSelfUrl(name, news, accountId)
|
|
2027
|
+
: undefined;
|
|
936
2028
|
yield* Effect.logInfo(`Cloudflare Worker ${olds ? "update" : "create"}: preparing bundle for ${name}`);
|
|
937
2029
|
// If the caller handed us a precomputed asset hash that matches
|
|
938
2030
|
// what we previously stored, we can skip walking the directory
|
|
@@ -942,6 +2034,7 @@ export const LiveWorkerProvider = () => Provider.effect(Worker, Effect.gen(funct
|
|
|
942
2034
|
const prebuiltAssets = normalizePrebuiltAssets(news.assets, output);
|
|
943
2035
|
const { assets, bundle, hash: preparedHash, } = yield* prepareAssetsAndBundle(id, news, {
|
|
944
2036
|
skipAssetsRead: prebuiltAssets?.skip,
|
|
2037
|
+
selfUrl,
|
|
945
2038
|
});
|
|
946
2039
|
// When the caller supplied a precomputed hash (e.g. via
|
|
947
2040
|
// `Command.Build`), store *that* hash in output state so the
|
|
@@ -954,6 +2047,7 @@ export const LiveWorkerProvider = () => Provider.effect(Worker, Effect.gen(funct
|
|
|
954
2047
|
bindings,
|
|
955
2048
|
accountId,
|
|
956
2049
|
stack: { name: stack.name, stage: stack.stage },
|
|
2050
|
+
selfUrl,
|
|
957
2051
|
});
|
|
958
2052
|
const hash = {
|
|
959
2053
|
...preparedHash,
|
|
@@ -965,11 +2059,24 @@ export const LiveWorkerProvider = () => Provider.effect(Worker, Effect.gen(funct
|
|
|
965
2059
|
// `transferred_classes` migration below and must be stripped from the
|
|
966
2060
|
// wire-shape binding before upload.
|
|
967
2061
|
const metadataBindings = bindings.flatMap((b) => (b.data.bindings ?? []).map((item) => {
|
|
2062
|
+
// Lower the `Worker.URL` sentinel into the resolved URL —
|
|
2063
|
+
// Cloudflare has no native binding for it.
|
|
2064
|
+
if (item.type === "self_url") {
|
|
2065
|
+
return { type: "plain_text", name: item.name, text: selfUrl };
|
|
2066
|
+
}
|
|
968
2067
|
if (item.type === "durable_object_namespace" &&
|
|
969
2068
|
item.transferredFrom !== undefined) {
|
|
970
2069
|
const { transferredFrom: _, ...rest } = item;
|
|
971
2070
|
return rest;
|
|
972
2071
|
}
|
|
2072
|
+
// `queueId` (mode discrimination) and `shim` (dev-mode remote
|
|
2073
|
+
// producer) are alchemy-only metadata on queue bindings — strip
|
|
2074
|
+
// them from the wire shape.
|
|
2075
|
+
if (item.type === "queue" &&
|
|
2076
|
+
(item.queueId !== undefined || item.shim !== undefined)) {
|
|
2077
|
+
const { queueId: _, shim: __, ...rest } = item;
|
|
2078
|
+
return rest;
|
|
2079
|
+
}
|
|
973
2080
|
return item;
|
|
974
2081
|
}));
|
|
975
2082
|
const expectedDurableObjectClassNames = getExpectedDurableObjectClassNames(metadataBindings, name);
|
|
@@ -1018,56 +2125,7 @@ export const LiveWorkerProvider = () => Provider.effect(Worker, Effect.gen(funct
|
|
|
1018
2125
|
name: "ASSETS",
|
|
1019
2126
|
});
|
|
1020
2127
|
}
|
|
1021
|
-
metadataBindings
|
|
1022
|
-
type: "plain_text",
|
|
1023
|
-
name: "ALCHEMY_PHASE",
|
|
1024
|
-
text: "runtime",
|
|
1025
|
-
}, {
|
|
1026
|
-
type: "plain_text",
|
|
1027
|
-
name: "ALCHEMY_STACK_NAME",
|
|
1028
|
-
text: stack.name,
|
|
1029
|
-
}, {
|
|
1030
|
-
type: "plain_text",
|
|
1031
|
-
name: "ALCHEMY_STAGE",
|
|
1032
|
-
text: stack.stage,
|
|
1033
|
-
}, {
|
|
1034
|
-
type: "plain_text",
|
|
1035
|
-
name: "ALCHEMY_CLOUDFLARE_ACCOUNT_ID",
|
|
1036
|
-
text: accountId,
|
|
1037
|
-
});
|
|
1038
|
-
// Add environment variables as metadata bindings
|
|
1039
|
-
if (news.env) {
|
|
1040
|
-
for (const [key, value] of Object.entries(news.env)) {
|
|
1041
|
-
if (value === undefined)
|
|
1042
|
-
continue;
|
|
1043
|
-
if (metadataBindings.some((b) => b.name === key))
|
|
1044
|
-
continue;
|
|
1045
|
-
if (Redacted.isRedacted(value)) {
|
|
1046
|
-
const unredacted = Redacted.value(value);
|
|
1047
|
-
metadataBindings.push({
|
|
1048
|
-
type: "secret_text",
|
|
1049
|
-
name: key,
|
|
1050
|
-
text: typeof unredacted === "string"
|
|
1051
|
-
? unredacted
|
|
1052
|
-
: JSON.stringify(unredacted),
|
|
1053
|
-
});
|
|
1054
|
-
}
|
|
1055
|
-
else if (typeof value === "string") {
|
|
1056
|
-
metadataBindings.push({
|
|
1057
|
-
type: "plain_text",
|
|
1058
|
-
name: key,
|
|
1059
|
-
text: value,
|
|
1060
|
-
});
|
|
1061
|
-
}
|
|
1062
|
-
else {
|
|
1063
|
-
metadataBindings.push({
|
|
1064
|
-
type: "json",
|
|
1065
|
-
name: key,
|
|
1066
|
-
json: value,
|
|
1067
|
-
});
|
|
1068
|
-
}
|
|
1069
|
-
}
|
|
1070
|
-
}
|
|
2128
|
+
appendAlchemyAndEnvBindings(metadataBindings, news, accountId, name);
|
|
1071
2129
|
yield* Effect.logInfo(`Cloudflare Worker ${olds ? "update" : "create"}: uploading script for ${name}`);
|
|
1072
2130
|
const size = bundle.files
|
|
1073
2131
|
?.filter((file) => !file.name.endsWith(".map"))
|
|
@@ -1337,41 +2395,131 @@ export const LiveWorkerProvider = () => Provider.effect(Worker, Effect.gen(funct
|
|
|
1337
2395
|
tailConsumers: undefined,
|
|
1338
2396
|
usageModel: undefined,
|
|
1339
2397
|
};
|
|
1340
|
-
const
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
2398
|
+
const rolloutTraffic = getSelfRolloutTraffic(news);
|
|
2399
|
+
let versionId;
|
|
2400
|
+
let deploymentId;
|
|
2401
|
+
let worker;
|
|
2402
|
+
// A gradual rollout (`version.traffic` < 100) deploys through the
|
|
2403
|
+
// versions API instead of the full-cutover script PUT. That's only
|
|
2404
|
+
// possible when the script already has a live deployment to split
|
|
2405
|
+
// traffic against (otherwise the first deploy takes 100%), and only
|
|
2406
|
+
// for changes a version can carry — code, bindings, compatibility
|
|
2407
|
+
// settings, cache. Script-level settings in the upload metadata
|
|
2408
|
+
// (tags, observability, limits, placement, logpush) keep their live
|
|
2409
|
+
// values until the next full deploy, matching
|
|
2410
|
+
// `wrangler versions upload` semantics.
|
|
2411
|
+
if (rolloutTraffic !== undefined &&
|
|
2412
|
+
existingSettings !== undefined &&
|
|
2413
|
+
output?.hash !== undefined &&
|
|
2414
|
+
!dispatchNamespace) {
|
|
2415
|
+
if (metadataAssets !== undefined) {
|
|
2416
|
+
return yield* Effect.fail(new WorkerVersionConfigError({
|
|
2417
|
+
message: `Worker '${name}' has static assets, which the versions API cannot carry — gradual rollouts (version.traffic) are not supported for Workers with assets.`,
|
|
2418
|
+
}));
|
|
2419
|
+
}
|
|
2420
|
+
const migratedClasses = [
|
|
2421
|
+
...migrations.newClasses,
|
|
2422
|
+
...migrations.newSqliteClasses,
|
|
2423
|
+
...migrations.deletedClasses,
|
|
2424
|
+
...migrations.renamedClasses.map((r) => r.to),
|
|
2425
|
+
...migrations.transferredClasses.map((t) => t.to),
|
|
2426
|
+
];
|
|
2427
|
+
if (migratedClasses.length > 0) {
|
|
2428
|
+
return yield* Effect.fail(new WorkerVersionConfigError({
|
|
2429
|
+
message: `This deploy of '${name}' changes Durable Object classes (${migratedClasses.join(", ")}), which requires a migration — migrations cannot ride a gradual rollout. Deploy at 100% (remove version.traffic) first, then resume gradual rollouts.`,
|
|
2430
|
+
}));
|
|
2431
|
+
}
|
|
2432
|
+
yield* session.note(`Uploading version of ${name} (${bundleSize}) ...`);
|
|
2433
|
+
const created = yield* workers
|
|
2434
|
+
.createScriptVersion({
|
|
2435
|
+
accountId,
|
|
2436
|
+
scriptName: name,
|
|
2437
|
+
metadata: {
|
|
2438
|
+
mainModule: metadata.mainModule,
|
|
2439
|
+
bindings: metadata.bindings,
|
|
2440
|
+
compatibilityDate: metadata.compatibilityDate,
|
|
2441
|
+
compatibilityFlags: metadata.compatibilityFlags,
|
|
2442
|
+
cache: metadata.cache,
|
|
2443
|
+
annotations: news.version?.alias !== undefined ||
|
|
2444
|
+
news.version?.message !== undefined ||
|
|
2445
|
+
news.version?.tag !== undefined
|
|
2446
|
+
? {
|
|
2447
|
+
workersAlias: news.version?.alias,
|
|
2448
|
+
workersMessage: news.version?.message,
|
|
2449
|
+
workersTag: news.version?.tag,
|
|
2450
|
+
}
|
|
2451
|
+
: undefined,
|
|
2452
|
+
},
|
|
2453
|
+
files: bundle.files,
|
|
2454
|
+
})
|
|
2455
|
+
.pipe(Effect.retry({
|
|
2456
|
+
while: isBindingTargetNotFound,
|
|
2457
|
+
schedule: bindingTargetNotFoundRetrySchedule(),
|
|
2458
|
+
}));
|
|
2459
|
+
versionId = created.id ?? undefined;
|
|
2460
|
+
if (versionId === undefined) {
|
|
2461
|
+
return yield* Effect.fail(new WorkerVersionConfigError({
|
|
2462
|
+
message: `Cloudflare did not return a version id for the uploaded version of '${name}'.`,
|
|
2463
|
+
}));
|
|
2464
|
+
}
|
|
2465
|
+
if (rolloutTraffic > 0) {
|
|
2466
|
+
yield* session.note(`Deploying version at ${rolloutTraffic}% of traffic ...`);
|
|
2467
|
+
deploymentId = yield* deployVersionTraffic({
|
|
1358
2468
|
accountId,
|
|
1359
2469
|
scriptName: name,
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
migrations: {
|
|
1364
|
-
...migrations,
|
|
1365
|
-
oldTag: expectedTag,
|
|
1366
|
-
newTag: bumpMigrationTagVersion(expectedTag),
|
|
1367
|
-
},
|
|
1368
|
-
},
|
|
1369
|
-
files: bundle.files,
|
|
2470
|
+
versionId,
|
|
2471
|
+
traffic: rolloutTraffic,
|
|
2472
|
+
message: news.version?.message,
|
|
1370
2473
|
});
|
|
1371
2474
|
}
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
2475
|
+
worker = { id: name, logpush: existingSettings.logpush };
|
|
2476
|
+
}
|
|
2477
|
+
else {
|
|
2478
|
+
if (rolloutTraffic !== undefined) {
|
|
2479
|
+
// First deploy of this script (or a pre-create placeholder is
|
|
2480
|
+
// the only thing live) — there is no previous version worth
|
|
2481
|
+
// splitting traffic with, so this deploy takes 100%.
|
|
2482
|
+
yield* Effect.logInfo(`Cloudflare Worker ${name}: no previous live version to split traffic with; deploying at 100%`);
|
|
2483
|
+
}
|
|
2484
|
+
worker = yield* putWorkerScriptWithMigrationRecovery();
|
|
2485
|
+
}
|
|
2486
|
+
function putWorkerScriptWithMigrationRecovery() {
|
|
2487
|
+
return putWorkerScript({
|
|
2488
|
+
accountId,
|
|
2489
|
+
scriptName: name,
|
|
2490
|
+
dispatchNamespace,
|
|
2491
|
+
metadata,
|
|
2492
|
+
files: bundle.files,
|
|
2493
|
+
}).pipe(Effect.catch((err) => {
|
|
2494
|
+
// When adopting a Worker managed by Wrangler (or after a previous
|
|
2495
|
+
// deploy with mismatched migrations), the old_tag precondition
|
|
2496
|
+
// fails. The only way to discover the actual tag is through the
|
|
2497
|
+
// error message — getScriptSettings is meant to return it but
|
|
2498
|
+
// doesn't at runtime.
|
|
2499
|
+
const msg = String(typeof err === "object" && err !== null && "message" in err
|
|
2500
|
+
? err.message
|
|
2501
|
+
: err);
|
|
2502
|
+
const expectedTag = msg.match(/when expected tag is ['"]?([^'"]+)['"]?/)?.[1];
|
|
2503
|
+
if (expectedTag) {
|
|
2504
|
+
return putWorkerScript({
|
|
2505
|
+
accountId,
|
|
2506
|
+
scriptName: name,
|
|
2507
|
+
dispatchNamespace,
|
|
2508
|
+
metadata: {
|
|
2509
|
+
...metadata,
|
|
2510
|
+
migrations: {
|
|
2511
|
+
...migrations,
|
|
2512
|
+
oldTag: expectedTag,
|
|
2513
|
+
newTag: bumpMigrationTagVersion(expectedTag),
|
|
2514
|
+
},
|
|
2515
|
+
},
|
|
2516
|
+
files: bundle.files,
|
|
2517
|
+
});
|
|
2518
|
+
}
|
|
2519
|
+
// @effect-diagnostics-next-line anyUnknownInErrorContext:off
|
|
2520
|
+
return Effect.fail(err);
|
|
2521
|
+
}));
|
|
2522
|
+
}
|
|
1375
2523
|
const { settings, durableObjectNamespaces } = yield* getWorkerSettingsWithDurableObjects(name, expectedDurableObjectClassNames, dispatchNamespace);
|
|
1376
2524
|
// Workers for Platforms user workers are invoked via dynamic dispatch,
|
|
1377
2525
|
// never routed directly — they have no workers.dev subdomain, custom
|
|
@@ -1387,20 +2535,21 @@ export const LiveWorkerProvider = () => Provider.effect(Worker, Effect.gen(funct
|
|
|
1387
2535
|
tags: settings.tags ?? metadata.tags,
|
|
1388
2536
|
durableObjectNamespaces,
|
|
1389
2537
|
accountId,
|
|
1390
|
-
|
|
2538
|
+
urls: [],
|
|
2539
|
+
domain: undefined,
|
|
1391
2540
|
routes: [],
|
|
1392
2541
|
crons: [],
|
|
1393
2542
|
hash,
|
|
1394
2543
|
};
|
|
1395
2544
|
}
|
|
1396
|
-
// Reconcile workers.dev
|
|
1397
|
-
// We can't diff `news.
|
|
1398
|
-
// default to `undefined` (meaning "enable") — that
|
|
1399
|
-
// would skip the API call on every deploy where the user
|
|
1400
|
-
//
|
|
2545
|
+
// Reconcile the workers.dev settings against observed cloud state.
|
|
2546
|
+
// We can't diff `news.workersDev` against `olds.workersDev` here
|
|
2547
|
+
// because both default to `undefined` (meaning "enable") — that
|
|
2548
|
+
// comparison would skip the API call on every deploy where the user
|
|
2549
|
+
// never set the prop, leaving the subdomain in whatever state
|
|
1401
2550
|
// Cloudflare currently has it (disabled by default, or whatever
|
|
1402
2551
|
// a previous failed/external action left it as).
|
|
1403
|
-
const
|
|
2552
|
+
const workersDev = resolveWorkersDev(news.workersDev);
|
|
1404
2553
|
const observedSubdomain = yield* workers
|
|
1405
2554
|
.getScriptSubdomain({
|
|
1406
2555
|
accountId,
|
|
@@ -1410,9 +2559,9 @@ export const LiveWorkerProvider = () => Provider.effect(Worker, Effect.gen(funct
|
|
|
1410
2559
|
enabled: false,
|
|
1411
2560
|
previewsEnabled: false,
|
|
1412
2561
|
})));
|
|
1413
|
-
if (
|
|
1414
|
-
|
|
1415
|
-
yield* session.note(`${
|
|
2562
|
+
if (workersDev.enabled !== observedSubdomain.enabled ||
|
|
2563
|
+
workersDev.previewsEnabled !== observedSubdomain.previewsEnabled) {
|
|
2564
|
+
yield* session.note(`${workersDev.enabled || workersDev.previewsEnabled ? "Enabling" : "Disabling"} workers.dev subdomain...`);
|
|
1416
2565
|
// Cloudflare's script registry is eventually consistent — for the
|
|
1417
2566
|
// first few hundred ms after `putScript` returns, POST /subdomain
|
|
1418
2567
|
// can still get back `WorkerNotFound` (a generic "unknown error"
|
|
@@ -1421,7 +2570,7 @@ export const LiveWorkerProvider = () => Provider.effect(Worker, Effect.gen(funct
|
|
|
1421
2570
|
// Retry the subdomain toggle on those transient tags with a short
|
|
1422
2571
|
// exponential backoff; same pattern we use elsewhere in this
|
|
1423
2572
|
// provider for DO-namespace propagation and for `putScript` itself.
|
|
1424
|
-
yield* setWorkerSubdomain(name,
|
|
2573
|
+
yield* setWorkerSubdomain(name, workersDev).pipe(Effect.retry({
|
|
1425
2574
|
while: (error) => error._tag === "WorkerNotFound" ||
|
|
1426
2575
|
error._tag === "InternalServerError" ||
|
|
1427
2576
|
error._tag === "UnknownCloudflareError",
|
|
@@ -1431,38 +2580,169 @@ export const LiveWorkerProvider = () => Provider.effect(Worker, Effect.gen(funct
|
|
|
1431
2580
|
]),
|
|
1432
2581
|
}));
|
|
1433
2582
|
}
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
2583
|
+
// Custom domains are managed only when `domain` is declared on props
|
|
2584
|
+
// (#942): an omitted `domain` leaves live attachments alone — and
|
|
2585
|
+
// spares a plain workers.dev Worker the listDomains call on every
|
|
2586
|
+
// deploy (#926) — while `domain: null` explicitly detaches
|
|
2587
|
+
// everything. When managed, the canonical name + aliases + redirect
|
|
2588
|
+
// hostnames are all attached (DNS + edge certificate); redirect
|
|
2589
|
+
// hostnames additionally get a redirect rule, which runs before the
|
|
2590
|
+
// Worker so those requests never invoke it. Unmanaged domains
|
|
2591
|
+
// persisted in state carry forward so read keeps observing them.
|
|
2592
|
+
const manageCustomDomains = news.domain !== undefined;
|
|
2593
|
+
const domainConfig = yield* resolveWorkerDomain(news.domain);
|
|
2594
|
+
const previousDomain = stateWorkerDomain(output);
|
|
2595
|
+
let effectiveDomain = manageCustomDomains
|
|
2596
|
+
? domainConfig
|
|
2597
|
+
: previousDomain;
|
|
2598
|
+
if (!manageCustomDomains && previousDomain !== undefined) {
|
|
2599
|
+
// Unmanaged, but state remembers domains: observe which of them
|
|
2600
|
+
// are actually still attached and carry only those forward —
|
|
2601
|
+
// stale state entries (e.g. legacy records whose attach never
|
|
2602
|
+
// happened, or hostnames detached out-of-band) must not
|
|
2603
|
+
// resurface in `urls`. Plain workers.dev Workers (no persisted
|
|
2604
|
+
// domains) never pay this listDomains call (#926).
|
|
2605
|
+
const live = new Set(yield* workers.listDomains({ accountId, service: name }).pipe(Effect.map((r) => (r.result ?? []).flatMap((d) => d.hostname ? [d.hostname] : [])), Effect.catch(() => Effect.succeed([]))));
|
|
2606
|
+
const serving = [
|
|
2607
|
+
...(live.has(previousDomain.name) ? [previousDomain.name] : []),
|
|
2608
|
+
...previousDomain.aliases.filter((h) => live.has(h)),
|
|
2609
|
+
];
|
|
2610
|
+
effectiveDomain =
|
|
2611
|
+
serving.length > 0
|
|
2612
|
+
? {
|
|
2613
|
+
name: serving[0],
|
|
2614
|
+
aliases: serving.slice(1),
|
|
2615
|
+
redirects: previousDomain.redirects.filter((h) => live.has(h)),
|
|
2616
|
+
}
|
|
2617
|
+
: undefined;
|
|
1438
2618
|
}
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
2619
|
+
if (manageCustomDomains) {
|
|
2620
|
+
const desiredHostnames = domainConfig
|
|
2621
|
+
? [
|
|
2622
|
+
domainConfig.name,
|
|
2623
|
+
...domainConfig.aliases,
|
|
2624
|
+
...domainConfig.redirects,
|
|
2625
|
+
]
|
|
2626
|
+
: [];
|
|
2627
|
+
yield* session.note(`Reconciling custom domains (${desiredHostnames.length}) ...`);
|
|
2628
|
+
// Capture hostname → zone for *currently attached* domains before
|
|
2629
|
+
// reconcile detaches removed ones — a removed redirect hostname's
|
|
2630
|
+
// zone is otherwise unresolvable and its rule couldn't be cleaned.
|
|
2631
|
+
const liveBeforeReconcile = yield* workers
|
|
2632
|
+
.listDomains({ accountId, service: name })
|
|
2633
|
+
.pipe(Effect.map((r) => (r.result ?? []).flatMap((d) => d.hostname && d.zoneId
|
|
2634
|
+
? [[d.hostname, d.zoneId]]
|
|
2635
|
+
: [])), Effect.catch(() => Effect.succeed([])));
|
|
2636
|
+
const reconciled = yield* reconcileDomains(name, desiredHostnames);
|
|
2637
|
+
const zoneIdByHostname = new Map([
|
|
2638
|
+
...liveBeforeReconcile,
|
|
2639
|
+
...reconciled.map((d) => [d.hostname, d.zoneId]),
|
|
2640
|
+
]);
|
|
2641
|
+
yield* reconcileRedirectRules({
|
|
2642
|
+
scriptName: name,
|
|
2643
|
+
domain: domainConfig,
|
|
2644
|
+
zoneIdByHostname,
|
|
2645
|
+
previousRedirects: previousDomain?.redirects ?? [],
|
|
2646
|
+
});
|
|
2647
|
+
effectiveDomain = domainConfig;
|
|
2648
|
+
}
|
|
2649
|
+
const urls = yield* computeWorkerUrls({
|
|
2650
|
+
scriptName: name,
|
|
2651
|
+
workersDev,
|
|
2652
|
+
domain: effectiveDomain,
|
|
2653
|
+
uploadedVersionId: versionId,
|
|
2654
|
+
uploadedVersionAlias: news.version?.alias,
|
|
2655
|
+
});
|
|
1447
2656
|
const desiredRoutes = yield* normalizeRoutes(news.routes);
|
|
1448
2657
|
const previousRoutes = output?.routes ?? [];
|
|
1449
2658
|
if (desiredRoutes.length > 0 || previousRoutes.length > 0) {
|
|
1450
2659
|
yield* session.note(`Reconciling worker routes (${desiredRoutes.length}) ...`);
|
|
1451
2660
|
}
|
|
1452
2661
|
const routes = yield* reconcileRoutes(name, desiredRoutes, previousRoutes);
|
|
1453
|
-
|
|
2662
|
+
// Version-affinity transform rules (`version.affinity`): pin each
|
|
2663
|
+
// user to one version during gradual rollouts by filling the
|
|
2664
|
+
// version-key header from a stable request property, on every zone
|
|
2665
|
+
// this Worker serves on. Runs after the domain/route reconciles so
|
|
2666
|
+
// freshly attached surfaces resolve their zones, and also when only
|
|
2667
|
+
// *previous* state holds rules, so removing affinity converges.
|
|
2668
|
+
const affinity = news.version?.parent == null ? news.version?.affinity : undefined;
|
|
2669
|
+
const previousAffinityZoneIds = output?.affinityZoneIds ?? [];
|
|
2670
|
+
let affinityZoneIds;
|
|
2671
|
+
if (affinity !== undefined || previousAffinityZoneIds.length > 0) {
|
|
2672
|
+
const resolvedAffinity = affinity !== undefined
|
|
2673
|
+
? yield* resolveVersionAffinity(affinity)
|
|
2674
|
+
: undefined;
|
|
2675
|
+
let hostsByZone;
|
|
2676
|
+
if (resolvedAffinity !== undefined) {
|
|
2677
|
+
hostsByZone = new Map();
|
|
2678
|
+
const addHost = (zoneId, host) => {
|
|
2679
|
+
const hosts = hostsByZone.get(zoneId) ?? [];
|
|
2680
|
+
hosts.push(host);
|
|
2681
|
+
hostsByZone.set(zoneId, hosts);
|
|
2682
|
+
};
|
|
2683
|
+
// Serving hostnames from *observed* domain attachments (covers
|
|
2684
|
+
// managed, unmanaged, and adopted domains alike); redirect
|
|
2685
|
+
// hostnames 301 before the Worker and never need a key.
|
|
2686
|
+
const redirectHosts = new Set(effectiveDomain?.redirects ?? []);
|
|
2687
|
+
const attached = yield* workers
|
|
2688
|
+
.listDomains({ accountId, service: name })
|
|
2689
|
+
.pipe(Effect.map((r) => r.result ?? []));
|
|
2690
|
+
for (const d of attached) {
|
|
2691
|
+
if (!d.hostname || !d.zoneId || redirectHosts.has(d.hostname)) {
|
|
2692
|
+
continue;
|
|
2693
|
+
}
|
|
2694
|
+
addHost(d.zoneId, { host: d.hostname, wildcard: false });
|
|
2695
|
+
}
|
|
2696
|
+
for (const route of routes) {
|
|
2697
|
+
const host = route.pattern.split("/")[0];
|
|
2698
|
+
if (!host || host.includes('"'))
|
|
2699
|
+
continue;
|
|
2700
|
+
addHost(route.zoneId, { host, wildcard: host.includes("*") });
|
|
2701
|
+
}
|
|
2702
|
+
if (hostsByZone.size === 0) {
|
|
2703
|
+
return yield* Effect.fail(new WorkerVersionConfigError({
|
|
2704
|
+
message: `version.affinity pins users via a zone Transform Rule setting the ${AFFINITY_HEADER} header, which only sees zone traffic — give '${name}' a custom domain (\`domain\`) or zone \`routes\`. ` +
|
|
2705
|
+
`On the bare workers.dev URL, clients must send the header themselves.`,
|
|
2706
|
+
}));
|
|
2707
|
+
}
|
|
2708
|
+
yield* session.note("Reconciling version-affinity rules ...");
|
|
2709
|
+
}
|
|
2710
|
+
const placed = yield* reconcileAffinityRules({
|
|
2711
|
+
scriptName: name,
|
|
2712
|
+
desired: resolvedAffinity !== undefined && hostsByZone !== undefined
|
|
2713
|
+
? { affinity: resolvedAffinity, hostsByZone }
|
|
2714
|
+
: undefined,
|
|
2715
|
+
previousZoneIds: previousAffinityZoneIds,
|
|
2716
|
+
});
|
|
2717
|
+
affinityZoneIds = placed.length > 0 ? placed : undefined;
|
|
2718
|
+
}
|
|
2719
|
+
const desiredCrons = normalizeCrons([
|
|
2720
|
+
...getCronBindings(bindings),
|
|
2721
|
+
...(news.crons ?? []),
|
|
2722
|
+
]);
|
|
2723
|
+
const previousCrons = output?.crons ?? [];
|
|
2724
|
+
// Same gating as read: skip getScriptSchedule when neither props nor
|
|
2725
|
+
// prior state indicate cron management (#926).
|
|
2726
|
+
const crons = desiredCrons.length > 0 || previousCrons.length > 0
|
|
2727
|
+
? yield* reconcileCrons(name, desiredCrons, previousCrons, session)
|
|
2728
|
+
: [];
|
|
1454
2729
|
return {
|
|
1455
2730
|
workerId: worker.id ?? name,
|
|
1456
2731
|
workerName: name,
|
|
1457
2732
|
namespace: undefined,
|
|
1458
2733
|
logpush: worker.logpush ?? undefined,
|
|
1459
|
-
url:
|
|
2734
|
+
url: urls[0],
|
|
2735
|
+
urls,
|
|
2736
|
+
domain: effectiveDomain,
|
|
1460
2737
|
tags: settings.tags ?? metadata.tags,
|
|
1461
2738
|
durableObjectNamespaces,
|
|
1462
2739
|
accountId,
|
|
1463
|
-
domains,
|
|
1464
2740
|
routes,
|
|
1465
2741
|
crons,
|
|
2742
|
+
versionOf: undefined,
|
|
2743
|
+
versionId,
|
|
2744
|
+
deploymentId,
|
|
2745
|
+
affinityZoneIds,
|
|
1466
2746
|
hash,
|
|
1467
2747
|
};
|
|
1468
2748
|
});
|
|
@@ -1474,11 +2754,29 @@ export const LiveWorkerProvider = () => Provider.effect(Worker, Effect.gen(funct
|
|
|
1474
2754
|
// unresolved: the hash can't be computed deterministically here, and
|
|
1475
2755
|
// the eventual apply stores it once bindings resolve.
|
|
1476
2756
|
if (bindings) {
|
|
2757
|
+
// For a version worker, `Worker.URL` resolves to the *aliased
|
|
2758
|
+
// preview URL* (see `putWorkerVersion`) — recompute the same
|
|
2759
|
+
// value here so the metadata hash matches what the apply stored
|
|
2760
|
+
// and unchanged deploys stay noops.
|
|
2761
|
+
const versionParent = props.version?.parent != null
|
|
2762
|
+
? (resolveVersionParentName(props.version) ?? output.versionOf)
|
|
2763
|
+
: undefined;
|
|
2764
|
+
const selfUrl = hasSelfUrlBinding(bindings)
|
|
2765
|
+
? versionParent !== undefined
|
|
2766
|
+
? yield* Effect.gen(function* () {
|
|
2767
|
+
const alias = yield* resolveVersionAlias(id, props, versionParent);
|
|
2768
|
+
return alias !== undefined
|
|
2769
|
+
? `https://${alias}-${versionParent}.${yield* getAccountSubdomain(accountId)}.workers.dev`
|
|
2770
|
+
: undefined;
|
|
2771
|
+
})
|
|
2772
|
+
: yield* resolveSelfUrl(output.workerName, props, accountId)
|
|
2773
|
+
: undefined;
|
|
1477
2774
|
const metadataHash = yield* resolveWorkerMetadataHash({
|
|
1478
2775
|
props,
|
|
1479
2776
|
bindings,
|
|
1480
2777
|
accountId,
|
|
1481
2778
|
stack: { name: stack.name, stage: stack.stage },
|
|
2779
|
+
selfUrl,
|
|
1482
2780
|
});
|
|
1483
2781
|
if (metadataHash !== output.hash?.metadata) {
|
|
1484
2782
|
return true;
|
|
@@ -1504,6 +2802,24 @@ export const LiveWorkerProvider = () => Provider.effect(Worker, Effect.gen(funct
|
|
|
1504
2802
|
const { hash } = yield* hashViteInput(props.vite.rootDir, props.vite.memo, Effect.succeed(output.hash?.additionalWorkspaces ?? []));
|
|
1505
2803
|
return hash !== output.hash?.input;
|
|
1506
2804
|
}
|
|
2805
|
+
// Assets-only Worker — there is no bundle to hash. A stored bundle
|
|
2806
|
+
// hash means the Worker previously had a script and is being
|
|
2807
|
+
// converted to assets-only, which must deploy.
|
|
2808
|
+
if (props.main === undefined) {
|
|
2809
|
+
if (output.hash?.bundle !== undefined) {
|
|
2810
|
+
return true;
|
|
2811
|
+
}
|
|
2812
|
+
const assetsHash = props.assets && Predicate.hasProperty(props.assets, "hash")
|
|
2813
|
+
? props.assets.hash
|
|
2814
|
+
: undefined;
|
|
2815
|
+
if (assetsHash === undefined) {
|
|
2816
|
+
// Same conservative rule as the directory-shaped `assets` below:
|
|
2817
|
+
// don't read the directory during diff; `putWorker` reads once
|
|
2818
|
+
// and keeps the existing manifest if nothing actually changed.
|
|
2819
|
+
return true;
|
|
2820
|
+
}
|
|
2821
|
+
return assetsHash !== output.hash?.assets;
|
|
2822
|
+
}
|
|
1507
2823
|
const bundleHash = yield* prepareBundle(id, props).pipe(Effect.map((b) => b.hash));
|
|
1508
2824
|
if (bundleHash !== output.hash?.bundle) {
|
|
1509
2825
|
return true;
|
|
@@ -1572,7 +2888,8 @@ export const LiveWorkerProvider = () => Provider.effect(Worker, Effect.gen(funct
|
|
|
1572
2888
|
url: undefined,
|
|
1573
2889
|
tags: script.tags ?? undefined,
|
|
1574
2890
|
durableObjectNamespaces: {},
|
|
1575
|
-
|
|
2891
|
+
urls: [],
|
|
2892
|
+
domain: undefined,
|
|
1576
2893
|
routes: [],
|
|
1577
2894
|
crons: [],
|
|
1578
2895
|
},
|
|
@@ -1604,6 +2921,23 @@ export const LiveWorkerProvider = () => Provider.effect(Worker, Effect.gen(funct
|
|
|
1604
2921
|
if (newNamespace !== oldNamespace) {
|
|
1605
2922
|
return { action: "replace" };
|
|
1606
2923
|
}
|
|
2924
|
+
// A version worker (version.parent) and a script-owning Worker are
|
|
2925
|
+
// distinct cloud resources, and a version belongs to exactly one
|
|
2926
|
+
// parent script — switching mode or parent is a replacement.
|
|
2927
|
+
const newIsVersion = news.version?.parent != null;
|
|
2928
|
+
const oldIsVersion = output?.versionOf !== undefined || olds?.version?.parent != null;
|
|
2929
|
+
if (newIsVersion !== oldIsVersion) {
|
|
2930
|
+
return { action: "replace" };
|
|
2931
|
+
}
|
|
2932
|
+
if (newIsVersion) {
|
|
2933
|
+
const newParent = resolveVersionParentName(news.version);
|
|
2934
|
+
const oldParent = output?.versionOf ?? resolveVersionParentName(olds?.version);
|
|
2935
|
+
if (newParent !== undefined &&
|
|
2936
|
+
oldParent !== undefined &&
|
|
2937
|
+
newParent !== oldParent) {
|
|
2938
|
+
return { action: "replace" };
|
|
2939
|
+
}
|
|
2940
|
+
}
|
|
1607
2941
|
const oldWorkerName = output?.workerName ?? (yield* createWorkerName(id, olds?.name));
|
|
1608
2942
|
// Auto-generated names are engine-owned: the deployed name stays
|
|
1609
2943
|
// authoritative even if the generator would name this id
|
|
@@ -1617,14 +2951,21 @@ export const LiveWorkerProvider = () => Provider.effect(Worker, Effect.gen(funct
|
|
|
1617
2951
|
if (!output) {
|
|
1618
2952
|
return;
|
|
1619
2953
|
}
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
const
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
2954
|
+
// Compare the full domain config — name, aliases (ordered: they
|
|
2955
|
+
// drive `urls` order), and redirects — against persisted state
|
|
2956
|
+
// (with legacy `domains`-list state migrated on the fly).
|
|
2957
|
+
const newDomainConfig = yield* resolveWorkerDomain(news.domain);
|
|
2958
|
+
const oldDomainConfig = stateWorkerDomain(output);
|
|
2959
|
+
const domainKey = (d) => d === undefined
|
|
2960
|
+
? ""
|
|
2961
|
+
: JSON.stringify([d.name, d.aliases, [...d.redirects].sort()]);
|
|
2962
|
+
// An omitted `domain` unmanages the surface (#942): reconcile
|
|
2963
|
+
// carries previously-observed custom domains forward in state, so
|
|
2964
|
+
// comparing the empty desired config against those would report a
|
|
2965
|
+
// dirty plan on every deploy, forever. Only a declared `domain`
|
|
2966
|
+
// (including `null`, the explicit detach-all) participates.
|
|
2967
|
+
const domainsChanged = news.domain !== undefined &&
|
|
2968
|
+
domainKey(newDomainConfig) !== domainKey(oldDomainConfig);
|
|
1628
2969
|
const newCrons = normalizeCrons([
|
|
1629
2970
|
...(Array.isArray(newBindings)
|
|
1630
2971
|
? getCronBindings(newBindings)
|
|
@@ -1640,24 +2981,28 @@ export const LiveWorkerProvider = () => Provider.effect(Worker, Effect.gen(funct
|
|
|
1640
2981
|
const oldRouteKeys = (output?.routes ?? []).map(routeKey).sort();
|
|
1641
2982
|
const routesChanged = newRouteKeys.length !== oldRouteKeys.length ||
|
|
1642
2983
|
newRouteKeys.some((key, index) => key !== oldRouteKeys[index]);
|
|
1643
|
-
// `url` is `
|
|
1644
|
-
//
|
|
1645
|
-
//
|
|
1646
|
-
//
|
|
1647
|
-
//
|
|
1648
|
-
//
|
|
1649
|
-
//
|
|
1650
|
-
//
|
|
1651
|
-
//
|
|
1652
|
-
//
|
|
1653
|
-
//
|
|
1654
|
-
//
|
|
1655
|
-
const
|
|
1656
|
-
const
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
2984
|
+
// `url` is `urls[0]`: the canonical custom domain when one is
|
|
2985
|
+
// configured (or carried forward from state when the surface is
|
|
2986
|
+
// unmanaged, #942/#975), else the stable workers.dev URL. It's
|
|
2987
|
+
// stable across this update exactly when the recomputed value
|
|
2988
|
+
// matches what's deployed. Carry it forward as a stable only
|
|
2989
|
+
// then, so downstream resources that reference `worker.url`
|
|
2990
|
+
// (e.g. a GitHub Webhook delivery URL built via
|
|
2991
|
+
// `Output.interpolate`) resolve it to a concrete value during
|
|
2992
|
+
// planning instead of an unresolved Output — otherwise every
|
|
2993
|
+
// worker update spuriously re-updates them. Previews-only mode
|
|
2994
|
+
// and version workers derive `url` from version preview URLs —
|
|
2995
|
+
// never assumed stable here.
|
|
2996
|
+
const newWorkersDev = resolveWorkersDev(news.workersDev);
|
|
2997
|
+
const effectiveDomainConfig = news.domain !== undefined ? newDomainConfig : oldDomainConfig;
|
|
2998
|
+
const newUrl = newIsVersion
|
|
2999
|
+
? undefined
|
|
3000
|
+
: effectiveDomainConfig !== undefined
|
|
3001
|
+
? `https://${effectiveDomainConfig.name}`
|
|
3002
|
+
: newWorkersDev.enabled
|
|
3003
|
+
? (output.urls ?? []).find((u) => isWorkersDevHostname(urlHostname(u)) &&
|
|
3004
|
+
urlHostname(u).startsWith(`${workerName}.`))
|
|
3005
|
+
: undefined;
|
|
1661
3006
|
const urlStable = newUrl !== undefined && newUrl === output.url;
|
|
1662
3007
|
// `durableObjectNamespaces` maps each hosted DO class name to the
|
|
1663
3008
|
// namespace id Cloudflare assigned it. Those ids are permanent for
|
|
@@ -1708,6 +3053,30 @@ export const LiveWorkerProvider = () => Provider.effect(Worker, Effect.gen(funct
|
|
|
1708
3053
|
precreate: Effect.fn(function* ({ id, news, session, bindings }) {
|
|
1709
3054
|
const { accountId } = yield* yield* CloudflareEnvironment;
|
|
1710
3055
|
const name = yield* createWorkerName(id, news.name);
|
|
3056
|
+
// A version worker uploads to its parent's script during
|
|
3057
|
+
// reconcile; pre-creating a placeholder script under this
|
|
3058
|
+
// resource's own generated name would leave a stray script behind.
|
|
3059
|
+
// Version workers can't host Durable Object / Workflow classes, so
|
|
3060
|
+
// no placeholder is needed for circular bindings either. (`news`
|
|
3061
|
+
// is raw here — `version.parent` may be an unresolved ref, but its
|
|
3062
|
+
// *presence* is statically known.)
|
|
3063
|
+
if (news.version?.parent != null) {
|
|
3064
|
+
yield* Effect.logInfo(`Cloudflare Worker precreate: skipping stub for version worker ${id}`);
|
|
3065
|
+
return {
|
|
3066
|
+
workerId: name,
|
|
3067
|
+
workerName: name,
|
|
3068
|
+
namespace: undefined,
|
|
3069
|
+
logpush: undefined,
|
|
3070
|
+
url: undefined,
|
|
3071
|
+
tags: undefined,
|
|
3072
|
+
durableObjectNamespaces: {},
|
|
3073
|
+
accountId,
|
|
3074
|
+
urls: [],
|
|
3075
|
+
domain: undefined,
|
|
3076
|
+
routes: [],
|
|
3077
|
+
crons: [],
|
|
3078
|
+
};
|
|
3079
|
+
}
|
|
1711
3080
|
// A Workers for Platforms user worker can't be pre-created: precreate
|
|
1712
3081
|
// runs on raw, *unresolved* props (so resources in a dependency cycle
|
|
1713
3082
|
// can signal early), meaning a `namespace` that references the
|
|
@@ -1727,7 +3096,8 @@ export const LiveWorkerProvider = () => Provider.effect(Worker, Effect.gen(funct
|
|
|
1727
3096
|
tags: undefined,
|
|
1728
3097
|
durableObjectNamespaces: {},
|
|
1729
3098
|
accountId,
|
|
1730
|
-
|
|
3099
|
+
urls: [],
|
|
3100
|
+
domain: undefined,
|
|
1731
3101
|
routes: [],
|
|
1732
3102
|
crons: [],
|
|
1733
3103
|
};
|
|
@@ -1787,6 +3157,7 @@ export const LiveWorkerProvider = () => Provider.effect(Worker, Effect.gen(funct
|
|
|
1787
3157
|
}
|
|
1788
3158
|
else {
|
|
1789
3159
|
yield* session.note("Pre-creating worker...");
|
|
3160
|
+
const compatibility = getCompatibility(news);
|
|
1790
3161
|
const mainModule = "main.js";
|
|
1791
3162
|
const placeholderScript = `${doClasses.length > 0 ? 'import { DurableObject } from "cloudflare:workers";\n\n' : ""}export default { fetch() { return new Response("Alchemy worker is being deployed...") } };\n${doClasses
|
|
1792
3163
|
.map((className) => `export class ${className} extends DurableObject {}`)
|
|
@@ -1804,7 +3175,11 @@ export const LiveWorkerProvider = () => Provider.effect(Worker, Effect.gen(funct
|
|
|
1804
3175
|
className,
|
|
1805
3176
|
}))
|
|
1806
3177
|
: undefined,
|
|
1807
|
-
|
|
3178
|
+
// Spreading `getCompatibility` here would set `date`/`flags`,
|
|
3179
|
+
// which are not metadata keys — the placeholder would upload
|
|
3180
|
+
// with no compatibility date or flags at all.
|
|
3181
|
+
compatibilityDate: compatibility.date,
|
|
3182
|
+
compatibilityFlags: compatibility.flags,
|
|
1808
3183
|
containers,
|
|
1809
3184
|
migrations: doClasses.length > 0
|
|
1810
3185
|
? {
|
|
@@ -1865,13 +3240,35 @@ export const LiveWorkerProvider = () => Provider.effect(Worker, Effect.gen(funct
|
|
|
1865
3240
|
tags: existingSettings?.tags ?? tags,
|
|
1866
3241
|
durableObjectNamespaces,
|
|
1867
3242
|
accountId,
|
|
1868
|
-
|
|
3243
|
+
urls: [],
|
|
3244
|
+
domain: undefined,
|
|
1869
3245
|
routes: [],
|
|
1870
3246
|
crons: [],
|
|
1871
3247
|
};
|
|
1872
3248
|
}),
|
|
1873
3249
|
read: Effect.fn(function* ({ id, output, olds }) {
|
|
1874
3250
|
const { accountId } = yield* yield* CloudflareEnvironment;
|
|
3251
|
+
// Version workers don't own a script — their `workerName` is the
|
|
3252
|
+
// *parent's* script, so the normal read below would hydrate (and
|
|
3253
|
+
// potentially brand as adoptable) a resource we don't own. The
|
|
3254
|
+
// version itself is immutable; all we verify is that it still
|
|
3255
|
+
// exists on the parent.
|
|
3256
|
+
if (output?.versionOf !== undefined ||
|
|
3257
|
+
olds?.version?.parent != null) {
|
|
3258
|
+
if (output?.versionOf === undefined || !output.versionId) {
|
|
3259
|
+
// No recorded version (e.g. state was written before the
|
|
3260
|
+
// upload completed) — nothing to observe; reconcile will
|
|
3261
|
+
// upload a fresh version.
|
|
3262
|
+
return undefined;
|
|
3263
|
+
}
|
|
3264
|
+
return yield* workers
|
|
3265
|
+
.getScriptVersion({
|
|
3266
|
+
accountId,
|
|
3267
|
+
scriptName: output.versionOf,
|
|
3268
|
+
versionId: output.versionId,
|
|
3269
|
+
})
|
|
3270
|
+
.pipe(Effect.map(() => output), Effect.catchTag(["WorkerNotFound", "VersionNotFound"], () => Effect.succeed(undefined)));
|
|
3271
|
+
}
|
|
1875
3272
|
const workerName = output?.workerName ?? (yield* createWorkerName(id, olds?.name));
|
|
1876
3273
|
const dispatchNamespace = output?.namespace ?? resolveNamespaceName(olds?.namespace);
|
|
1877
3274
|
yield* Effect.logInfo(`Cloudflare Worker read: checking ${workerName}`);
|
|
@@ -1890,7 +3287,8 @@ export const LiveWorkerProvider = () => Provider.effect(Worker, Effect.gen(funct
|
|
|
1890
3287
|
url: undefined,
|
|
1891
3288
|
tags: settings.tags ?? undefined,
|
|
1892
3289
|
durableObjectNamespaces: getDurableObjects(settings.bindings),
|
|
1893
|
-
|
|
3290
|
+
urls: [],
|
|
3291
|
+
domain: undefined,
|
|
1894
3292
|
routes: [],
|
|
1895
3293
|
crons: [],
|
|
1896
3294
|
};
|
|
@@ -1906,6 +3304,18 @@ export const LiveWorkerProvider = () => Provider.effect(Worker, Effect.gen(funct
|
|
|
1906
3304
|
// `WorkerNotFound` if the script doesn't exist, which the
|
|
1907
3305
|
// surrounding `Effect.catchTag` turns into `undefined` — that's
|
|
1908
3306
|
// all the existence check we need.
|
|
3307
|
+
//
|
|
3308
|
+
// Domain / route / cron observation is gated on whether Alchemy
|
|
3309
|
+
// manages that surface for this Worker (#926). A plain workers.dev
|
|
3310
|
+
// Worker otherwise pays for listDomains + zone route listings +
|
|
3311
|
+
// getScriptSchedule on every plan/deploy read. Route observation
|
|
3312
|
+
// is additionally scoped to the zones state already associates
|
|
3313
|
+
// with this Worker — see readWorkerRoutes. Empty-array props
|
|
3314
|
+
// (`domain: []`, `routes: []`, `crons: []`) still observe so we
|
|
3315
|
+
// can detect drift and converge deletions.
|
|
3316
|
+
const observeDomains = shouldObserveWorkerDomains(olds, output);
|
|
3317
|
+
const observeRoutes = shouldObserveWorkerRoutes(olds, output);
|
|
3318
|
+
const observeCrons = shouldObserveWorkerCrons(olds, output);
|
|
1909
3319
|
const [subdomain, settings, domainsList, routesList] = yield* Effect.all([
|
|
1910
3320
|
workers.getScriptSubdomain({
|
|
1911
3321
|
accountId,
|
|
@@ -1915,34 +3325,62 @@ export const LiveWorkerProvider = () => Provider.effect(Worker, Effect.gen(funct
|
|
|
1915
3325
|
accountId,
|
|
1916
3326
|
scriptName: workerName,
|
|
1917
3327
|
}),
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
//
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
3328
|
+
observeDomains
|
|
3329
|
+
? workers
|
|
3330
|
+
.listDomains({
|
|
3331
|
+
accountId,
|
|
3332
|
+
service: workerName,
|
|
3333
|
+
})
|
|
3334
|
+
.pipe(Effect.map((r) => r.result ?? []))
|
|
3335
|
+
: Effect.succeed([]),
|
|
3336
|
+
observeRoutes
|
|
3337
|
+
? readWorkerRoutes(workerName, output?.routes)
|
|
3338
|
+
: Effect.succeed([]),
|
|
3339
|
+
],
|
|
3340
|
+
// Bound concurrency so a single Worker read doesn't stampede
|
|
3341
|
+
// the account alongside every other Worker's lifecycle calls.
|
|
3342
|
+
{ concurrency: 1 });
|
|
3343
|
+
// Classify the observed hostnames using the declared config
|
|
3344
|
+
// (`olds.domain`): the canonical name and each alias/redirect
|
|
3345
|
+
// keep their declared role while still attached; drift (attached
|
|
3346
|
+
// hostnames we don't know about) is appended to the aliases. The
|
|
3347
|
+
// Cloudflare API returns domains in non-deterministic order, so
|
|
3348
|
+
// the declared order is what keeps `url`/`urls` from flipping
|
|
3349
|
+
// between reads.
|
|
3350
|
+
const declared = yield* resolveWorkerDomain(olds?.domain).pipe(Effect.orElseSucceed(() => undefined));
|
|
3351
|
+
const observed = new Set(domainsList.flatMap((d) => (d.hostname ? [d.hostname] : [])));
|
|
3352
|
+
const keptName = declared !== undefined && observed.has(declared.name)
|
|
3353
|
+
? declared.name
|
|
3354
|
+
: undefined;
|
|
3355
|
+
const keptAliases = declared?.aliases.filter((h) => observed.has(h)) ?? [];
|
|
3356
|
+
const keptRedirects = declared?.redirects.filter((h) => observed.has(h)) ?? [];
|
|
3357
|
+
const classified = new Set([
|
|
3358
|
+
...(keptName ? [keptName] : []),
|
|
3359
|
+
...keptAliases,
|
|
3360
|
+
...keptRedirects,
|
|
3361
|
+
]);
|
|
3362
|
+
const drift = [...observed].filter((h) => !classified.has(h));
|
|
3363
|
+
const serving = [
|
|
3364
|
+
...(keptName ? [keptName] : []),
|
|
3365
|
+
...keptAliases,
|
|
3366
|
+
...drift,
|
|
1937
3367
|
];
|
|
1938
|
-
const
|
|
1939
|
-
?
|
|
3368
|
+
const observedDomain = serving.length > 0
|
|
3369
|
+
? {
|
|
3370
|
+
name: serving[0],
|
|
3371
|
+
aliases: serving.slice(1),
|
|
3372
|
+
redirects: keptRedirects,
|
|
3373
|
+
}
|
|
1940
3374
|
: undefined;
|
|
1941
|
-
const
|
|
1942
|
-
...
|
|
1943
|
-
...(
|
|
3375
|
+
const urls = [
|
|
3376
|
+
...serving.map((h) => `https://${h}`),
|
|
3377
|
+
...(subdomain.enabled
|
|
3378
|
+
? [
|
|
3379
|
+
`https://${workerName}.${yield* getAccountSubdomain(accountId)}.workers.dev`,
|
|
3380
|
+
]
|
|
3381
|
+
: []),
|
|
1944
3382
|
];
|
|
1945
|
-
const crons = yield* getWorkerCrons(workerName);
|
|
3383
|
+
const crons = observeCrons ? yield* getWorkerCrons(workerName) : [];
|
|
1946
3384
|
yield* Effect.logInfo(`Cloudflare Worker read: found ${workerName}`);
|
|
1947
3385
|
const attrs = {
|
|
1948
3386
|
accountId,
|
|
@@ -1950,12 +3388,17 @@ export const LiveWorkerProvider = () => Provider.effect(Worker, Effect.gen(funct
|
|
|
1950
3388
|
workerName,
|
|
1951
3389
|
namespace: undefined,
|
|
1952
3390
|
logpush: settings.logpush ?? undefined,
|
|
1953
|
-
url:
|
|
3391
|
+
url: urls[0],
|
|
3392
|
+
urls,
|
|
3393
|
+
domain: observedDomain,
|
|
1954
3394
|
tags: settings.tags ?? undefined,
|
|
1955
3395
|
durableObjectNamespaces: getDurableObjects(settings.bindings),
|
|
1956
|
-
domains,
|
|
1957
3396
|
routes: routesList,
|
|
1958
3397
|
crons,
|
|
3398
|
+
// Rule placement is provider-managed state, not observed here
|
|
3399
|
+
// (a getPhas call per known zone on every read); carry the
|
|
3400
|
+
// cleanup list forward like any other stable cache.
|
|
3401
|
+
affinityZoneIds: output?.affinityZoneIds,
|
|
1959
3402
|
};
|
|
1960
3403
|
// Centralized ownership decision: the engine routes `read`'s
|
|
1961
3404
|
// return value based on `AdoptPolicy`. We hand it the attrs
|
|
@@ -1973,6 +3416,12 @@ export const LiveWorkerProvider = () => Provider.effect(Worker, Effect.gen(funct
|
|
|
1973
3416
|
// `DispatchNamespaceScriptNotFound` / `DispatchNamespaceNotFound`.
|
|
1974
3417
|
Effect.catchTag("WorkerNotFound", () => Effect.succeed(undefined)), Effect.catchTag("WorkerHasNoVersions", () => Effect.succeed(undefined)), Effect.catchTag("DispatchNamespaceScriptNotFound", () => Effect.succeed(undefined)), Effect.catchTag("DispatchNamespaceNotFound", () => Effect.succeed(undefined)))),
|
|
1975
3418
|
reconcile: Effect.fn(function* ({ id, news, olds, bindings, output, session, }) {
|
|
3419
|
+
// A version worker uploads an immutable version to its parent's
|
|
3420
|
+
// script instead of owning a script of its own — none of the
|
|
3421
|
+
// script-level observation below applies.
|
|
3422
|
+
if (news.version?.parent != null) {
|
|
3423
|
+
return yield* putWorkerVersion(id, news, bindings, session, output);
|
|
3424
|
+
}
|
|
1976
3425
|
const { accountId } = yield* yield* CloudflareEnvironment;
|
|
1977
3426
|
const name = output?.workerName ?? (yield* createWorkerName(id, news.name));
|
|
1978
3427
|
const durableObjects = getDurableObjectBindings(bindings, name).map(({ logicalId, className }) => ({
|
|
@@ -2000,6 +3449,60 @@ export const LiveWorkerProvider = () => Provider.effect(Worker, Effect.gen(funct
|
|
|
2000
3449
|
return yield* putWorker(id, news, bindings, olds, output, session, existingSettings);
|
|
2001
3450
|
}),
|
|
2002
3451
|
delete: Effect.fn(function* ({ output }) {
|
|
3452
|
+
// Version workers own a version, not the script — `workerName` is
|
|
3453
|
+
// the *parent's* script and must never be deleted here. Versions
|
|
3454
|
+
// can't be deleted through the API (they age out of Cloudflare's
|
|
3455
|
+
// retention window); all we clean up is live traffic: if the
|
|
3456
|
+
// current deployment routes traffic to our version, restore 100%
|
|
3457
|
+
// to the other version in the split.
|
|
3458
|
+
if (output.versionOf !== undefined) {
|
|
3459
|
+
// Remove our affinity rules from the parent's zones — the
|
|
3460
|
+
// canary owned them, and with the canary gone there is no
|
|
3461
|
+
// split left to pin. Best-effort: a zone we can no longer
|
|
3462
|
+
// touch must not fail the destroy.
|
|
3463
|
+
if (output.affinityZoneIds?.length) {
|
|
3464
|
+
yield* reconcileAffinityRules({
|
|
3465
|
+
scriptName: output.versionOf,
|
|
3466
|
+
desired: undefined,
|
|
3467
|
+
previousZoneIds: output.affinityZoneIds,
|
|
3468
|
+
}).pipe(Effect.catch(() => Effect.void));
|
|
3469
|
+
}
|
|
3470
|
+
if (!output.versionId)
|
|
3471
|
+
return;
|
|
3472
|
+
yield* Effect.logInfo(`Cloudflare Worker delete: releasing version ${output.versionId} of ${output.versionOf}`);
|
|
3473
|
+
const { deployments } = yield* workers
|
|
3474
|
+
.listScriptDeployments({
|
|
3475
|
+
accountId: output.accountId,
|
|
3476
|
+
scriptName: output.versionOf,
|
|
3477
|
+
})
|
|
3478
|
+
.pipe(Effect.catchTag("WorkerNotFound", () => Effect.succeed({
|
|
3479
|
+
deployments: [],
|
|
3480
|
+
})));
|
|
3481
|
+
const latest = deployments[0];
|
|
3482
|
+
if (!latest?.versions.some((v) => v.versionId === output.versionId)) {
|
|
3483
|
+
// Not part of the live deployment — the version just ages out.
|
|
3484
|
+
return;
|
|
3485
|
+
}
|
|
3486
|
+
const stable = latest.versions
|
|
3487
|
+
.filter((v) => v.versionId !== output.versionId)
|
|
3488
|
+
.sort((a, b) => b.percentage - a.percentage)[0];
|
|
3489
|
+
if (!stable) {
|
|
3490
|
+
// Our version holds 100% of traffic; there is no other version
|
|
3491
|
+
// to restore. Deleting the resource must not take the parent's
|
|
3492
|
+
// script down — leave the deployment in place.
|
|
3493
|
+
yield* Effect.logWarning(`Cloudflare Worker delete: version ${output.versionId} serves 100% of '${output.versionOf}' traffic; leaving the deployment as-is. Re-deploy the parent to move off it.`);
|
|
3494
|
+
return;
|
|
3495
|
+
}
|
|
3496
|
+
yield* workers
|
|
3497
|
+
.createScriptDeployment({
|
|
3498
|
+
accountId: output.accountId,
|
|
3499
|
+
scriptName: output.versionOf,
|
|
3500
|
+
strategy: "percentage",
|
|
3501
|
+
versions: [{ versionId: stable.versionId, percentage: 100 }],
|
|
3502
|
+
})
|
|
3503
|
+
.pipe(Effect.catchTag("WorkerNotFound", () => Effect.void));
|
|
3504
|
+
return;
|
|
3505
|
+
}
|
|
2003
3506
|
yield* Effect.logInfo(`Cloudflare Worker delete: deleting ${output.workerName}`);
|
|
2004
3507
|
// Workers for Platforms user workers have no custom domains; delete
|
|
2005
3508
|
// the script straight out of its dispatch namespace.
|
|
@@ -2020,6 +3523,32 @@ export const LiveWorkerProvider = () => Provider.effect(Worker, Effect.gen(funct
|
|
|
2020
3523
|
service: output.workerName,
|
|
2021
3524
|
})
|
|
2022
3525
|
.pipe(Effect.map((r) => r.result ?? []), Effect.catch(() => Effect.succeed([])));
|
|
3526
|
+
// Remove our redirect rules from each affected zone's dynamic
|
|
3527
|
+
// redirect entrypoint *before* detaching the domains — the live
|
|
3528
|
+
// domain list is what resolves each redirect hostname's zone.
|
|
3529
|
+
// Reconciling with an empty domain config removes exactly the
|
|
3530
|
+
// rules tagged `alchemy:worker:<script>:redirect:*`, leaving
|
|
3531
|
+
// everything else in the shared entrypoint untouched.
|
|
3532
|
+
// Remove our version-affinity rules from every zone state says
|
|
3533
|
+
// holds them. Best-effort, like the redirect rules below.
|
|
3534
|
+
if (output.affinityZoneIds?.length) {
|
|
3535
|
+
yield* reconcileAffinityRules({
|
|
3536
|
+
scriptName: output.workerName,
|
|
3537
|
+
desired: undefined,
|
|
3538
|
+
previousZoneIds: output.affinityZoneIds,
|
|
3539
|
+
}).pipe(Effect.catch(() => Effect.void));
|
|
3540
|
+
}
|
|
3541
|
+
const redirects = stateWorkerDomain(output)?.redirects ?? [];
|
|
3542
|
+
if (redirects.length > 0) {
|
|
3543
|
+
yield* reconcileRedirectRules({
|
|
3544
|
+
scriptName: output.workerName,
|
|
3545
|
+
domain: undefined,
|
|
3546
|
+
zoneIdByHostname: new Map(liveDomains.flatMap((d) => d.hostname && d.zoneId
|
|
3547
|
+
? [[d.hostname, d.zoneId]]
|
|
3548
|
+
: [])),
|
|
3549
|
+
previousRedirects: redirects,
|
|
3550
|
+
}).pipe(Effect.catch(() => Effect.void));
|
|
3551
|
+
}
|
|
2023
3552
|
if (liveDomains.length) {
|
|
2024
3553
|
yield* Effect.all(liveDomains.flatMap((d) => d.id
|
|
2025
3554
|
? [
|