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.ts";
|
|
14
19
|
import * as Artifacts from "../../Artifacts.ts";
|
|
@@ -19,10 +24,12 @@ import * as ProviderLayer from "../../Local/ProviderLayer.ts";
|
|
|
19
24
|
import * as Provider from "../../Provider.ts";
|
|
20
25
|
import { type ResourceBinding } from "../../Resource.ts";
|
|
21
26
|
import { Stack } from "../../Stack.ts";
|
|
27
|
+
import { cachedFunction } from "../../Util/cached-function.ts";
|
|
22
28
|
import { sha256Object } from "../../Util/sha256.ts";
|
|
23
29
|
import { CloudflareEnvironment } from "../CloudflareEnvironment.ts";
|
|
30
|
+
import { localRuntimeServices } from "../LocalRuntime.ts";
|
|
24
31
|
import { CloudflareLogs } from "../Logs.ts";
|
|
25
|
-
import {
|
|
32
|
+
import { resolveZoneId } from "../Zone/lookup.ts";
|
|
26
33
|
import {
|
|
27
34
|
mergeAssetsConfigFiles,
|
|
28
35
|
readAssets,
|
|
@@ -33,13 +40,23 @@ import { getCompatibility } from "./Compatibility.ts";
|
|
|
33
40
|
import { isDurableObjectExport } from "./DurableObject.ts";
|
|
34
41
|
import { LocalWorkerProvider } from "./LocalWorkerProvider.ts";
|
|
35
42
|
import {
|
|
43
|
+
isSelfUrl,
|
|
36
44
|
Worker,
|
|
37
45
|
type ViteOptions,
|
|
38
46
|
type WorkerProps,
|
|
39
47
|
type WorkerRouteConfig,
|
|
48
|
+
type WorkerVersionAffinity,
|
|
40
49
|
} from "./Worker.ts";
|
|
41
|
-
import {
|
|
42
|
-
|
|
50
|
+
import {
|
|
51
|
+
getCacheBinding,
|
|
52
|
+
getCronBindings,
|
|
53
|
+
isContainerDecl,
|
|
54
|
+
} from "./WorkerAsyncBindings.ts";
|
|
55
|
+
import type {
|
|
56
|
+
WireWorkerBinding,
|
|
57
|
+
WorkerBinding,
|
|
58
|
+
WorkerSettingsBinding,
|
|
59
|
+
} from "./WorkerBinding.ts";
|
|
43
60
|
import { isPythonMain, readPythonWorkerBundle } from "./PythonWorkerBundle.ts";
|
|
44
61
|
import { readPrebuiltWorkerBundle, WorkerBundle } from "./WorkerBundle.ts";
|
|
45
62
|
import { isWorkerLoader } from "./WorkerLoader.ts";
|
|
@@ -125,6 +142,433 @@ export const resolveNamespaceName = (
|
|
|
125
142
|
return (namespace as { name?: string }).name;
|
|
126
143
|
};
|
|
127
144
|
|
|
145
|
+
/**
|
|
146
|
+
* A Worker's `version` configuration is invalid — a prop that can't be
|
|
147
|
+
* combined with `version.parent` (script-level settings belong to the
|
|
148
|
+
* parent), a locally-hosted Durable Object / Workflow class on a version
|
|
149
|
+
* worker, an out-of-range `traffic`, or a gradual rollout that requires
|
|
150
|
+
* changes the versions API can't carry (assets, DO migrations).
|
|
151
|
+
*/
|
|
152
|
+
export class WorkerVersionConfigError extends Data.TaggedError(
|
|
153
|
+
"WorkerVersionConfigError",
|
|
154
|
+
)<{
|
|
155
|
+
message: string;
|
|
156
|
+
}> {}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Resolve the parent script *name* from a resolved `version.parent` prop or
|
|
160
|
+
* persisted props. The engine resolves a passed {@link Worker} (or
|
|
161
|
+
* `Worker.ref(...)`) to its Attributes object — possibly stables-only during
|
|
162
|
+
* planning, but `workerName` is always a stable — so the value is either the
|
|
163
|
+
* script name string, that attributes object, or `undefined`.
|
|
164
|
+
*
|
|
165
|
+
* @internal
|
|
166
|
+
*/
|
|
167
|
+
export const resolveVersionParentName = (
|
|
168
|
+
version: WorkerProps["version"],
|
|
169
|
+
): string | undefined => {
|
|
170
|
+
const parent = version?.parent;
|
|
171
|
+
if (parent == null) return undefined;
|
|
172
|
+
if (typeof parent === "string") return parent;
|
|
173
|
+
const workerName = (parent as { workerName?: unknown }).workerName;
|
|
174
|
+
return typeof workerName === "string" ? workerName : undefined;
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* The traffic percentage a *self-owned* Worker's new version should receive,
|
|
179
|
+
* or `undefined` for the default full cutover. Only a `version` prop without
|
|
180
|
+
* a `parent` participates — version workers handle traffic separately.
|
|
181
|
+
*
|
|
182
|
+
* @internal
|
|
183
|
+
*/
|
|
184
|
+
const getSelfRolloutTraffic = (news: WorkerProps): number | undefined => {
|
|
185
|
+
if (!news.version || news.version.parent != null) return undefined;
|
|
186
|
+
const traffic = news.version.traffic;
|
|
187
|
+
return traffic === undefined || traffic >= 100 ? undefined : traffic;
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const validateTraffic = (traffic: number | undefined) =>
|
|
191
|
+
traffic !== undefined &&
|
|
192
|
+
(!Number.isFinite(traffic) || traffic < 0 || traffic > 100)
|
|
193
|
+
? Effect.fail(
|
|
194
|
+
new WorkerVersionConfigError({
|
|
195
|
+
message: `version.traffic must be a percentage between 0 and 100, got ${traffic}`,
|
|
196
|
+
}),
|
|
197
|
+
)
|
|
198
|
+
: Effect.void;
|
|
199
|
+
|
|
200
|
+
/** The request header Cloudflare hashes to pin a request to a version. */
|
|
201
|
+
const AFFINITY_HEADER = "Cloudflare-Workers-Version-Key";
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* `version.affinity` normalized to a single key source plus the optional
|
|
205
|
+
* IP fallback.
|
|
206
|
+
*
|
|
207
|
+
* @internal exported for unit testing.
|
|
208
|
+
*/
|
|
209
|
+
export interface ResolvedVersionAffinity {
|
|
210
|
+
source:
|
|
211
|
+
| { kind: "cookie" | "header"; name: string }
|
|
212
|
+
| { kind: "ip" }
|
|
213
|
+
| { kind: "key"; expression: string };
|
|
214
|
+
ipFallback: boolean;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Cookie / header names are interpolated into a double-quoted Rules-language
|
|
218
|
+
// string literal — restrict them to the token characters real-world names
|
|
219
|
+
// use so a name can never terminate the literal or smuggle expression text.
|
|
220
|
+
const AFFINITY_NAME_PATTERN = /^[A-Za-z0-9_.-]+$/;
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Validate `version.affinity` and normalize it to its key source: exactly
|
|
224
|
+
* one of `cookie` / `header` / `key`, or `ip: true` alone; `ip` combines
|
|
225
|
+
* with `cookie` / `header` as the absent-source fallback.
|
|
226
|
+
*
|
|
227
|
+
* @internal exported for unit testing.
|
|
228
|
+
*/
|
|
229
|
+
export const resolveVersionAffinity = (
|
|
230
|
+
affinity: WorkerVersionAffinity,
|
|
231
|
+
): Effect.Effect<ResolvedVersionAffinity, WorkerVersionConfigError> =>
|
|
232
|
+
Effect.gen(function* () {
|
|
233
|
+
const declared = [
|
|
234
|
+
...(affinity.cookie !== undefined ? ["cookie"] : []),
|
|
235
|
+
...(affinity.header !== undefined ? ["header"] : []),
|
|
236
|
+
...(affinity.key !== undefined ? ["key"] : []),
|
|
237
|
+
];
|
|
238
|
+
if (declared.length > 1) {
|
|
239
|
+
return yield* Effect.fail(
|
|
240
|
+
new WorkerVersionConfigError({
|
|
241
|
+
message: `version.affinity accepts exactly one key source, got ${declared.join(" and ")}. Combine sources with a raw \`key\` expression instead.`,
|
|
242
|
+
}),
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
if (declared.length === 0 && affinity.ip !== true) {
|
|
246
|
+
return yield* Effect.fail(
|
|
247
|
+
new WorkerVersionConfigError({
|
|
248
|
+
message:
|
|
249
|
+
"version.affinity requires a key source: set `cookie`, `header`, `key`, or `ip: true`.",
|
|
250
|
+
}),
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
if (affinity.key !== undefined && affinity.ip === true) {
|
|
254
|
+
return yield* Effect.fail(
|
|
255
|
+
new WorkerVersionConfigError({
|
|
256
|
+
message:
|
|
257
|
+
"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.",
|
|
258
|
+
}),
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
for (const [prop, name] of [
|
|
262
|
+
["cookie", affinity.cookie],
|
|
263
|
+
["header", affinity.header],
|
|
264
|
+
] as const) {
|
|
265
|
+
if (name !== undefined && !AFFINITY_NAME_PATTERN.test(name)) {
|
|
266
|
+
return yield* Effect.fail(
|
|
267
|
+
new WorkerVersionConfigError({
|
|
268
|
+
message: `version.affinity.${prop} '${name}' is not a valid ${prop} name: expected only letters, digits, '_', '.', and '-'.`,
|
|
269
|
+
}),
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
const source: ResolvedVersionAffinity["source"] =
|
|
274
|
+
affinity.cookie !== undefined
|
|
275
|
+
? { kind: "cookie", name: affinity.cookie }
|
|
276
|
+
: affinity.header !== undefined
|
|
277
|
+
? // Rules-language header map keys are lowercase.
|
|
278
|
+
{ kind: "header", name: affinity.header.toLowerCase() }
|
|
279
|
+
: affinity.key !== undefined
|
|
280
|
+
? { kind: "key", expression: affinity.key }
|
|
281
|
+
: { kind: "ip" };
|
|
282
|
+
return {
|
|
283
|
+
source,
|
|
284
|
+
ipFallback:
|
|
285
|
+
affinity.ip === true &&
|
|
286
|
+
(source.kind === "cookie" || source.kind === "header"),
|
|
287
|
+
};
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
/** A hostname a Worker serves on within one zone. */
|
|
291
|
+
interface AffinityZoneHost {
|
|
292
|
+
host: string;
|
|
293
|
+
/** `true` when `host` came from a route pattern containing `*`. */
|
|
294
|
+
wildcard: boolean;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* The `http.host` clause scoping a zone's affinity rules to the Worker's
|
|
299
|
+
* own hostnames, so unrelated zone traffic — and other Workers' rollouts
|
|
300
|
+
* on the same zone — never get this Worker's version key.
|
|
301
|
+
*
|
|
302
|
+
* @internal exported for unit testing.
|
|
303
|
+
*/
|
|
304
|
+
export const affinityHostExpression = (
|
|
305
|
+
hosts: readonly AffinityZoneHost[],
|
|
306
|
+
): string => {
|
|
307
|
+
const dedupe = (values: string[]) => [...new Set(values)].sort();
|
|
308
|
+
const exact = dedupe(hosts.filter((h) => !h.wildcard).map((h) => h.host));
|
|
309
|
+
const wild = dedupe(hosts.filter((h) => h.wildcard).map((h) => h.host));
|
|
310
|
+
const clauses = [
|
|
311
|
+
...(exact.length === 1
|
|
312
|
+
? [`http.host eq "${exact[0]}"`]
|
|
313
|
+
: exact.length > 1
|
|
314
|
+
? [`http.host in {${exact.map((h) => `"${h}"`).join(" ")}}`]
|
|
315
|
+
: []),
|
|
316
|
+
...wild.map((h) => `http.host wildcard "${h}"`),
|
|
317
|
+
];
|
|
318
|
+
return clauses.length === 1 ? clauses[0] : `(${clauses.join(" or ")})`;
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
interface AffinityRuleSpec {
|
|
322
|
+
description: string;
|
|
323
|
+
expression: string;
|
|
324
|
+
/** Rules-language expression producing the header value. */
|
|
325
|
+
value: string;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const affinityRulePrefix = (scriptName: string) =>
|
|
329
|
+
`alchemy:worker:${scriptName}:affinity`;
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* The transform rules pinning one zone's traffic: a primary rule filling
|
|
333
|
+
* the version-key header from the configured source, plus — for
|
|
334
|
+
* `cookie`/`header` sources with `ip: true` — a fallback rule keying
|
|
335
|
+
* requests that lack the source by client IP.
|
|
336
|
+
*
|
|
337
|
+
* @internal exported for unit testing.
|
|
338
|
+
*/
|
|
339
|
+
export const buildAffinityZoneRules = (
|
|
340
|
+
scriptName: string,
|
|
341
|
+
affinity: ResolvedVersionAffinity,
|
|
342
|
+
hosts: readonly AffinityZoneHost[],
|
|
343
|
+
): AffinityRuleSpec[] => {
|
|
344
|
+
const prefix = affinityRulePrefix(scriptName);
|
|
345
|
+
const hostExpr = affinityHostExpression(hosts);
|
|
346
|
+
const { source } = affinity;
|
|
347
|
+
if (source.kind === "ip" || source.kind === "key") {
|
|
348
|
+
return [
|
|
349
|
+
{
|
|
350
|
+
description: `${prefix}:key`,
|
|
351
|
+
expression: hostExpr,
|
|
352
|
+
value: source.kind === "ip" ? "to_string(ip.src)" : source.expression,
|
|
353
|
+
},
|
|
354
|
+
];
|
|
355
|
+
}
|
|
356
|
+
const field =
|
|
357
|
+
source.kind === "cookie"
|
|
358
|
+
? `http.request.cookies["${source.name}"]`
|
|
359
|
+
: `http.request.headers["${source.name}"]`;
|
|
360
|
+
return [
|
|
361
|
+
{
|
|
362
|
+
description: `${prefix}:key`,
|
|
363
|
+
expression: `${hostExpr} and len(${field}) > 0`,
|
|
364
|
+
value: `${field}[0]`,
|
|
365
|
+
},
|
|
366
|
+
...(affinity.ipFallback
|
|
367
|
+
? [
|
|
368
|
+
{
|
|
369
|
+
// An absent cookie/header is a *missing* value in the Rules
|
|
370
|
+
// language, and every comparison on missing evaluates false —
|
|
371
|
+
// `len(field) == 0` can never match. `not (len(field) > 0)`
|
|
372
|
+
// is the complement that does: missing → false → `not` → true.
|
|
373
|
+
description: `${prefix}:ip`,
|
|
374
|
+
expression: `${hostExpr} and not (len(${field}) > 0)`,
|
|
375
|
+
value: "to_string(ip.src)",
|
|
376
|
+
},
|
|
377
|
+
]
|
|
378
|
+
: []),
|
|
379
|
+
];
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* A Worker's `domain` configuration is invalid — a hostname appears in more
|
|
384
|
+
* than one role (name/aliases/redirects), or a redirect targets itself.
|
|
385
|
+
*/
|
|
386
|
+
export class WorkerDomainConfigError extends Data.TaggedError(
|
|
387
|
+
"WorkerDomainConfigError",
|
|
388
|
+
)<{
|
|
389
|
+
message: string;
|
|
390
|
+
}> {}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* The resolved shape of `WorkerProps.workersDev`: `enabled` drives the
|
|
394
|
+
* stable `<name>.<account>.workers.dev` URL, `previewsEnabled` the
|
|
395
|
+
* per-version preview URLs. The two toggles are independent on the
|
|
396
|
+
* Cloudflare API.
|
|
397
|
+
*
|
|
398
|
+
* @internal exported for unit testing.
|
|
399
|
+
*/
|
|
400
|
+
export interface ResolvedWorkersDev {
|
|
401
|
+
enabled: boolean;
|
|
402
|
+
previewsEnabled: boolean;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Resolve the `workersDev` prop to its full shape. `true` / omitted means
|
|
407
|
+
* "default workers.dev behavior" (stable URL + version previews), `false`
|
|
408
|
+
* disables both, and the object form fills unset toggles with `true`.
|
|
409
|
+
*
|
|
410
|
+
* @internal exported for unit testing.
|
|
411
|
+
*/
|
|
412
|
+
export const resolveWorkersDev = (
|
|
413
|
+
workersDev: WorkerProps["workersDev"],
|
|
414
|
+
): ResolvedWorkersDev => {
|
|
415
|
+
if (workersDev === undefined || workersDev === true) {
|
|
416
|
+
return { enabled: true, previewsEnabled: true };
|
|
417
|
+
}
|
|
418
|
+
if (workersDev === false) {
|
|
419
|
+
return { enabled: false, previewsEnabled: false };
|
|
420
|
+
}
|
|
421
|
+
return {
|
|
422
|
+
enabled: workersDev.enabled ?? true,
|
|
423
|
+
previewsEnabled: workersDev.previewsEnabled ?? true,
|
|
424
|
+
};
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* The resolved shape of `WorkerProps.domain`: the canonical hostname plus
|
|
429
|
+
* alias and redirect hostname lists, all punycode-normalized and
|
|
430
|
+
* de-duplicated.
|
|
431
|
+
*
|
|
432
|
+
* @internal exported for unit testing.
|
|
433
|
+
*/
|
|
434
|
+
export interface ResolvedWorkerDomain {
|
|
435
|
+
name: string;
|
|
436
|
+
aliases: string[];
|
|
437
|
+
redirects: string[];
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Convert non-ASCII hostnames (emoji, IDN, etc.) to punycode so the
|
|
441
|
+
// Cloudflare API receives the form it stores domains in. `new URL(...)`
|
|
442
|
+
// does IDNA via WHATWG URL parsing — `📦.alchemy.run` → `xn--5z8h.alchemy.run`.
|
|
443
|
+
const toPunycode = (hostname: string): string => {
|
|
444
|
+
try {
|
|
445
|
+
return new URL(`https://${hostname}`).hostname;
|
|
446
|
+
} catch {
|
|
447
|
+
return hostname;
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Resolve the `domain` prop to its full shape — a bare string is shorthand
|
|
453
|
+
* for `{ name }`. Hostnames are punycode-normalized and de-duplicated;
|
|
454
|
+
* a hostname may only play one role, so aliases/redirects that repeat the
|
|
455
|
+
* canonical name (or each other) fail with a typed error.
|
|
456
|
+
*
|
|
457
|
+
* @internal exported for unit testing.
|
|
458
|
+
*/
|
|
459
|
+
export const resolveWorkerDomain = (
|
|
460
|
+
// `string[]` is the pre-redesign prop shape — it can still reach us from
|
|
461
|
+
// persisted `olds` written by older providers (read's classification).
|
|
462
|
+
domain: WorkerProps["domain"] | string[],
|
|
463
|
+
): Effect.Effect<ResolvedWorkerDomain | undefined, WorkerDomainConfigError> =>
|
|
464
|
+
Effect.gen(function* () {
|
|
465
|
+
if (domain === undefined || domain === null) return undefined;
|
|
466
|
+
// Legacy array form: the first hostname was the primary custom domain
|
|
467
|
+
// (`url = domains[0]` back then), the rest map to aliases. A legacy
|
|
468
|
+
// empty array was the explicit detach-all — no domain.
|
|
469
|
+
const config =
|
|
470
|
+
typeof domain === "string"
|
|
471
|
+
? { name: domain }
|
|
472
|
+
: Array.isArray(domain)
|
|
473
|
+
? domain.length > 0
|
|
474
|
+
? { name: domain[0], aliases: domain.slice(1) }
|
|
475
|
+
: undefined
|
|
476
|
+
: domain;
|
|
477
|
+
if (config === undefined) return undefined;
|
|
478
|
+
const name = toPunycode(config.name);
|
|
479
|
+
const aliases = Array.from(new Set((config.aliases ?? []).map(toPunycode)));
|
|
480
|
+
const redirects = Array.from(
|
|
481
|
+
new Set((config.redirects ?? []).map(toPunycode)),
|
|
482
|
+
);
|
|
483
|
+
const overlap = [
|
|
484
|
+
...aliases.filter((h) => h === name),
|
|
485
|
+
...redirects.filter((h) => h === name || aliases.includes(h)),
|
|
486
|
+
];
|
|
487
|
+
if (overlap.length > 0) {
|
|
488
|
+
return yield* Effect.fail(
|
|
489
|
+
new WorkerDomainConfigError({
|
|
490
|
+
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.`,
|
|
491
|
+
}),
|
|
492
|
+
);
|
|
493
|
+
}
|
|
494
|
+
return { name, aliases, redirects };
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
const isWorkersDevHostname = (hostname: string) =>
|
|
498
|
+
hostname.endsWith(".workers.dev");
|
|
499
|
+
|
|
500
|
+
// Hostnames that only appear in local-dev state (the dev server's
|
|
501
|
+
// localhost/LAN URLs), never as attachable custom domains.
|
|
502
|
+
const isLocalDevHostname = (hostname: string) =>
|
|
503
|
+
hostname === "localhost" ||
|
|
504
|
+
hostname === "::1" ||
|
|
505
|
+
/^\d+\.\d+\.\d+\.\d+$/.test(hostname);
|
|
506
|
+
|
|
507
|
+
const urlHostname = (url: string): string => {
|
|
508
|
+
try {
|
|
509
|
+
return new URL(url).hostname;
|
|
510
|
+
} catch {
|
|
511
|
+
return url;
|
|
512
|
+
}
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
/** The `rules` payload shape of a zone phase-entrypoint PUT. */
|
|
516
|
+
type PutZoneRedirectRules = rulesets.PutPhasForZoneRequest["rules"];
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* The *custom domain* hostnames recorded in a Worker's persisted legacy
|
|
520
|
+
* `domains` state — minus the workers.dev (stable or preview) and local-dev
|
|
521
|
+
* entries that shared the list in older formats.
|
|
522
|
+
*
|
|
523
|
+
* @internal exported for unit testing.
|
|
524
|
+
*/
|
|
525
|
+
export const stateCustomDomains = (
|
|
526
|
+
domains: readonly unknown[] | undefined,
|
|
527
|
+
): string[] =>
|
|
528
|
+
normalizeStateDomains(domains).filter(
|
|
529
|
+
(hostname) =>
|
|
530
|
+
!isWorkersDevHostname(hostname) && !isLocalDevHostname(hostname),
|
|
531
|
+
);
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* The Worker's persisted domain configuration: the `domain` attribute for
|
|
535
|
+
* state written by the current format, else re-derived from the legacy
|
|
536
|
+
* `domains` list (first custom hostname = canonical name, rest = aliases;
|
|
537
|
+
* legacy state had no redirects).
|
|
538
|
+
*
|
|
539
|
+
* @internal exported for unit testing.
|
|
540
|
+
*/
|
|
541
|
+
export const stateWorkerDomain = (
|
|
542
|
+
output: object | undefined,
|
|
543
|
+
): ResolvedWorkerDomain | undefined => {
|
|
544
|
+
const state = output as
|
|
545
|
+
| {
|
|
546
|
+
domain?: {
|
|
547
|
+
name?: unknown;
|
|
548
|
+
aliases?: unknown[];
|
|
549
|
+
redirects?: unknown[];
|
|
550
|
+
} | null;
|
|
551
|
+
domains?: unknown[];
|
|
552
|
+
}
|
|
553
|
+
| undefined;
|
|
554
|
+
const domain = state?.domain;
|
|
555
|
+
if (domain && typeof domain.name === "string") {
|
|
556
|
+
return {
|
|
557
|
+
name: domain.name,
|
|
558
|
+
aliases: (domain.aliases ?? []).filter(
|
|
559
|
+
(h): h is string => typeof h === "string",
|
|
560
|
+
),
|
|
561
|
+
redirects: (domain.redirects ?? []).filter(
|
|
562
|
+
(h): h is string => typeof h === "string",
|
|
563
|
+
),
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
const legacy = stateCustomDomains(state?.domains);
|
|
567
|
+
return legacy.length > 0
|
|
568
|
+
? { name: legacy[0], aliases: legacy.slice(1), redirects: [] }
|
|
569
|
+
: undefined;
|
|
570
|
+
};
|
|
571
|
+
|
|
128
572
|
// Workers for Platforms "user workers" live inside a dispatch namespace and
|
|
129
573
|
// use a parallel family of script endpoints (`/workers/dispatch/namespaces/
|
|
130
574
|
// :namespace/scripts/...`). The request/response shapes are identical to the
|
|
@@ -181,7 +625,8 @@ const getScriptSettings = (
|
|
|
181
625
|
const isBindingTargetNotFound = (
|
|
182
626
|
e:
|
|
183
627
|
| Effect.Error<ReturnType<typeof workers.putScript>>
|
|
184
|
-
| Effect.Error<ReturnType<typeof wfp.putDispatchNamespaceScript
|
|
628
|
+
| Effect.Error<ReturnType<typeof wfp.putDispatchNamespaceScript>>
|
|
629
|
+
| Effect.Error<ReturnType<typeof workers.createScriptVersion>>,
|
|
185
630
|
): boolean =>
|
|
186
631
|
e._tag === "SecretsStoreBindingNotFound" ||
|
|
187
632
|
e._tag === "KVNamespaceNotFound" ||
|
|
@@ -198,6 +643,20 @@ const isBindingTargetNotFound = (
|
|
|
198
643
|
const bindingTargetNotFoundRetrySchedule = () =>
|
|
199
644
|
Schedule.max([Schedule.fixed("2 seconds"), Schedule.recurs(10)]);
|
|
200
645
|
|
|
646
|
+
/**
|
|
647
|
+
* Script PUT is an idempotent upsert, so a pure transport failure (the
|
|
648
|
+
* request died before any response — e.g. Cloudflare closing a keep-alive
|
|
649
|
+
* socket that idled while slow upstream resources provisioned earlier in the
|
|
650
|
+
* deploy) is safe to replay. Errors that carry a response are real API
|
|
651
|
+
* verdicts and are NOT retried here.
|
|
652
|
+
*/
|
|
653
|
+
const isScriptPutTransportError = (e: { _tag?: string }): boolean =>
|
|
654
|
+
isHttpClientError(e) && e.reason._tag === "TransportError";
|
|
655
|
+
|
|
656
|
+
const retryableScriptPut = (
|
|
657
|
+
e: Parameters<typeof isBindingTargetNotFound>[0],
|
|
658
|
+
): boolean => isBindingTargetNotFound(e) || isScriptPutTransportError(e);
|
|
659
|
+
|
|
201
660
|
/**
|
|
202
661
|
* Upsert a Worker script, routing to the dispatch-namespace endpoint when
|
|
203
662
|
* `dispatchNamespace` is set. The metadata/files contract is identical, and
|
|
@@ -226,7 +685,7 @@ const putWorkerScript = (params: {
|
|
|
226
685
|
})
|
|
227
686
|
.pipe(
|
|
228
687
|
Effect.retry({
|
|
229
|
-
while:
|
|
688
|
+
while: retryableScriptPut,
|
|
230
689
|
schedule: bindingTargetNotFoundRetrySchedule(),
|
|
231
690
|
}),
|
|
232
691
|
);
|
|
@@ -240,7 +699,7 @@ const putWorkerScript = (params: {
|
|
|
240
699
|
})
|
|
241
700
|
.pipe(
|
|
242
701
|
Effect.retry({
|
|
243
|
-
while:
|
|
702
|
+
while: retryableScriptPut,
|
|
244
703
|
schedule: bindingTargetNotFoundRetrySchedule(),
|
|
245
704
|
}),
|
|
246
705
|
);
|
|
@@ -270,14 +729,14 @@ const deleteWorkerScript = (
|
|
|
270
729
|
});
|
|
271
730
|
|
|
272
731
|
/**
|
|
273
|
-
* Normalize a Worker's persisted `domains` state to
|
|
274
|
-
*
|
|
275
|
-
* `{ id, hostname, zoneId }` object; beta.45+
|
|
276
|
-
* strings
|
|
277
|
-
*
|
|
278
|
-
*
|
|
279
|
-
*
|
|
280
|
-
*
|
|
732
|
+
* Normalize a Worker's persisted *legacy* `domains` state to bare
|
|
733
|
+
* hostnames. Alchemy <= beta.44 stored each custom domain as a
|
|
734
|
+
* `{ id, hostname, zoneId }` object; beta.45+ stored `https://<hostname>`
|
|
735
|
+
* URL strings (with the workers.dev URL mixed in); current state stores the
|
|
736
|
+
* `domain` config object instead of a `domains` list. All legacy
|
|
737
|
+
* generations coerce to hostnames so the diff never throws on older state
|
|
738
|
+
* (#546). Entries that fit no generation are dropped rather than turned
|
|
739
|
+
* into a bogus hostname that would skew the diff.
|
|
281
740
|
*
|
|
282
741
|
* @internal exported for unit testing.
|
|
283
742
|
*/
|
|
@@ -285,11 +744,79 @@ export const normalizeStateDomains = (
|
|
|
285
744
|
domains: readonly unknown[] | undefined,
|
|
286
745
|
): string[] =>
|
|
287
746
|
(domains ?? []).flatMap((u) => {
|
|
288
|
-
if (typeof u === "string")
|
|
747
|
+
if (typeof u === "string") {
|
|
748
|
+
if (u.includes("://")) {
|
|
749
|
+
try {
|
|
750
|
+
return [new URL(u).hostname];
|
|
751
|
+
} catch {
|
|
752
|
+
return [];
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
return u.length > 0 ? [u] : [];
|
|
756
|
+
}
|
|
289
757
|
const hostname = (u as { hostname?: unknown } | null)?.hostname;
|
|
290
|
-
return typeof hostname === "string" ? [
|
|
758
|
+
return typeof hostname === "string" ? [hostname] : [];
|
|
291
759
|
});
|
|
292
760
|
|
|
761
|
+
/**
|
|
762
|
+
* Custom domains Alchemy is responsible for on this Worker — either declared
|
|
763
|
+
* on props (`domain`) or already persisted as non-`workers.dev` URLs in state.
|
|
764
|
+
* Used by `read` to skip `listDomains` when the surface is unmanaged (#926).
|
|
765
|
+
*
|
|
766
|
+
* @internal exported for unit testing.
|
|
767
|
+
*/
|
|
768
|
+
export const shouldObserveWorkerDomains = (
|
|
769
|
+
olds: Pick<WorkerProps, "domain"> | undefined,
|
|
770
|
+
output: object | undefined,
|
|
771
|
+
): boolean =>
|
|
772
|
+
olds?.domain !== undefined || stateWorkerDomain(output) !== undefined;
|
|
773
|
+
|
|
774
|
+
/**
|
|
775
|
+
* Zone routes Alchemy is responsible for on this Worker. Used by `read` to
|
|
776
|
+
* skip account-wide zone/route fan-out when the surface is unmanaged (#926).
|
|
777
|
+
*
|
|
778
|
+
* @internal exported for unit testing.
|
|
779
|
+
*/
|
|
780
|
+
export const shouldObserveWorkerRoutes = (
|
|
781
|
+
olds: Pick<WorkerProps, "routes"> | undefined,
|
|
782
|
+
output: Pick<Worker["Attributes"], "routes"> | undefined,
|
|
783
|
+
): boolean => olds?.routes !== undefined || (output?.routes?.length ?? 0) > 0;
|
|
784
|
+
|
|
785
|
+
/**
|
|
786
|
+
* Cron triggers Alchemy is responsible for on this Worker. Used by `read` to
|
|
787
|
+
* skip `getScriptSchedule` when the surface is unmanaged (#926). Effect-native
|
|
788
|
+
* `cron()` bindings persist into `output.crons` after the first reconcile, so
|
|
789
|
+
* subsequent reads still observe them.
|
|
790
|
+
*
|
|
791
|
+
* @internal exported for unit testing.
|
|
792
|
+
*/
|
|
793
|
+
export const shouldObserveWorkerCrons = (
|
|
794
|
+
olds: Pick<WorkerProps, "crons"> | undefined,
|
|
795
|
+
output: Pick<Worker["Attributes"], "crons"> | undefined,
|
|
796
|
+
): boolean => olds?.crons !== undefined || (output?.crons?.length ?? 0) > 0;
|
|
797
|
+
|
|
798
|
+
/**
|
|
799
|
+
* Optional override for the account's stable `workers.dev` subdomain
|
|
800
|
+
* (`<subdomain>` in `https://<script>.<subdomain>.workers.dev`). When set,
|
|
801
|
+
* Worker URL construction skips `GET /accounts/{id}/workers/subdomain`.
|
|
802
|
+
*/
|
|
803
|
+
const CLOUDFLARE_WORKERS_SUBDOMAIN = Config.string(
|
|
804
|
+
"CLOUDFLARE_WORKERS_SUBDOMAIN",
|
|
805
|
+
).pipe(
|
|
806
|
+
Config.map((value) => value.trim()),
|
|
807
|
+
Config.option,
|
|
808
|
+
);
|
|
809
|
+
|
|
810
|
+
const WORKERS_SUBDOMAIN_PATTERN = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
|
|
811
|
+
|
|
812
|
+
/**
|
|
813
|
+
* Max concurrent `GET /zones/{id}/workers/routes` calls when observing the
|
|
814
|
+
* routes attached to a Worker. Route listing is per-zone, so this fans out
|
|
815
|
+
* with the number of zones the Worker has routes in; unbounded fan-out here
|
|
816
|
+
* is what trips Cloudflare account-level 429 / code 971 throttling (#926).
|
|
817
|
+
*/
|
|
818
|
+
const WORKER_ROUTE_LIST_CONCURRENCY = 8;
|
|
819
|
+
|
|
293
820
|
type MetadataHashValue =
|
|
294
821
|
| string
|
|
295
822
|
| number
|
|
@@ -379,6 +906,13 @@ interface WorkerMetadataHashInput {
|
|
|
379
906
|
readonly bindings: readonly ResourceBinding<Worker["Binding"]>[];
|
|
380
907
|
readonly accountId: string;
|
|
381
908
|
readonly stack: { readonly name: string; readonly stage: string };
|
|
909
|
+
/**
|
|
910
|
+
* The resolved URL a `Worker.URL` (`self_url`) binding lowers into.
|
|
911
|
+
* Included in the hash so external URL changes (an account-subdomain
|
|
912
|
+
* rename, a custom-domain reorder) redeploy the Worker with the fresh
|
|
913
|
+
* value even though props and binding data are unchanged.
|
|
914
|
+
*/
|
|
915
|
+
readonly selfUrl?: string;
|
|
382
916
|
}
|
|
383
917
|
|
|
384
918
|
// The asset router config the resource declares (htmlHandling,
|
|
@@ -409,9 +943,11 @@ const resolveWorkerMetadataHash = ({
|
|
|
409
943
|
bindings,
|
|
410
944
|
accountId,
|
|
411
945
|
stack,
|
|
946
|
+
selfUrl,
|
|
412
947
|
}: WorkerMetadataHashInput): Effect.Effect<string> =>
|
|
413
948
|
resolveMetadataHashValue({
|
|
414
949
|
accountId,
|
|
950
|
+
selfUrl,
|
|
415
951
|
stack: { name: stack.name, stage: stack.stage },
|
|
416
952
|
compatibility: getCompatibility(props),
|
|
417
953
|
env: props.env,
|
|
@@ -425,15 +961,33 @@ const resolveWorkerMetadataHash = ({
|
|
|
425
961
|
logpush: props.logpush,
|
|
426
962
|
observability: props.observability,
|
|
427
963
|
placement: props.placement,
|
|
428
|
-
subdomain: props.subdomain,
|
|
429
964
|
tags: props.tags,
|
|
430
|
-
|
|
965
|
+
workersDev: resolveWorkersDev(props.workersDev),
|
|
966
|
+
// Reduce `version.parent` to the parent's script name: the resolved
|
|
967
|
+
// parent is a full attributes object whose *other* fields (hash, url,
|
|
968
|
+
// ...) change on every parent deploy, which would spuriously re-version
|
|
969
|
+
// this Worker even though nothing about the version itself changed.
|
|
970
|
+
version: props.version
|
|
971
|
+
? {
|
|
972
|
+
parent: resolveVersionParentName(props.version),
|
|
973
|
+
traffic: props.version.traffic,
|
|
974
|
+
alias: props.version.alias,
|
|
975
|
+
affinity: props.version.affinity,
|
|
976
|
+
message: props.version.message,
|
|
977
|
+
tag: props.version.tag,
|
|
978
|
+
}
|
|
979
|
+
: undefined,
|
|
431
980
|
}).pipe(Effect.flatMap((metadata) => sha256Object({ metadata })));
|
|
432
981
|
|
|
433
982
|
export const WorkerProvider = () =>
|
|
434
|
-
ProviderLayer.
|
|
983
|
+
ProviderLayer.dual(Worker, {
|
|
435
984
|
live: () => LiveWorkerProvider(),
|
|
436
|
-
local
|
|
985
|
+
// The local runtime deps (workerd, WorkerProxy, LocalRuntimeState)
|
|
986
|
+
// compose INTO the local variant so a live deploy only constructs them
|
|
987
|
+
// if the local provider is actually demanded (e.g. deleting a local
|
|
988
|
+
// dev worker's state row). See ProviderLayer.dual.
|
|
989
|
+
local: () =>
|
|
990
|
+
LocalWorkerProvider().pipe(Layer.provide(localRuntimeServices())),
|
|
437
991
|
});
|
|
438
992
|
|
|
439
993
|
export const LiveWorkerProvider = () =>
|
|
@@ -459,57 +1013,79 @@ export const LiveWorkerProvider = () =>
|
|
|
459
1013
|
// const listZones = yield* zones.listZones;
|
|
460
1014
|
const telemetry = yield* CloudflareLogs;
|
|
461
1015
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
1016
|
+
// Account subdomain is invariant for the life of a provider layer —
|
|
1017
|
+
// memoize so N Workers (or repeated plan/deploy reads) don't each hit
|
|
1018
|
+
// GET /accounts/{id}/workers/subdomain and trip Cloudflare's 429/971
|
|
1019
|
+
// throttle (#926). `CLOUDFLARE_WORKERS_SUBDOMAIN` bypasses the lookup
|
|
1020
|
+
// entirely when the operator already knows the value.
|
|
1021
|
+
const getAccountSubdomain = yield* cachedFunction((accountId: string) =>
|
|
1022
|
+
Effect.gen(function* () {
|
|
1023
|
+
const configured = yield* CLOUDFLARE_WORKERS_SUBDOMAIN;
|
|
1024
|
+
if (Option.isSome(configured) && configured.value !== "") {
|
|
1025
|
+
if (!WORKERS_SUBDOMAIN_PATTERN.test(configured.value)) {
|
|
1026
|
+
return yield* Effect.die(
|
|
1027
|
+
new Error(
|
|
1028
|
+
`Invalid CLOUDFLARE_WORKERS_SUBDOMAIN "${configured.value}": ` +
|
|
1029
|
+
"expected a workers.dev subdomain label (lowercase letters, digits, hyphens)",
|
|
1030
|
+
),
|
|
1031
|
+
);
|
|
1032
|
+
}
|
|
1033
|
+
return configured.value;
|
|
1034
|
+
}
|
|
1035
|
+
const result = yield* workers.getSubdomain({ accountId });
|
|
1036
|
+
return result.subdomain;
|
|
1037
|
+
}),
|
|
1038
|
+
);
|
|
1039
|
+
|
|
1040
|
+
// Converge the script's workers.dev settings via `POST /subdomain`.
|
|
1041
|
+
// The two toggles are independent on the Cloudflare API: `enabled`
|
|
1042
|
+
// drives the stable `<name>.<account>.workers.dev` URL and
|
|
1043
|
+
// `previews_enabled` the per-version preview URLs.
|
|
475
1044
|
const setWorkerSubdomain = Effect.fn(function* (
|
|
476
1045
|
name: string,
|
|
477
|
-
|
|
1046
|
+
desired: ResolvedWorkersDev,
|
|
478
1047
|
) {
|
|
479
1048
|
const { accountId } = yield* yield* CloudflareEnvironment;
|
|
480
1049
|
return yield* workers.createScriptSubdomain({
|
|
481
1050
|
accountId,
|
|
482
1051
|
scriptName: name,
|
|
483
|
-
enabled,
|
|
484
|
-
previewsEnabled:
|
|
1052
|
+
enabled: desired.enabled,
|
|
1053
|
+
previewsEnabled: desired.previewsEnabled,
|
|
485
1054
|
});
|
|
486
1055
|
});
|
|
487
1056
|
|
|
488
|
-
// Convert non-ASCII hostnames (emoji, IDN, etc.) to punycode so the
|
|
489
|
-
// Cloudflare API receives the form it stores domains in. `new URL(...)`
|
|
490
|
-
// does IDNA via WHATWG URL parsing — `📦.alchemy.run` → `xn--5z8h.alchemy.run`.
|
|
491
|
-
const toPunycode = (hostname: string): string => {
|
|
492
|
-
try {
|
|
493
|
-
return new URL(`https://${hostname}`).hostname;
|
|
494
|
-
} catch {
|
|
495
|
-
return hostname;
|
|
496
|
-
}
|
|
497
|
-
};
|
|
498
|
-
|
|
499
|
-
const normalizeDomains = (
|
|
500
|
-
domain: string | string[] | undefined,
|
|
501
|
-
): string[] =>
|
|
502
|
-
domain === undefined
|
|
503
|
-
? []
|
|
504
|
-
: Array.from(
|
|
505
|
-
new Set(
|
|
506
|
-
(Array.isArray(domain) ? domain : [domain]).map(toPunycode),
|
|
507
|
-
),
|
|
508
|
-
);
|
|
509
|
-
|
|
510
1057
|
const normalizeCrons = (crons: string[] | undefined): string[] =>
|
|
511
1058
|
Array.from(new Set(crons ?? []));
|
|
512
1059
|
|
|
1060
|
+
const hasSelfUrlBinding = (
|
|
1061
|
+
bindings: readonly ResourceBinding<Worker["Binding"]>[],
|
|
1062
|
+
) =>
|
|
1063
|
+
bindings.some((b) =>
|
|
1064
|
+
(b.data.bindings ?? []).some((item) => item.type === "self_url"),
|
|
1065
|
+
);
|
|
1066
|
+
|
|
1067
|
+
// Resolve the URL this Worker will be served at — the same formula that
|
|
1068
|
+
// produces the `url` attribute (first custom domain in user order, else
|
|
1069
|
+
// the workers.dev URL) — usable BEFORE the script upload, so `Worker.URL`
|
|
1070
|
+
// bindings can be lowered into plain_text bindings and VITE_*-prefixed
|
|
1071
|
+
// env entries can be inlined into the client bundle at build time.
|
|
1072
|
+
const resolveSelfUrl = Effect.fn(function* (
|
|
1073
|
+
name: string,
|
|
1074
|
+
props: WorkerProps,
|
|
1075
|
+
accountId: string,
|
|
1076
|
+
) {
|
|
1077
|
+
const domain = yield* resolveWorkerDomain(props.domain);
|
|
1078
|
+
if (domain) {
|
|
1079
|
+
return `https://${domain.name}`;
|
|
1080
|
+
}
|
|
1081
|
+
if (!resolveWorkersDev(props.workersDev).enabled) {
|
|
1082
|
+
return yield* Effect.die(
|
|
1083
|
+
`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.`,
|
|
1084
|
+
);
|
|
1085
|
+
}
|
|
1086
|
+
return `https://${name}.${yield* getAccountSubdomain(accountId)}.workers.dev`;
|
|
1087
|
+
});
|
|
1088
|
+
|
|
513
1089
|
const getWorkerCrons = Effect.fn(function* (scriptName: string) {
|
|
514
1090
|
const { accountId } = yield* yield* CloudflareEnvironment;
|
|
515
1091
|
return yield* workers
|
|
@@ -731,6 +1307,315 @@ export const LiveWorkerProvider = () =>
|
|
|
731
1307
|
return applied;
|
|
732
1308
|
});
|
|
733
1309
|
|
|
1310
|
+
// The preview URL of the Worker's *current* version:
|
|
1311
|
+
// `https://<version-prefix>-<name>.<subdomain>.workers.dev`, where the
|
|
1312
|
+
// prefix is the first 8 characters of the version id (read from the
|
|
1313
|
+
// latest deployment's majority version). Resolves to `undefined` when
|
|
1314
|
+
// no deployment is visible yet — callers treat the preview URL as
|
|
1315
|
+
// best-effort. Only consulted in previews-only mode
|
|
1316
|
+
// (`workersDev: { enabled: false, previewsEnabled: true }`), where it
|
|
1317
|
+
// is the Worker's only workers.dev surface.
|
|
1318
|
+
const getPreviewUrl = Effect.fn(function* (
|
|
1319
|
+
scriptName: string,
|
|
1320
|
+
accountSubdomain: string,
|
|
1321
|
+
) {
|
|
1322
|
+
const { accountId } = yield* yield* CloudflareEnvironment;
|
|
1323
|
+
const deployments = yield* workers
|
|
1324
|
+
.listScriptDeployments({ accountId, scriptName })
|
|
1325
|
+
.pipe(
|
|
1326
|
+
Effect.map((response) => response.deployments ?? []),
|
|
1327
|
+
Effect.catch(() => Effect.succeed([])),
|
|
1328
|
+
);
|
|
1329
|
+
const latest = [...deployments].sort((a, b) =>
|
|
1330
|
+
b.createdOn.localeCompare(a.createdOn),
|
|
1331
|
+
)[0];
|
|
1332
|
+
const version = latest?.versions?.reduce(
|
|
1333
|
+
(max, candidate) =>
|
|
1334
|
+
max === undefined || candidate.percentage > max.percentage
|
|
1335
|
+
? candidate
|
|
1336
|
+
: max,
|
|
1337
|
+
undefined as { percentage: number; versionId: string } | undefined,
|
|
1338
|
+
);
|
|
1339
|
+
if (!version?.versionId) return undefined;
|
|
1340
|
+
return `https://${version.versionId.slice(0, 8)}-${scriptName}.${accountSubdomain}.workers.dev`;
|
|
1341
|
+
});
|
|
1342
|
+
|
|
1343
|
+
/**
|
|
1344
|
+
* Assemble every URL a deployed, script-owning Worker serves at, most
|
|
1345
|
+
* significant first — the first entry becomes the `url` attribute:
|
|
1346
|
+
*
|
|
1347
|
+
* 1. `https://<domain.name>` — the canonical custom domain
|
|
1348
|
+
* 2. aliases, in declared order
|
|
1349
|
+
* 3. the stable workers.dev URL (`workersDev.enabled`)
|
|
1350
|
+
* 4. preview URLs of the version this deploy uploaded (gradual
|
|
1351
|
+
* rollouts), or — in previews-only mode — the current version's
|
|
1352
|
+
* preview URL, which is then the only workers.dev surface
|
|
1353
|
+
*
|
|
1354
|
+
* Redirect hostnames never appear: they don't serve the Worker.
|
|
1355
|
+
* (Version workers don't use this — their `urls` are their preview
|
|
1356
|
+
* URLs, assembled in `putWorkerVersion`.)
|
|
1357
|
+
*/
|
|
1358
|
+
const computeWorkerUrls = Effect.fn(function* (params: {
|
|
1359
|
+
scriptName: string;
|
|
1360
|
+
workersDev: ResolvedWorkersDev;
|
|
1361
|
+
domain: ResolvedWorkerDomain | undefined;
|
|
1362
|
+
/** the version uploaded by this deploy (self-rollout), if any */
|
|
1363
|
+
uploadedVersionId?: string;
|
|
1364
|
+
/** user-provided `version.alias` attached to the uploaded version */
|
|
1365
|
+
uploadedVersionAlias?: string;
|
|
1366
|
+
}) {
|
|
1367
|
+
const { workersDev, domain, scriptName } = params;
|
|
1368
|
+
const urls: string[] = [];
|
|
1369
|
+
if (domain) {
|
|
1370
|
+
urls.push(
|
|
1371
|
+
`https://${domain.name}`,
|
|
1372
|
+
...domain.aliases.map((hostname) => `https://${hostname}`),
|
|
1373
|
+
);
|
|
1374
|
+
}
|
|
1375
|
+
if (workersDev.enabled || workersDev.previewsEnabled) {
|
|
1376
|
+
const { accountId } = yield* yield* CloudflareEnvironment;
|
|
1377
|
+
const accountSubdomain = yield* getAccountSubdomain(accountId);
|
|
1378
|
+
if (workersDev.enabled) {
|
|
1379
|
+
urls.push(`https://${scriptName}.${accountSubdomain}.workers.dev`);
|
|
1380
|
+
}
|
|
1381
|
+
if (workersDev.previewsEnabled) {
|
|
1382
|
+
if (params.uploadedVersionId !== undefined) {
|
|
1383
|
+
if (params.uploadedVersionAlias !== undefined) {
|
|
1384
|
+
urls.push(
|
|
1385
|
+
`https://${params.uploadedVersionAlias}-${scriptName}.${accountSubdomain}.workers.dev`,
|
|
1386
|
+
);
|
|
1387
|
+
}
|
|
1388
|
+
urls.push(
|
|
1389
|
+
`https://${params.uploadedVersionId.split("-")[0]}-${scriptName}.${accountSubdomain}.workers.dev`,
|
|
1390
|
+
);
|
|
1391
|
+
} else if (!workersDev.enabled) {
|
|
1392
|
+
const previewUrl = yield* getPreviewUrl(
|
|
1393
|
+
scriptName,
|
|
1394
|
+
accountSubdomain,
|
|
1395
|
+
);
|
|
1396
|
+
if (previewUrl) urls.push(previewUrl);
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
return urls;
|
|
1401
|
+
});
|
|
1402
|
+
|
|
1403
|
+
/**
|
|
1404
|
+
* Converge the redirect rules for a Worker's `domain.redirects` in
|
|
1405
|
+
* each affected zone's `http_request_dynamic_redirect` phase
|
|
1406
|
+
* entrypoint. Rules are identified by a
|
|
1407
|
+
* `alchemy:worker:<script>:redirect:<host>` description; only our own
|
|
1408
|
+
* rules are added/removed — every other rule in the shared entrypoint
|
|
1409
|
+
* ruleset passes through untouched. Zones that previously held our
|
|
1410
|
+
* rules but no longer should (removed redirects, removed domain) are
|
|
1411
|
+
* cleaned the same way.
|
|
1412
|
+
*/
|
|
1413
|
+
const reconcileRedirectRules = Effect.fn(function* (params: {
|
|
1414
|
+
scriptName: string;
|
|
1415
|
+
domain: ResolvedWorkerDomain | undefined;
|
|
1416
|
+
/** hostname → zoneId for every currently-attached custom domain */
|
|
1417
|
+
zoneIdByHostname: ReadonlyMap<string, string>;
|
|
1418
|
+
/** redirect hostnames from previous state (for zone cleanup) */
|
|
1419
|
+
previousRedirects: readonly string[];
|
|
1420
|
+
}) {
|
|
1421
|
+
const prefix = `alchemy:worker:${params.scriptName}:redirect:`;
|
|
1422
|
+
const desired = params.domain?.redirects ?? [];
|
|
1423
|
+
const targetName = params.domain?.name;
|
|
1424
|
+
// Zones to touch: every zone hosting a desired redirect, plus every
|
|
1425
|
+
// zone that hosted one before (so removals converge). Hostnames
|
|
1426
|
+
// whose zone we can't resolve (e.g. the domain was already
|
|
1427
|
+
// detached) are skipped — their rules become unreachable dead
|
|
1428
|
+
// config at worst, never a failed destroy.
|
|
1429
|
+
const zoneIds = new Set<string>();
|
|
1430
|
+
for (const hostname of [...desired, ...params.previousRedirects]) {
|
|
1431
|
+
const zoneId = params.zoneIdByHostname.get(hostname);
|
|
1432
|
+
if (zoneId) zoneIds.add(zoneId);
|
|
1433
|
+
}
|
|
1434
|
+
if (zoneIds.size === 0) return;
|
|
1435
|
+
for (const zoneId of zoneIds) {
|
|
1436
|
+
const entrypoint = yield* rulesets
|
|
1437
|
+
.getPhasForZone({
|
|
1438
|
+
zoneId,
|
|
1439
|
+
rulesetPhase: "http_request_dynamic_redirect",
|
|
1440
|
+
})
|
|
1441
|
+
.pipe(Effect.catch(() => Effect.succeed(undefined)));
|
|
1442
|
+
const existingRules = entrypoint?.rules ?? [];
|
|
1443
|
+
const ourDesired = desired
|
|
1444
|
+
.filter(
|
|
1445
|
+
(hostname) => params.zoneIdByHostname.get(hostname) === zoneId,
|
|
1446
|
+
)
|
|
1447
|
+
.map((hostname) => ({
|
|
1448
|
+
action: "redirect" as const,
|
|
1449
|
+
expression: `http.host eq "${hostname}"`,
|
|
1450
|
+
description: `${prefix}${hostname}`,
|
|
1451
|
+
enabled: true,
|
|
1452
|
+
actionParameters: {
|
|
1453
|
+
fromValue: {
|
|
1454
|
+
statusCode: 301,
|
|
1455
|
+
preserveQueryString: true,
|
|
1456
|
+
targetUrl: {
|
|
1457
|
+
expression: `concat("https://${targetName}", http.request.uri.path)`,
|
|
1458
|
+
},
|
|
1459
|
+
},
|
|
1460
|
+
},
|
|
1461
|
+
}));
|
|
1462
|
+
const isOurs = (rule: { description?: string | null }) =>
|
|
1463
|
+
(rule.description ?? "").startsWith(prefix);
|
|
1464
|
+
const ourExisting = existingRules.filter(isOurs);
|
|
1465
|
+
const converged =
|
|
1466
|
+
ourExisting.length === ourDesired.length &&
|
|
1467
|
+
ourDesired.every((rule) =>
|
|
1468
|
+
ourExisting.some(
|
|
1469
|
+
(existing) =>
|
|
1470
|
+
existing.description === rule.description &&
|
|
1471
|
+
existing.expression === rule.expression,
|
|
1472
|
+
),
|
|
1473
|
+
);
|
|
1474
|
+
if (converged) continue;
|
|
1475
|
+
// Pass foreign rules through untouched (minus the read-only
|
|
1476
|
+
// fields the PUT schema doesn't accept) and replace our own set.
|
|
1477
|
+
const foreign = existingRules
|
|
1478
|
+
.filter((rule) => !isOurs(rule))
|
|
1479
|
+
.map((rule) => {
|
|
1480
|
+
const {
|
|
1481
|
+
lastUpdated: _lastUpdated,
|
|
1482
|
+
version: _version,
|
|
1483
|
+
...rest
|
|
1484
|
+
} = rule as Record<string, unknown> & {
|
|
1485
|
+
lastUpdated?: string;
|
|
1486
|
+
version?: string;
|
|
1487
|
+
};
|
|
1488
|
+
return rest;
|
|
1489
|
+
});
|
|
1490
|
+
yield* rulesets.putPhasForZone({
|
|
1491
|
+
zoneId,
|
|
1492
|
+
rulesetPhase: "http_request_dynamic_redirect",
|
|
1493
|
+
rules: [...foreign, ...ourDesired] as PutZoneRedirectRules,
|
|
1494
|
+
});
|
|
1495
|
+
}
|
|
1496
|
+
});
|
|
1497
|
+
|
|
1498
|
+
/**
|
|
1499
|
+
* Converge the version-affinity transform rules (`version.affinity`)
|
|
1500
|
+
* in each serving zone's `http_request_late_transform` phase
|
|
1501
|
+
* entrypoint: `rewrite` rules that fill the
|
|
1502
|
+
* `Cloudflare-Workers-Version-Key` header from the configured source,
|
|
1503
|
+
* scoped to the Worker's hostnames in that zone. Rules are identified
|
|
1504
|
+
* by an `alchemy:worker:<script>:affinity` description prefix; only
|
|
1505
|
+
* our own rules are added/updated/removed — every other rule in the
|
|
1506
|
+
* shared entrypoint passes through untouched. Zones that previously
|
|
1507
|
+
* held our rules but no longer should (affinity removed, zone no
|
|
1508
|
+
* longer served) are cleaned the same way. Returns the zone ids now
|
|
1509
|
+
* holding rules, for persistence in `affinityZoneIds`.
|
|
1510
|
+
*/
|
|
1511
|
+
const reconcileAffinityRules = Effect.fn(function* (params: {
|
|
1512
|
+
scriptName: string;
|
|
1513
|
+
/** `undefined` removes every rule (affinity removed / delete). */
|
|
1514
|
+
desired:
|
|
1515
|
+
| {
|
|
1516
|
+
affinity: ResolvedVersionAffinity;
|
|
1517
|
+
hostsByZone: ReadonlyMap<string, AffinityZoneHost[]>;
|
|
1518
|
+
}
|
|
1519
|
+
| undefined;
|
|
1520
|
+
/** zone ids holding rules per previous state (for cleanup). */
|
|
1521
|
+
previousZoneIds: readonly string[];
|
|
1522
|
+
}) {
|
|
1523
|
+
const prefix = affinityRulePrefix(params.scriptName);
|
|
1524
|
+
const zoneIds = new Set([
|
|
1525
|
+
...(params.desired?.hostsByZone.keys() ?? []),
|
|
1526
|
+
...params.previousZoneIds,
|
|
1527
|
+
]);
|
|
1528
|
+
for (const zoneId of zoneIds) {
|
|
1529
|
+
const hosts = params.desired?.hostsByZone.get(zoneId);
|
|
1530
|
+
const ourDesired =
|
|
1531
|
+
params.desired !== undefined && hosts !== undefined
|
|
1532
|
+
? buildAffinityZoneRules(
|
|
1533
|
+
params.scriptName,
|
|
1534
|
+
params.desired.affinity,
|
|
1535
|
+
hosts,
|
|
1536
|
+
)
|
|
1537
|
+
: [];
|
|
1538
|
+
const entrypoint = yield* rulesets
|
|
1539
|
+
.getPhasForZone({
|
|
1540
|
+
zoneId,
|
|
1541
|
+
rulesetPhase: "http_request_late_transform",
|
|
1542
|
+
})
|
|
1543
|
+
.pipe(Effect.catch(() => Effect.succeed(undefined)));
|
|
1544
|
+
const existingRules = entrypoint?.rules ?? [];
|
|
1545
|
+
const isOurs = (rule: { description?: string | null }) =>
|
|
1546
|
+
(rule.description ?? "").startsWith(prefix);
|
|
1547
|
+
// The header-value expression of an existing rewrite rule, for
|
|
1548
|
+
// convergence comparison. `headers` is an untyped wire record:
|
|
1549
|
+
// `{ "<Header-Name>": { operation, expression | value } }`.
|
|
1550
|
+
const existingValue = (rule: (typeof existingRules)[number]) => {
|
|
1551
|
+
const headers =
|
|
1552
|
+
"actionParameters" in rule
|
|
1553
|
+
? (
|
|
1554
|
+
rule.actionParameters as
|
|
1555
|
+
| { headers?: Record<string, unknown> | null }
|
|
1556
|
+
| null
|
|
1557
|
+
| undefined
|
|
1558
|
+
)?.headers
|
|
1559
|
+
: undefined;
|
|
1560
|
+
const header = headers?.[AFFINITY_HEADER] as
|
|
1561
|
+
| { expression?: unknown }
|
|
1562
|
+
| undefined;
|
|
1563
|
+
return typeof header?.expression === "string"
|
|
1564
|
+
? header.expression
|
|
1565
|
+
: undefined;
|
|
1566
|
+
};
|
|
1567
|
+
const ourExisting = existingRules.filter(isOurs);
|
|
1568
|
+
const converged =
|
|
1569
|
+
ourExisting.length === ourDesired.length &&
|
|
1570
|
+
ourDesired.every((rule) =>
|
|
1571
|
+
ourExisting.some(
|
|
1572
|
+
(existing) =>
|
|
1573
|
+
existing.description === rule.description &&
|
|
1574
|
+
existing.expression === rule.expression &&
|
|
1575
|
+
existingValue(existing) === rule.value,
|
|
1576
|
+
),
|
|
1577
|
+
);
|
|
1578
|
+
if (converged) continue;
|
|
1579
|
+
// Pass foreign rules through untouched (minus the read-only
|
|
1580
|
+
// fields the PUT schema doesn't accept) and replace our own set.
|
|
1581
|
+
const foreign = existingRules
|
|
1582
|
+
.filter((rule) => !isOurs(rule))
|
|
1583
|
+
.map((rule) => {
|
|
1584
|
+
const {
|
|
1585
|
+
lastUpdated: _lastUpdated,
|
|
1586
|
+
version: _version,
|
|
1587
|
+
...rest
|
|
1588
|
+
} = rule as Record<string, unknown> & {
|
|
1589
|
+
lastUpdated?: string;
|
|
1590
|
+
version?: string;
|
|
1591
|
+
};
|
|
1592
|
+
return rest;
|
|
1593
|
+
});
|
|
1594
|
+
yield* rulesets.putPhasForZone({
|
|
1595
|
+
zoneId,
|
|
1596
|
+
rulesetPhase: "http_request_late_transform",
|
|
1597
|
+
rules: [
|
|
1598
|
+
...foreign,
|
|
1599
|
+
...ourDesired.map((rule) => ({
|
|
1600
|
+
action: "rewrite" as const,
|
|
1601
|
+
description: rule.description,
|
|
1602
|
+
enabled: true,
|
|
1603
|
+
expression: rule.expression,
|
|
1604
|
+
actionParameters: {
|
|
1605
|
+
headers: {
|
|
1606
|
+
[AFFINITY_HEADER]: {
|
|
1607
|
+
operation: "set",
|
|
1608
|
+
expression: rule.value,
|
|
1609
|
+
},
|
|
1610
|
+
},
|
|
1611
|
+
},
|
|
1612
|
+
})),
|
|
1613
|
+
] as PutZoneRedirectRules,
|
|
1614
|
+
});
|
|
1615
|
+
}
|
|
1616
|
+
return [...(params.desired?.hostsByZone.keys() ?? [])].sort();
|
|
1617
|
+
});
|
|
1618
|
+
|
|
734
1619
|
type NormalizedWorkerRoute = {
|
|
735
1620
|
pattern: string;
|
|
736
1621
|
zoneId: string;
|
|
@@ -810,28 +1695,39 @@ export const LiveWorkerProvider = () =>
|
|
|
810
1695
|
Effect.catch(() => Effect.succeed([])),
|
|
811
1696
|
),
|
|
812
1697
|
),
|
|
813
|
-
|
|
1698
|
+
// Bounded: this issues one request per zone, so a Worker with routes
|
|
1699
|
+
// spread across many zones would otherwise burst the account's whole
|
|
1700
|
+
// API budget in a single tick (#926).
|
|
1701
|
+
{ concurrency: WORKER_ROUTE_LIST_CONCURRENCY },
|
|
814
1702
|
);
|
|
815
1703
|
|
|
816
1704
|
return Effect.map(routesByZone, (routes) => routes.flat());
|
|
817
1705
|
};
|
|
818
1706
|
|
|
819
|
-
// Observe
|
|
820
|
-
//
|
|
821
|
-
//
|
|
822
|
-
//
|
|
823
|
-
//
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
1707
|
+
// Observe the routes attached to `scriptName` in the zones Alchemy
|
|
1708
|
+
// already associates with this Worker.
|
|
1709
|
+
//
|
|
1710
|
+
// This used to enumerate every zone on the account and then list routes
|
|
1711
|
+
// in each one. Routes are zone-scoped with no account-level enumeration
|
|
1712
|
+
// API, so that costs O(zones) requests on *every* Worker read — on an
|
|
1713
|
+
// account with a few hundred zones a couple of plan/deploy cycles
|
|
1714
|
+
// exhausts Cloudflare's per-user API budget and unrelated calls start
|
|
1715
|
+
// coming back 429 / code 971 (#926).
|
|
1716
|
+
//
|
|
1717
|
+
// `reconcileRoutes` already scopes itself to the zones implied by
|
|
1718
|
+
// `desired ∪ previous` rather than sweeping the account, so `read` now
|
|
1719
|
+
// uses the same bounded zone set: the zones of the routes already
|
|
1720
|
+
// recorded in state. The blind spot this introduces — a route added out
|
|
1721
|
+
// of band in a zone this Worker has never had a route in — is the one
|
|
1722
|
+
// `reconcileRoutes` already has.
|
|
1723
|
+
const readWorkerRoutes = (
|
|
1724
|
+
scriptName: string,
|
|
1725
|
+
knownRoutes: Worker["Attributes"]["routes"] | undefined,
|
|
1726
|
+
) =>
|
|
1727
|
+
listWorkerRoutesInZones(
|
|
1728
|
+
scriptName,
|
|
1729
|
+
(knownRoutes ?? []).map((route) => route.zoneId),
|
|
1730
|
+
);
|
|
835
1731
|
|
|
836
1732
|
// Converge the zone routes attached to `scriptName` to `desired`.
|
|
837
1733
|
// Observed cloud state (not `previous`) is the diff baseline —
|
|
@@ -1175,7 +2071,10 @@ export const LiveWorkerProvider = () =>
|
|
|
1175
2071
|
crypto.createHash("sha256").update(script).digest("hex"),
|
|
1176
2072
|
);
|
|
1177
2073
|
|
|
1178
|
-
const viteBuild = Effect.fn(function* (
|
|
2074
|
+
const viteBuild = Effect.fn(function* (
|
|
2075
|
+
props: WorkerProps,
|
|
2076
|
+
selfUrl?: string,
|
|
2077
|
+
) {
|
|
1179
2078
|
const compatibility = getCompatibility(props);
|
|
1180
2079
|
// Loaded lazily: `./Vite.ts` pulls in `@distilled.cloud/cloudflare-vite-plugin`
|
|
1181
2080
|
// (~0.5s), which is only needed for vite-based workers at build time —
|
|
@@ -1195,22 +2094,44 @@ export const LiveWorkerProvider = () =>
|
|
|
1195
2094
|
: Redacted.isRedacted(value) &&
|
|
1196
2095
|
typeof Redacted.value(value) === "string"
|
|
1197
2096
|
? Redacted.value(value)
|
|
1198
|
-
: //
|
|
1199
|
-
//
|
|
1200
|
-
//
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
2097
|
+
: // `Worker.URL` (bare tag or called) — resolved to
|
|
2098
|
+
// this Worker's own URL. The bare tag is
|
|
2099
|
+
// Effect-shaped, so check before `Effect.isEffect`.
|
|
2100
|
+
isSelfUrl(value)
|
|
2101
|
+
? selfUrl
|
|
2102
|
+
: // A `WorkerLoader` is a real Effect that also carries
|
|
2103
|
+
// the `~alchemy/Kind` marker — it is a binding, not a
|
|
2104
|
+
// runnable env value. Check it before `Effect.isEffect`
|
|
2105
|
+
// so we don't execute it as an inlined env entry.
|
|
2106
|
+
isWorkerLoader(value)
|
|
2107
|
+
? undefined
|
|
2108
|
+
: // A `Cloudflare.Container` declaration is likewise
|
|
2109
|
+
// Effect-shaped but is a binding (DO namespace +
|
|
2110
|
+
// ContainerApplication) — yielding it would resolve
|
|
2111
|
+
// the started-instance tag, which only exists inside
|
|
2112
|
+
// a Durable Object (#997).
|
|
2113
|
+
isContainerDecl(value)
|
|
2114
|
+
? undefined
|
|
2115
|
+
: Effect.isEffect(value)
|
|
2116
|
+
? yield* value as any as Effect.Effect<any>
|
|
2117
|
+
: undefined,
|
|
1207
2118
|
];
|
|
1208
2119
|
}),
|
|
1209
2120
|
),
|
|
1210
2121
|
)).filter(([_, value]) => value !== undefined),
|
|
1211
2122
|
),
|
|
1212
2123
|
{
|
|
1213
|
-
|
|
2124
|
+
// A relative `vite.main` is documented to resolve from the Vite
|
|
2125
|
+
// root. The rolldown plugin resolves the worker entry with no
|
|
2126
|
+
// importer (i.e. against `process.cwd()`), which breaks when the
|
|
2127
|
+
// deploy runs from a different directory (e.g. a monorepo infra
|
|
2128
|
+
// package) — absolutize before handing it over (#796).
|
|
2129
|
+
main: props.vite?.main
|
|
2130
|
+
? path.resolve(
|
|
2131
|
+
props.vite.rootDir ?? process.cwd(),
|
|
2132
|
+
props.vite.main,
|
|
2133
|
+
)
|
|
2134
|
+
: undefined,
|
|
1214
2135
|
compatibilityDate: compatibility.date,
|
|
1215
2136
|
compatibilityFlags: compatibility.flags,
|
|
1216
2137
|
viteEnvironments: props.vite?.viteEnvironments,
|
|
@@ -1298,7 +2219,7 @@ export const LiveWorkerProvider = () =>
|
|
|
1298
2219
|
const prepareAssetsAndBundle = (
|
|
1299
2220
|
id: string,
|
|
1300
2221
|
props: WorkerProps,
|
|
1301
|
-
opts: { skipAssetsRead?: boolean } = {},
|
|
2222
|
+
opts: { skipAssetsRead?: boolean; selfUrl?: string } = {},
|
|
1302
2223
|
) =>
|
|
1303
2224
|
Effect.gen(function* () {
|
|
1304
2225
|
if (props.script !== undefined) {
|
|
@@ -1322,7 +2243,29 @@ export const LiveWorkerProvider = () =>
|
|
|
1322
2243
|
};
|
|
1323
2244
|
}
|
|
1324
2245
|
if (props.vite) {
|
|
1325
|
-
return yield* viteBuild(props);
|
|
2246
|
+
return yield* viteBuild(props, opts.selfUrl);
|
|
2247
|
+
}
|
|
2248
|
+
// Assets-only Worker: no entry module at all. The script PUT goes
|
|
2249
|
+
// out with no modules and no main_module — Cloudflare's asset
|
|
2250
|
+
// layer serves every request and applies `notFoundHandling`
|
|
2251
|
+
// (including SPA fallback) itself, exactly like Wrangler's
|
|
2252
|
+
// assets-only deploys.
|
|
2253
|
+
if (props.main === undefined) {
|
|
2254
|
+
if (!props.assets) {
|
|
2255
|
+
return yield* Effect.die(
|
|
2256
|
+
new Error(
|
|
2257
|
+
`Worker "${id}" has no main, script, or assets. Provide an entry module (main / script) or an assets directory to deploy an assets-only Worker.`,
|
|
2258
|
+
),
|
|
2259
|
+
);
|
|
2260
|
+
}
|
|
2261
|
+
return {
|
|
2262
|
+
assets: opts.skipAssetsRead
|
|
2263
|
+
? undefined
|
|
2264
|
+
: yield* prepareAssets(props.assets),
|
|
2265
|
+
bundle: undefined,
|
|
2266
|
+
input: undefined,
|
|
2267
|
+
additionalWorkspaces: undefined,
|
|
2268
|
+
};
|
|
1326
2269
|
}
|
|
1327
2270
|
const [assets, bundle] = yield* Effect.all(
|
|
1328
2271
|
[
|
|
@@ -1369,6 +2312,523 @@ export const LiveWorkerProvider = () =>
|
|
|
1369
2312
|
return { directory, config, hash, skip: hash === output?.hash?.assets };
|
|
1370
2313
|
};
|
|
1371
2314
|
|
|
2315
|
+
/**
|
|
2316
|
+
* Append the standard Alchemy runtime bindings plus the user's `env`
|
|
2317
|
+
* entries (routed by shape: `Redacted` → secret_text, string →
|
|
2318
|
+
* plain_text, everything else → json) to a metadata binding list.
|
|
2319
|
+
* Shared between the full script upload and the version upload.
|
|
2320
|
+
*/
|
|
2321
|
+
const appendAlchemyAndEnvBindings = (
|
|
2322
|
+
metadataBindings: WorkerBinding[],
|
|
2323
|
+
news: WorkerProps,
|
|
2324
|
+
accountId: string,
|
|
2325
|
+
workerName: string,
|
|
2326
|
+
) => {
|
|
2327
|
+
metadataBindings.push(
|
|
2328
|
+
{
|
|
2329
|
+
type: "plain_text",
|
|
2330
|
+
name: "ALCHEMY_PHASE",
|
|
2331
|
+
text: "runtime",
|
|
2332
|
+
},
|
|
2333
|
+
{
|
|
2334
|
+
type: "plain_text",
|
|
2335
|
+
name: "ALCHEMY_WORKER_NAME",
|
|
2336
|
+
text: workerName,
|
|
2337
|
+
},
|
|
2338
|
+
{
|
|
2339
|
+
type: "plain_text",
|
|
2340
|
+
name: "ALCHEMY_STACK_NAME",
|
|
2341
|
+
text: stack.name,
|
|
2342
|
+
},
|
|
2343
|
+
{
|
|
2344
|
+
type: "plain_text",
|
|
2345
|
+
name: "ALCHEMY_STAGE",
|
|
2346
|
+
text: stack.stage,
|
|
2347
|
+
},
|
|
2348
|
+
{
|
|
2349
|
+
type: "plain_text",
|
|
2350
|
+
name: "ALCHEMY_CLOUDFLARE_ACCOUNT_ID",
|
|
2351
|
+
text: accountId,
|
|
2352
|
+
},
|
|
2353
|
+
);
|
|
2354
|
+
// Add environment variables as metadata bindings
|
|
2355
|
+
if (news.env) {
|
|
2356
|
+
for (const [key, value] of Object.entries(news.env)) {
|
|
2357
|
+
if (value === undefined) continue;
|
|
2358
|
+
if (metadataBindings.some((b) => b.name === key)) continue;
|
|
2359
|
+
if (Redacted.isRedacted(value)) {
|
|
2360
|
+
const unredacted = Redacted.value(value);
|
|
2361
|
+
metadataBindings.push({
|
|
2362
|
+
type: "secret_text",
|
|
2363
|
+
name: key,
|
|
2364
|
+
text:
|
|
2365
|
+
typeof unredacted === "string"
|
|
2366
|
+
? unredacted
|
|
2367
|
+
: JSON.stringify(unredacted),
|
|
2368
|
+
});
|
|
2369
|
+
} else if (typeof value === "string") {
|
|
2370
|
+
metadataBindings.push({
|
|
2371
|
+
type: "plain_text",
|
|
2372
|
+
name: key,
|
|
2373
|
+
text: value,
|
|
2374
|
+
});
|
|
2375
|
+
} else {
|
|
2376
|
+
metadataBindings.push({
|
|
2377
|
+
type: "json",
|
|
2378
|
+
name: key,
|
|
2379
|
+
json: value,
|
|
2380
|
+
});
|
|
2381
|
+
}
|
|
2382
|
+
}
|
|
2383
|
+
}
|
|
2384
|
+
};
|
|
2385
|
+
|
|
2386
|
+
/**
|
|
2387
|
+
* Create a deployment routing `traffic`% to `versionId`, with the
|
|
2388
|
+
* remainder staying on the currently-live version (the
|
|
2389
|
+
* highest-percentage version of the script's latest deployment).
|
|
2390
|
+
* `traffic >= 100` — or a script with no other live version to split
|
|
2391
|
+
* against — deploys the new version at 100%.
|
|
2392
|
+
*/
|
|
2393
|
+
const deployVersionTraffic = Effect.fn(function* (params: {
|
|
2394
|
+
accountId: string;
|
|
2395
|
+
scriptName: string;
|
|
2396
|
+
versionId: string;
|
|
2397
|
+
traffic: number;
|
|
2398
|
+
message: string | undefined;
|
|
2399
|
+
}) {
|
|
2400
|
+
const { accountId, scriptName, versionId, traffic } = params;
|
|
2401
|
+
const split = yield* Effect.gen(function* () {
|
|
2402
|
+
if (traffic >= 100) {
|
|
2403
|
+
return [{ versionId, percentage: 100 }];
|
|
2404
|
+
}
|
|
2405
|
+
const { deployments } = yield* workers.listScriptDeployments({
|
|
2406
|
+
accountId,
|
|
2407
|
+
scriptName,
|
|
2408
|
+
});
|
|
2409
|
+
// Deployments are returned newest-first; the live version is the
|
|
2410
|
+
// highest-percentage version of the most recent deployment.
|
|
2411
|
+
const stable = deployments[0]?.versions
|
|
2412
|
+
.filter((v) => v.versionId !== versionId)
|
|
2413
|
+
.sort((a, b) => b.percentage - a.percentage)[0];
|
|
2414
|
+
if (!stable) {
|
|
2415
|
+
// Nothing to split against (first deployment of this script).
|
|
2416
|
+
return [{ versionId, percentage: 100 }];
|
|
2417
|
+
}
|
|
2418
|
+
return [
|
|
2419
|
+
{ versionId, percentage: traffic },
|
|
2420
|
+
{ versionId: stable.versionId, percentage: 100 - traffic },
|
|
2421
|
+
];
|
|
2422
|
+
});
|
|
2423
|
+
const deployment = yield* workers.createScriptDeployment({
|
|
2424
|
+
accountId,
|
|
2425
|
+
scriptName,
|
|
2426
|
+
strategy: "percentage",
|
|
2427
|
+
versions: split,
|
|
2428
|
+
annotations: params.message
|
|
2429
|
+
? { workersMessage: params.message }
|
|
2430
|
+
: undefined,
|
|
2431
|
+
});
|
|
2432
|
+
return deployment.id;
|
|
2433
|
+
});
|
|
2434
|
+
|
|
2435
|
+
/**
|
|
2436
|
+
* Resolve the preview-URL alias for a version worker: the
|
|
2437
|
+
* user-provided `version.alias` (validated against Cloudflare's
|
|
2438
|
+
* naming rules), or an alias derived from the stack, stage, and
|
|
2439
|
+
* logical id — stable across deploys, so the aliased preview URL
|
|
2440
|
+
* (`<alias>-<script>.<subdomain>.workers.dev`) is a durable link
|
|
2441
|
+
* that always points at the latest uploaded version. Returns
|
|
2442
|
+
* `undefined` when the parent's script name leaves no room in the
|
|
2443
|
+
* 63-character DNS label and no explicit alias was given.
|
|
2444
|
+
*/
|
|
2445
|
+
const resolveVersionAlias = Effect.fn(function* (
|
|
2446
|
+
id: string,
|
|
2447
|
+
news: WorkerProps,
|
|
2448
|
+
parentName: string,
|
|
2449
|
+
) {
|
|
2450
|
+
// `<alias>-<script name>` is a single DNS label (≤ 63 chars).
|
|
2451
|
+
const budget = 63 - parentName.length - 1;
|
|
2452
|
+
const userAlias = news.version?.alias;
|
|
2453
|
+
if (userAlias !== undefined) {
|
|
2454
|
+
if (!/^[a-z][a-z0-9-]*$/.test(userAlias)) {
|
|
2455
|
+
return yield* Effect.fail(
|
|
2456
|
+
new WorkerVersionConfigError({
|
|
2457
|
+
message: `version.alias '${userAlias}' is invalid: aliases must start with a lowercase letter and contain only lowercase letters, digits, and dashes.`,
|
|
2458
|
+
}),
|
|
2459
|
+
);
|
|
2460
|
+
}
|
|
2461
|
+
if (userAlias.length > budget) {
|
|
2462
|
+
return yield* Effect.fail(
|
|
2463
|
+
new WorkerVersionConfigError({
|
|
2464
|
+
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.`,
|
|
2465
|
+
}),
|
|
2466
|
+
);
|
|
2467
|
+
}
|
|
2468
|
+
return userAlias;
|
|
2469
|
+
}
|
|
2470
|
+
if (budget < 4) {
|
|
2471
|
+
return undefined;
|
|
2472
|
+
}
|
|
2473
|
+
// Deterministic per (stack, stage, id) — survives updates AND
|
|
2474
|
+
// replacements so the aliased preview URL never moves. A short
|
|
2475
|
+
// hash disambiguates different stacks/stages whose readable
|
|
2476
|
+
// prefixes collide after truncation.
|
|
2477
|
+
const hash = yield* Effect.sync(() =>
|
|
2478
|
+
crypto
|
|
2479
|
+
.createHash("sha256")
|
|
2480
|
+
.update(`${stack.name}/${stack.stage}/${id}`)
|
|
2481
|
+
.digest("hex")
|
|
2482
|
+
.slice(0, 6),
|
|
2483
|
+
);
|
|
2484
|
+
const readable = `${stack.stage}-${id}`
|
|
2485
|
+
.toLowerCase()
|
|
2486
|
+
.replace(/[^a-z0-9-]+/g, "-")
|
|
2487
|
+
.replace(/^-+|-+$/g, "");
|
|
2488
|
+
const alias = [readable, hash]
|
|
2489
|
+
.filter((part) => part.length > 0)
|
|
2490
|
+
.join("-")
|
|
2491
|
+
.slice(0, budget)
|
|
2492
|
+
.replace(/-+$/, "");
|
|
2493
|
+
return /^[a-z]/.test(alias) ? alias : `v${alias}`.slice(0, budget);
|
|
2494
|
+
});
|
|
2495
|
+
|
|
2496
|
+
/**
|
|
2497
|
+
* The parent's workers.dev preview settings + account subdomain,
|
|
2498
|
+
* fetched once per version reconcile. Preview URLs (aliased and
|
|
2499
|
+
* versioned) only serve when the parent has previews enabled.
|
|
2500
|
+
*/
|
|
2501
|
+
const resolveVersionPreviewContext = Effect.fn(function* (
|
|
2502
|
+
accountId: string,
|
|
2503
|
+
scriptName: string,
|
|
2504
|
+
) {
|
|
2505
|
+
const subdomain = yield* workers
|
|
2506
|
+
.getScriptSubdomain({ accountId, scriptName })
|
|
2507
|
+
.pipe(
|
|
2508
|
+
Effect.orElseSucceed<workers.GetScriptSubdomainResponse>(() => ({
|
|
2509
|
+
enabled: false,
|
|
2510
|
+
previewsEnabled: false,
|
|
2511
|
+
})),
|
|
2512
|
+
);
|
|
2513
|
+
const accountSubdomain = yield* getAccountSubdomain(accountId);
|
|
2514
|
+
return {
|
|
2515
|
+
previewsEnabled: subdomain.previewsEnabled === true,
|
|
2516
|
+
accountSubdomain,
|
|
2517
|
+
};
|
|
2518
|
+
});
|
|
2519
|
+
|
|
2520
|
+
/**
|
|
2521
|
+
* Reject props that can't ride along on a *version worker*
|
|
2522
|
+
* (`version.parent` set). A version carries only code, bindings, and
|
|
2523
|
+
* compatibility settings; everything script-level belongs to the
|
|
2524
|
+
* parent and must not be mutated from a version resource. Locally
|
|
2525
|
+
* hosted Durable Object / Workflow classes are rejected because their
|
|
2526
|
+
* migrations would apply to the parent script when the version
|
|
2527
|
+
* deploys.
|
|
2528
|
+
*/
|
|
2529
|
+
const validateVersionWorkerProps = Effect.fn(function* (
|
|
2530
|
+
news: WorkerProps,
|
|
2531
|
+
bindings: ResourceBinding<Worker["Binding"]>[],
|
|
2532
|
+
parentName: string,
|
|
2533
|
+
) {
|
|
2534
|
+
const forbidden = (
|
|
2535
|
+
[
|
|
2536
|
+
["name", news.name],
|
|
2537
|
+
["assets", news.assets],
|
|
2538
|
+
["namespace", news.namespace],
|
|
2539
|
+
["crons", news.crons],
|
|
2540
|
+
["domain", news.domain],
|
|
2541
|
+
["routes", news.routes],
|
|
2542
|
+
["tags", news.tags],
|
|
2543
|
+
["logpush", news.logpush],
|
|
2544
|
+
["observability", news.observability],
|
|
2545
|
+
["placement", news.placement],
|
|
2546
|
+
["limits", news.limits],
|
|
2547
|
+
["workersDev", news.workersDev],
|
|
2548
|
+
["vite", news.vite],
|
|
2549
|
+
] as const
|
|
2550
|
+
).flatMap(([key, value]) => (value !== undefined ? [key] : []));
|
|
2551
|
+
if (forbidden.length > 0) {
|
|
2552
|
+
return yield* Effect.fail(
|
|
2553
|
+
new WorkerVersionConfigError({
|
|
2554
|
+
message:
|
|
2555
|
+
`version.parent uploads a version of '${parentName}' — script-level settings belong to the parent Worker and cannot be set here: ${forbidden.join(", ")}. ` +
|
|
2556
|
+
`Remove ${forbidden.length === 1 ? "this prop" : "these props"} or configure ${forbidden.length === 1 ? "it" : "them"} on the parent.`,
|
|
2557
|
+
}),
|
|
2558
|
+
);
|
|
2559
|
+
}
|
|
2560
|
+
yield* validateTraffic(news.version?.traffic);
|
|
2561
|
+
const cronBindings = getCronBindings(bindings);
|
|
2562
|
+
if (cronBindings.length > 0) {
|
|
2563
|
+
return yield* Effect.fail(
|
|
2564
|
+
new WorkerVersionConfigError({
|
|
2565
|
+
message: `Cron Triggers are script-level settings and cannot be registered from a version of '${parentName}'. Configure crons on the parent Worker.`,
|
|
2566
|
+
}),
|
|
2567
|
+
);
|
|
2568
|
+
}
|
|
2569
|
+
// Any locally-hosted DO class (a durable_object_namespace binding
|
|
2570
|
+
// without a foreign scriptName) or DO/Workflow export would require
|
|
2571
|
+
// migrations, which the versions API can't carry — and which would
|
|
2572
|
+
// mutate the parent's namespaces.
|
|
2573
|
+
const hostedClasses = getDurableObjectBindings(bindings, parentName);
|
|
2574
|
+
const exportedClasses = Object.keys(news.exports ?? {});
|
|
2575
|
+
if (hostedClasses.length > 0 || exportedClasses.length > 0) {
|
|
2576
|
+
return yield* Effect.fail(
|
|
2577
|
+
new WorkerVersionConfigError({
|
|
2578
|
+
message: `A version of '${parentName}' cannot host Durable Object or Workflow classes (${[
|
|
2579
|
+
...new Set([
|
|
2580
|
+
...hostedClasses.map((c) => c.className),
|
|
2581
|
+
...exportedClasses,
|
|
2582
|
+
]),
|
|
2583
|
+
].join(
|
|
2584
|
+
", ",
|
|
2585
|
+
)}): class migrations apply to the parent script. Host the classes on the parent Worker and reference them cross-script instead.`,
|
|
2586
|
+
}),
|
|
2587
|
+
);
|
|
2588
|
+
}
|
|
2589
|
+
});
|
|
2590
|
+
|
|
2591
|
+
/**
|
|
2592
|
+
* Reconcile a *version worker*: upload this Worker's code + bindings
|
|
2593
|
+
* as an immutable version of the parent's script (no script of its
|
|
2594
|
+
* own), then optionally shift `version.traffic`% of the parent's
|
|
2595
|
+
* traffic to it. Idempotent in the reconciler sense: every run
|
|
2596
|
+
* converges cloud state to "the parent's script has a version with
|
|
2597
|
+
* exactly this content, receiving exactly this traffic" — re-running
|
|
2598
|
+
* with changed content simply uploads the next immutable version.
|
|
2599
|
+
*/
|
|
2600
|
+
const putWorkerVersion = Effect.fn(function* (
|
|
2601
|
+
id: string,
|
|
2602
|
+
news: WorkerProps,
|
|
2603
|
+
bindings: ResourceBinding<Worker["Binding"]>[],
|
|
2604
|
+
session: ScopedPlanStatusSession,
|
|
2605
|
+
output: Worker["Attributes"] | undefined,
|
|
2606
|
+
) {
|
|
2607
|
+
const { accountId } = yield* yield* CloudflareEnvironment;
|
|
2608
|
+
const version = news.version!;
|
|
2609
|
+
const parentName = resolveVersionParentName(version);
|
|
2610
|
+
if (parentName === undefined) {
|
|
2611
|
+
return yield* Effect.fail(
|
|
2612
|
+
new WorkerVersionConfigError({
|
|
2613
|
+
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.`,
|
|
2614
|
+
}),
|
|
2615
|
+
);
|
|
2616
|
+
}
|
|
2617
|
+
yield* validateVersionWorkerProps(news, bindings, parentName);
|
|
2618
|
+
const traffic = version.traffic ?? 0;
|
|
2619
|
+
// Resolve the alias and preview context BEFORE the upload: the
|
|
2620
|
+
// aliased preview URL (`<alias>-<script>.<subdomain>.workers.dev`)
|
|
2621
|
+
// is deterministic ahead of time — unlike the versioned URL, whose
|
|
2622
|
+
// prefix comes from the server-assigned version id — which is what
|
|
2623
|
+
// lets a `Worker.URL` (self_url) binding be baked into the
|
|
2624
|
+
// version's own bindings.
|
|
2625
|
+
const alias = yield* resolveVersionAlias(id, news, parentName);
|
|
2626
|
+
const { previewsEnabled, accountSubdomain } =
|
|
2627
|
+
yield* resolveVersionPreviewContext(accountId, parentName);
|
|
2628
|
+
const aliasedUrl =
|
|
2629
|
+
alias !== undefined && previewsEnabled
|
|
2630
|
+
? `https://${alias}-${parentName}.${accountSubdomain}.workers.dev`
|
|
2631
|
+
: undefined;
|
|
2632
|
+
const selfUrl = hasSelfUrlBinding(bindings) ? aliasedUrl : undefined;
|
|
2633
|
+
if (hasSelfUrlBinding(bindings) && selfUrl === undefined) {
|
|
2634
|
+
return yield* Effect.fail(
|
|
2635
|
+
new WorkerVersionConfigError({
|
|
2636
|
+
message: previewsEnabled
|
|
2637
|
+
? `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.`
|
|
2638
|
+
: `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).`,
|
|
2639
|
+
}),
|
|
2640
|
+
);
|
|
2641
|
+
}
|
|
2642
|
+
yield* Effect.logInfo(
|
|
2643
|
+
`Cloudflare Worker version: preparing bundle for ${parentName} (from ${id})`,
|
|
2644
|
+
);
|
|
2645
|
+
const { bundle, hash: preparedHash } = yield* prepareAssetsAndBundle(
|
|
2646
|
+
id,
|
|
2647
|
+
news,
|
|
2648
|
+
{ skipAssetsRead: true },
|
|
2649
|
+
);
|
|
2650
|
+
const metadataHash = yield* resolveWorkerMetadataHash({
|
|
2651
|
+
props: news,
|
|
2652
|
+
bindings,
|
|
2653
|
+
accountId,
|
|
2654
|
+
stack: { name: stack.name, stage: stack.stage },
|
|
2655
|
+
selfUrl,
|
|
2656
|
+
});
|
|
2657
|
+
const hash = {
|
|
2658
|
+
...preparedHash,
|
|
2659
|
+
metadata: metadataHash,
|
|
2660
|
+
} satisfies Worker["Attributes"]["hash"];
|
|
2661
|
+
// Lower the `Worker.URL` sentinel into the aliased preview URL —
|
|
2662
|
+
// same lowering `putWorker` performs, with the alias standing in
|
|
2663
|
+
// for the script's own URL.
|
|
2664
|
+
const metadataBindings = bindings.flatMap((b) =>
|
|
2665
|
+
(b.data.bindings ?? []).map((item) =>
|
|
2666
|
+
item.type === "self_url"
|
|
2667
|
+
? { type: "plain_text" as const, name: item.name, text: selfUrl! }
|
|
2668
|
+
: item,
|
|
2669
|
+
),
|
|
2670
|
+
);
|
|
2671
|
+
appendAlchemyAndEnvBindings(
|
|
2672
|
+
metadataBindings,
|
|
2673
|
+
news,
|
|
2674
|
+
accountId,
|
|
2675
|
+
parentName,
|
|
2676
|
+
);
|
|
2677
|
+
const compatibility = getCompatibility(news);
|
|
2678
|
+
yield* session.note(`Uploading version of ${parentName} ...`);
|
|
2679
|
+
const created = yield* workers
|
|
2680
|
+
.createScriptVersion({
|
|
2681
|
+
accountId,
|
|
2682
|
+
scriptName: parentName,
|
|
2683
|
+
metadata: {
|
|
2684
|
+
mainModule: bundle.main!,
|
|
2685
|
+
bindings:
|
|
2686
|
+
metadataBindings as unknown as workers.CreateScriptVersionRequest["metadata"]["bindings"],
|
|
2687
|
+
compatibilityDate: compatibility.date,
|
|
2688
|
+
compatibilityFlags: compatibility.flags,
|
|
2689
|
+
cache: news.cache ?? getCacheBinding(bindings),
|
|
2690
|
+
annotations:
|
|
2691
|
+
alias !== undefined ||
|
|
2692
|
+
version.message !== undefined ||
|
|
2693
|
+
version.tag !== undefined
|
|
2694
|
+
? {
|
|
2695
|
+
workersAlias: alias,
|
|
2696
|
+
workersMessage: version.message,
|
|
2697
|
+
workersTag: version.tag,
|
|
2698
|
+
}
|
|
2699
|
+
: undefined,
|
|
2700
|
+
},
|
|
2701
|
+
files: bundle.files,
|
|
2702
|
+
})
|
|
2703
|
+
.pipe(
|
|
2704
|
+
Effect.retry({
|
|
2705
|
+
while: isBindingTargetNotFound,
|
|
2706
|
+
schedule: bindingTargetNotFoundRetrySchedule(),
|
|
2707
|
+
}),
|
|
2708
|
+
Effect.catchTag("WorkerNotFound", () =>
|
|
2709
|
+
Effect.fail(
|
|
2710
|
+
new WorkerVersionConfigError({
|
|
2711
|
+
message: `version.parent script '${parentName}' does not exist. Deploy the parent Worker first (or check the referenced stage/stack).`,
|
|
2712
|
+
}),
|
|
2713
|
+
),
|
|
2714
|
+
),
|
|
2715
|
+
);
|
|
2716
|
+
const versionId = created.id ?? undefined;
|
|
2717
|
+
if (versionId === undefined) {
|
|
2718
|
+
return yield* Effect.fail(
|
|
2719
|
+
new WorkerVersionConfigError({
|
|
2720
|
+
message: `Cloudflare did not return a version id for the uploaded version of '${parentName}'.`,
|
|
2721
|
+
}),
|
|
2722
|
+
);
|
|
2723
|
+
}
|
|
2724
|
+
let deploymentId: string | undefined;
|
|
2725
|
+
if (traffic > 0) {
|
|
2726
|
+
yield* session.note(
|
|
2727
|
+
`Deploying version at ${traffic}% of ${parentName}'s traffic ...`,
|
|
2728
|
+
);
|
|
2729
|
+
deploymentId = yield* deployVersionTraffic({
|
|
2730
|
+
accountId,
|
|
2731
|
+
scriptName: parentName,
|
|
2732
|
+
versionId,
|
|
2733
|
+
traffic,
|
|
2734
|
+
message: version.message,
|
|
2735
|
+
});
|
|
2736
|
+
}
|
|
2737
|
+
// Version-affinity transform rules (`version.affinity`): a canary
|
|
2738
|
+
// rides the *parent's* zone traffic, so the rules land on the
|
|
2739
|
+
// parent's zones — observed custom-domain attachments plus, when
|
|
2740
|
+
// `parent` was passed as a Worker resource, its recorded routes.
|
|
2741
|
+
const previousAffinityZoneIds = output?.affinityZoneIds ?? [];
|
|
2742
|
+
let affinityZoneIds: string[] | undefined;
|
|
2743
|
+
if (version.affinity !== undefined || previousAffinityZoneIds.length) {
|
|
2744
|
+
const resolvedAffinity =
|
|
2745
|
+
version.affinity !== undefined
|
|
2746
|
+
? yield* resolveVersionAffinity(version.affinity)
|
|
2747
|
+
: undefined;
|
|
2748
|
+
let hostsByZone: Map<string, AffinityZoneHost[]> | undefined;
|
|
2749
|
+
if (resolvedAffinity !== undefined) {
|
|
2750
|
+
hostsByZone = new Map();
|
|
2751
|
+
const addHost = (zoneId: string, host: AffinityZoneHost) => {
|
|
2752
|
+
const hosts = hostsByZone!.get(zoneId) ?? [];
|
|
2753
|
+
hosts.push(host);
|
|
2754
|
+
hostsByZone!.set(zoneId, hosts);
|
|
2755
|
+
};
|
|
2756
|
+
// A `version.parent` passed as a Worker resource resolves to
|
|
2757
|
+
// the parent's Attributes at reconcile time (see
|
|
2758
|
+
// resolveVersionParentName) — its recorded routes and domain
|
|
2759
|
+
// tell us the zones the parent serves on.
|
|
2760
|
+
const parentAttrs =
|
|
2761
|
+
typeof version.parent === "object" && version.parent !== null
|
|
2762
|
+
? (version.parent as unknown as Partial<Worker["Attributes"]>)
|
|
2763
|
+
: undefined;
|
|
2764
|
+
const redirectHosts = new Set(parentAttrs?.domain?.redirects ?? []);
|
|
2765
|
+
const attached = yield* workers
|
|
2766
|
+
.listDomains({ accountId, service: parentName })
|
|
2767
|
+
.pipe(Effect.map((r) => r.result ?? []));
|
|
2768
|
+
for (const d of attached) {
|
|
2769
|
+
if (!d.hostname || !d.zoneId || redirectHosts.has(d.hostname)) {
|
|
2770
|
+
continue;
|
|
2771
|
+
}
|
|
2772
|
+
addHost(d.zoneId, { host: d.hostname, wildcard: false });
|
|
2773
|
+
}
|
|
2774
|
+
for (const route of parentAttrs?.routes ?? []) {
|
|
2775
|
+
const host = route.pattern.split("/")[0];
|
|
2776
|
+
if (!host || host.includes('"')) continue;
|
|
2777
|
+
addHost(route.zoneId, { host, wildcard: host.includes("*") });
|
|
2778
|
+
}
|
|
2779
|
+
if (hostsByZone.size === 0) {
|
|
2780
|
+
return yield* Effect.fail(
|
|
2781
|
+
new WorkerVersionConfigError({
|
|
2782
|
+
message:
|
|
2783
|
+
`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. ` +
|
|
2784
|
+
`Give the parent a \`domain\` or zone \`routes\`, or pass the parent as a Worker resource so its routes are visible here.`,
|
|
2785
|
+
}),
|
|
2786
|
+
);
|
|
2787
|
+
}
|
|
2788
|
+
yield* session.note("Reconciling version-affinity rules ...");
|
|
2789
|
+
}
|
|
2790
|
+
const placed = yield* reconcileAffinityRules({
|
|
2791
|
+
scriptName: parentName,
|
|
2792
|
+
desired:
|
|
2793
|
+
resolvedAffinity !== undefined && hostsByZone !== undefined
|
|
2794
|
+
? { affinity: resolvedAffinity, hostsByZone }
|
|
2795
|
+
: undefined,
|
|
2796
|
+
previousZoneIds: previousAffinityZoneIds,
|
|
2797
|
+
});
|
|
2798
|
+
affinityZoneIds = placed.length > 0 ? placed : undefined;
|
|
2799
|
+
}
|
|
2800
|
+
// The aliased URL is primary (`url`): it is stable across deploys,
|
|
2801
|
+
// re-pointing at each newly uploaded version. The per-version URL
|
|
2802
|
+
// (prefixed by the version id) rides along in `urls`.
|
|
2803
|
+
const versionedUrl = previewsEnabled
|
|
2804
|
+
? `https://${versionId.split("-")[0]}-${parentName}.${accountSubdomain}.workers.dev`
|
|
2805
|
+
: undefined;
|
|
2806
|
+
const urls = [
|
|
2807
|
+
...(aliasedUrl ? [aliasedUrl] : []),
|
|
2808
|
+
...(versionedUrl ? [versionedUrl] : []),
|
|
2809
|
+
];
|
|
2810
|
+
return {
|
|
2811
|
+
workerId: parentName,
|
|
2812
|
+
workerName: parentName,
|
|
2813
|
+
namespace: undefined,
|
|
2814
|
+
logpush: undefined,
|
|
2815
|
+
url: urls[0],
|
|
2816
|
+
urls,
|
|
2817
|
+
domain: undefined,
|
|
2818
|
+
tags: undefined,
|
|
2819
|
+
durableObjectNamespaces: {},
|
|
2820
|
+
accountId,
|
|
2821
|
+
routes: [],
|
|
2822
|
+
crons: [],
|
|
2823
|
+
versionOf: parentName,
|
|
2824
|
+
versionId,
|
|
2825
|
+
versionAlias: alias,
|
|
2826
|
+
deploymentId,
|
|
2827
|
+
affinityZoneIds,
|
|
2828
|
+
hash,
|
|
2829
|
+
} satisfies Worker["Attributes"];
|
|
2830
|
+
});
|
|
2831
|
+
|
|
1372
2832
|
const putWorker = Effect.fn(function* (
|
|
1373
2833
|
id: string,
|
|
1374
2834
|
news: WorkerProps,
|
|
@@ -1388,6 +2848,26 @@ export const LiveWorkerProvider = () =>
|
|
|
1388
2848
|
// account-level script. The put/settings calls switch endpoints and
|
|
1389
2849
|
// the subdomain / custom-domain / cron reconciliation is skipped.
|
|
1390
2850
|
const dispatchNamespace = resolveNamespaceName(news?.namespace);
|
|
2851
|
+
yield* validateTraffic(news.version?.traffic);
|
|
2852
|
+
if (news.version !== undefined && dispatchNamespace) {
|
|
2853
|
+
return yield* Effect.fail(
|
|
2854
|
+
new WorkerVersionConfigError({
|
|
2855
|
+
message: `Workers for Platforms user workers do not support versions or gradual deployments — remove the version prop from '${name}'.`,
|
|
2856
|
+
}),
|
|
2857
|
+
);
|
|
2858
|
+
}
|
|
2859
|
+
// Resolve the Worker's own URL up front when a `Worker.URL` binding
|
|
2860
|
+
// is present: the value must exist before the bundle is built (Vite
|
|
2861
|
+
// inlines VITE_*-prefixed env into the client bundle) and before the
|
|
2862
|
+
// metadata bindings are assembled (the `self_url` sentinel lowers
|
|
2863
|
+
// into a plain_text binding below).
|
|
2864
|
+
const selfUrl = hasSelfUrlBinding(bindings)
|
|
2865
|
+
? dispatchNamespace
|
|
2866
|
+
? yield* Effect.die(
|
|
2867
|
+
`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.`,
|
|
2868
|
+
)
|
|
2869
|
+
: yield* resolveSelfUrl(name, news, accountId)
|
|
2870
|
+
: undefined;
|
|
1391
2871
|
yield* Effect.logInfo(
|
|
1392
2872
|
`Cloudflare Worker ${olds ? "update" : "create"}: preparing bundle for ${name}`,
|
|
1393
2873
|
);
|
|
@@ -1403,6 +2883,7 @@ export const LiveWorkerProvider = () =>
|
|
|
1403
2883
|
hash: preparedHash,
|
|
1404
2884
|
} = yield* prepareAssetsAndBundle(id, news, {
|
|
1405
2885
|
skipAssetsRead: prebuiltAssets?.skip,
|
|
2886
|
+
selfUrl,
|
|
1406
2887
|
});
|
|
1407
2888
|
// When the caller supplied a precomputed hash (e.g. via
|
|
1408
2889
|
// `Command.Build`), store *that* hash in output state so the
|
|
@@ -1415,6 +2896,7 @@ export const LiveWorkerProvider = () =>
|
|
|
1415
2896
|
bindings,
|
|
1416
2897
|
accountId,
|
|
1417
2898
|
stack: { name: stack.name, stage: stack.stage },
|
|
2899
|
+
selfUrl,
|
|
1418
2900
|
});
|
|
1419
2901
|
const hash = {
|
|
1420
2902
|
...preparedHash,
|
|
@@ -1426,7 +2908,12 @@ export const LiveWorkerProvider = () =>
|
|
|
1426
2908
|
// `transferred_classes` migration below and must be stripped from the
|
|
1427
2909
|
// wire-shape binding before upload.
|
|
1428
2910
|
const metadataBindings = bindings.flatMap((b) =>
|
|
1429
|
-
(b.data.bindings ?? []).map((item):
|
|
2911
|
+
(b.data.bindings ?? []).map((item): WireWorkerBinding => {
|
|
2912
|
+
// Lower the `Worker.URL` sentinel into the resolved URL —
|
|
2913
|
+
// Cloudflare has no native binding for it.
|
|
2914
|
+
if (item.type === "self_url") {
|
|
2915
|
+
return { type: "plain_text", name: item.name, text: selfUrl! };
|
|
2916
|
+
}
|
|
1430
2917
|
if (
|
|
1431
2918
|
item.type === "durable_object_namespace" &&
|
|
1432
2919
|
item.transferredFrom !== undefined
|
|
@@ -1434,6 +2921,16 @@ export const LiveWorkerProvider = () =>
|
|
|
1434
2921
|
const { transferredFrom: _, ...rest } = item;
|
|
1435
2922
|
return rest;
|
|
1436
2923
|
}
|
|
2924
|
+
// `queueId` (mode discrimination) and `shim` (dev-mode remote
|
|
2925
|
+
// producer) are alchemy-only metadata on queue bindings — strip
|
|
2926
|
+
// them from the wire shape.
|
|
2927
|
+
if (
|
|
2928
|
+
item.type === "queue" &&
|
|
2929
|
+
(item.queueId !== undefined || item.shim !== undefined)
|
|
2930
|
+
) {
|
|
2931
|
+
const { queueId: _, shim: __, ...rest } = item;
|
|
2932
|
+
return rest;
|
|
2933
|
+
}
|
|
1437
2934
|
return item;
|
|
1438
2935
|
}),
|
|
1439
2936
|
);
|
|
@@ -1498,58 +2995,7 @@ export const LiveWorkerProvider = () =>
|
|
|
1498
2995
|
name: "ASSETS",
|
|
1499
2996
|
});
|
|
1500
2997
|
}
|
|
1501
|
-
metadataBindings
|
|
1502
|
-
{
|
|
1503
|
-
type: "plain_text",
|
|
1504
|
-
name: "ALCHEMY_PHASE",
|
|
1505
|
-
text: "runtime",
|
|
1506
|
-
},
|
|
1507
|
-
{
|
|
1508
|
-
type: "plain_text",
|
|
1509
|
-
name: "ALCHEMY_STACK_NAME",
|
|
1510
|
-
text: stack.name,
|
|
1511
|
-
},
|
|
1512
|
-
{
|
|
1513
|
-
type: "plain_text",
|
|
1514
|
-
name: "ALCHEMY_STAGE",
|
|
1515
|
-
text: stack.stage,
|
|
1516
|
-
},
|
|
1517
|
-
{
|
|
1518
|
-
type: "plain_text",
|
|
1519
|
-
name: "ALCHEMY_CLOUDFLARE_ACCOUNT_ID",
|
|
1520
|
-
text: accountId,
|
|
1521
|
-
},
|
|
1522
|
-
);
|
|
1523
|
-
// Add environment variables as metadata bindings
|
|
1524
|
-
if (news.env) {
|
|
1525
|
-
for (const [key, value] of Object.entries(news.env)) {
|
|
1526
|
-
if (value === undefined) continue;
|
|
1527
|
-
if (metadataBindings.some((b) => b.name === key)) continue;
|
|
1528
|
-
if (Redacted.isRedacted(value)) {
|
|
1529
|
-
const unredacted = Redacted.value(value);
|
|
1530
|
-
metadataBindings.push({
|
|
1531
|
-
type: "secret_text",
|
|
1532
|
-
name: key,
|
|
1533
|
-
text:
|
|
1534
|
-
typeof unredacted === "string"
|
|
1535
|
-
? unredacted
|
|
1536
|
-
: JSON.stringify(unredacted),
|
|
1537
|
-
});
|
|
1538
|
-
} else if (typeof value === "string") {
|
|
1539
|
-
metadataBindings.push({
|
|
1540
|
-
type: "plain_text",
|
|
1541
|
-
name: key,
|
|
1542
|
-
text: value,
|
|
1543
|
-
});
|
|
1544
|
-
} else {
|
|
1545
|
-
metadataBindings.push({
|
|
1546
|
-
type: "json",
|
|
1547
|
-
name: key,
|
|
1548
|
-
json: value,
|
|
1549
|
-
});
|
|
1550
|
-
}
|
|
1551
|
-
}
|
|
1552
|
-
}
|
|
2998
|
+
appendAlchemyAndEnvBindings(metadataBindings, news, accountId, name);
|
|
1553
2999
|
yield* Effect.logInfo(
|
|
1554
3000
|
`Cloudflare Worker ${olds ? "update" : "create"}: uploading script for ${name}`,
|
|
1555
3001
|
);
|
|
@@ -1896,47 +3342,155 @@ export const LiveWorkerProvider = () =>
|
|
|
1896
3342
|
tailConsumers: undefined,
|
|
1897
3343
|
usageModel: undefined,
|
|
1898
3344
|
};
|
|
1899
|
-
const
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
3345
|
+
const rolloutTraffic = getSelfRolloutTraffic(news);
|
|
3346
|
+
let versionId: string | undefined;
|
|
3347
|
+
let deploymentId: string | undefined;
|
|
3348
|
+
let worker: { id?: string | null; logpush?: boolean | null };
|
|
3349
|
+
// A gradual rollout (`version.traffic` < 100) deploys through the
|
|
3350
|
+
// versions API instead of the full-cutover script PUT. That's only
|
|
3351
|
+
// possible when the script already has a live deployment to split
|
|
3352
|
+
// traffic against (otherwise the first deploy takes 100%), and only
|
|
3353
|
+
// for changes a version can carry — code, bindings, compatibility
|
|
3354
|
+
// settings, cache. Script-level settings in the upload metadata
|
|
3355
|
+
// (tags, observability, limits, placement, logpush) keep their live
|
|
3356
|
+
// values until the next full deploy, matching
|
|
3357
|
+
// `wrangler versions upload` semantics.
|
|
3358
|
+
if (
|
|
3359
|
+
rolloutTraffic !== undefined &&
|
|
3360
|
+
existingSettings !== undefined &&
|
|
3361
|
+
output?.hash !== undefined &&
|
|
3362
|
+
!dispatchNamespace
|
|
3363
|
+
) {
|
|
3364
|
+
if (metadataAssets !== undefined) {
|
|
3365
|
+
return yield* Effect.fail(
|
|
3366
|
+
new WorkerVersionConfigError({
|
|
3367
|
+
message: `Worker '${name}' has static assets, which the versions API cannot carry — gradual rollouts (version.traffic) are not supported for Workers with assets.`,
|
|
3368
|
+
}),
|
|
1916
3369
|
);
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
3370
|
+
}
|
|
3371
|
+
const migratedClasses = [
|
|
3372
|
+
...migrations.newClasses,
|
|
3373
|
+
...migrations.newSqliteClasses,
|
|
3374
|
+
...migrations.deletedClasses,
|
|
3375
|
+
...migrations.renamedClasses.map((r) => r.to),
|
|
3376
|
+
...migrations.transferredClasses.map((t) => t.to),
|
|
3377
|
+
];
|
|
3378
|
+
if (migratedClasses.length > 0) {
|
|
3379
|
+
return yield* Effect.fail(
|
|
3380
|
+
new WorkerVersionConfigError({
|
|
3381
|
+
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.`,
|
|
3382
|
+
}),
|
|
3383
|
+
);
|
|
3384
|
+
}
|
|
3385
|
+
yield* session.note(
|
|
3386
|
+
`Uploading version of ${name} (${bundleSize}) ...`,
|
|
3387
|
+
);
|
|
3388
|
+
const created = yield* workers
|
|
3389
|
+
.createScriptVersion({
|
|
3390
|
+
accountId,
|
|
3391
|
+
scriptName: name,
|
|
3392
|
+
metadata: {
|
|
3393
|
+
mainModule: metadata.mainModule!,
|
|
3394
|
+
bindings:
|
|
3395
|
+
metadata.bindings as unknown as workers.CreateScriptVersionRequest["metadata"]["bindings"],
|
|
3396
|
+
compatibilityDate: metadata.compatibilityDate,
|
|
3397
|
+
compatibilityFlags: metadata.compatibilityFlags,
|
|
3398
|
+
cache: metadata.cache,
|
|
3399
|
+
annotations:
|
|
3400
|
+
news.version?.alias !== undefined ||
|
|
3401
|
+
news.version?.message !== undefined ||
|
|
3402
|
+
news.version?.tag !== undefined
|
|
3403
|
+
? {
|
|
3404
|
+
workersAlias: news.version?.alias,
|
|
3405
|
+
workersMessage: news.version?.message,
|
|
3406
|
+
workersTag: news.version?.tag,
|
|
3407
|
+
}
|
|
3408
|
+
: undefined,
|
|
3409
|
+
},
|
|
3410
|
+
files: bundle.files,
|
|
3411
|
+
})
|
|
3412
|
+
.pipe(
|
|
3413
|
+
Effect.retry({
|
|
3414
|
+
while: isBindingTargetNotFound,
|
|
3415
|
+
schedule: bindingTargetNotFoundRetrySchedule(),
|
|
3416
|
+
}),
|
|
3417
|
+
);
|
|
3418
|
+
versionId = created.id ?? undefined;
|
|
3419
|
+
if (versionId === undefined) {
|
|
3420
|
+
return yield* Effect.fail(
|
|
3421
|
+
new WorkerVersionConfigError({
|
|
3422
|
+
message: `Cloudflare did not return a version id for the uploaded version of '${name}'.`,
|
|
3423
|
+
}),
|
|
3424
|
+
);
|
|
3425
|
+
}
|
|
3426
|
+
if (rolloutTraffic > 0) {
|
|
3427
|
+
yield* session.note(
|
|
3428
|
+
`Deploying version at ${rolloutTraffic}% of traffic ...`,
|
|
3429
|
+
);
|
|
3430
|
+
deploymentId = yield* deployVersionTraffic({
|
|
3431
|
+
accountId,
|
|
3432
|
+
scriptName: name,
|
|
3433
|
+
versionId,
|
|
3434
|
+
traffic: rolloutTraffic,
|
|
3435
|
+
message: news.version?.message,
|
|
3436
|
+
});
|
|
3437
|
+
}
|
|
3438
|
+
worker = { id: name, logpush: existingSettings.logpush };
|
|
3439
|
+
} else {
|
|
3440
|
+
if (rolloutTraffic !== undefined) {
|
|
3441
|
+
// First deploy of this script (or a pre-create placeholder is
|
|
3442
|
+
// the only thing live) — there is no previous version worth
|
|
3443
|
+
// splitting traffic with, so this deploy takes 100%.
|
|
3444
|
+
yield* Effect.logInfo(
|
|
3445
|
+
`Cloudflare Worker ${name}: no previous live version to split traffic with; deploying at 100%`,
|
|
3446
|
+
);
|
|
3447
|
+
}
|
|
3448
|
+
worker = yield* putWorkerScriptWithMigrationRecovery();
|
|
3449
|
+
}
|
|
3450
|
+
|
|
3451
|
+
function putWorkerScriptWithMigrationRecovery() {
|
|
3452
|
+
return putWorkerScript({
|
|
3453
|
+
accountId,
|
|
3454
|
+
scriptName: name,
|
|
3455
|
+
dispatchNamespace,
|
|
3456
|
+
metadata,
|
|
3457
|
+
files: bundle.files,
|
|
3458
|
+
}).pipe(
|
|
3459
|
+
Effect.catch((err) => {
|
|
3460
|
+
// When adopting a Worker managed by Wrangler (or after a previous
|
|
3461
|
+
// deploy with mismatched migrations), the old_tag precondition
|
|
3462
|
+
// fails. The only way to discover the actual tag is through the
|
|
3463
|
+
// error message — getScriptSettings is meant to return it but
|
|
3464
|
+
// doesn't at runtime.
|
|
3465
|
+
const msg = String(
|
|
3466
|
+
typeof err === "object" && err !== null && "message" in err
|
|
3467
|
+
? err.message
|
|
3468
|
+
: err,
|
|
3469
|
+
);
|
|
3470
|
+
const expectedTag = msg.match(
|
|
3471
|
+
/when expected tag is ['"]?([^'"]+)['"]?/,
|
|
3472
|
+
)?.[1];
|
|
3473
|
+
if (expectedTag) {
|
|
3474
|
+
return putWorkerScript({
|
|
3475
|
+
accountId,
|
|
3476
|
+
scriptName: name,
|
|
3477
|
+
dispatchNamespace,
|
|
3478
|
+
metadata: {
|
|
3479
|
+
...metadata,
|
|
3480
|
+
migrations: {
|
|
3481
|
+
...migrations,
|
|
3482
|
+
oldTag: expectedTag,
|
|
3483
|
+
newTag: bumpMigrationTagVersion(expectedTag),
|
|
3484
|
+
},
|
|
1931
3485
|
},
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
}
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
3486
|
+
files: bundle.files,
|
|
3487
|
+
});
|
|
3488
|
+
}
|
|
3489
|
+
// @effect-diagnostics-next-line anyUnknownInErrorContext:off
|
|
3490
|
+
return Effect.fail(err as any);
|
|
3491
|
+
}),
|
|
3492
|
+
);
|
|
3493
|
+
}
|
|
1940
3494
|
const { settings, durableObjectNamespaces } =
|
|
1941
3495
|
yield* getWorkerSettingsWithDurableObjects(
|
|
1942
3496
|
name,
|
|
@@ -1957,20 +3511,21 @@ export const LiveWorkerProvider = () =>
|
|
|
1957
3511
|
tags: settings.tags ?? metadata.tags,
|
|
1958
3512
|
durableObjectNamespaces,
|
|
1959
3513
|
accountId,
|
|
1960
|
-
|
|
3514
|
+
urls: [],
|
|
3515
|
+
domain: undefined,
|
|
1961
3516
|
routes: [],
|
|
1962
3517
|
crons: [],
|
|
1963
3518
|
hash,
|
|
1964
3519
|
} satisfies Worker["Attributes"];
|
|
1965
3520
|
}
|
|
1966
|
-
// Reconcile workers.dev
|
|
1967
|
-
// We can't diff `news.
|
|
1968
|
-
// default to `undefined` (meaning "enable") — that
|
|
1969
|
-
// would skip the API call on every deploy where the user
|
|
1970
|
-
//
|
|
3521
|
+
// Reconcile the workers.dev settings against observed cloud state.
|
|
3522
|
+
// We can't diff `news.workersDev` against `olds.workersDev` here
|
|
3523
|
+
// because both default to `undefined` (meaning "enable") — that
|
|
3524
|
+
// comparison would skip the API call on every deploy where the user
|
|
3525
|
+
// never set the prop, leaving the subdomain in whatever state
|
|
1971
3526
|
// Cloudflare currently has it (disabled by default, or whatever
|
|
1972
3527
|
// a previous failed/external action left it as).
|
|
1973
|
-
const
|
|
3528
|
+
const workersDev = resolveWorkersDev(news.workersDev);
|
|
1974
3529
|
const observedSubdomain = yield* workers
|
|
1975
3530
|
.getScriptSubdomain({
|
|
1976
3531
|
accountId,
|
|
@@ -1983,11 +3538,11 @@ export const LiveWorkerProvider = () =>
|
|
|
1983
3538
|
})),
|
|
1984
3539
|
);
|
|
1985
3540
|
if (
|
|
1986
|
-
|
|
1987
|
-
|
|
3541
|
+
workersDev.enabled !== observedSubdomain.enabled ||
|
|
3542
|
+
workersDev.previewsEnabled !== observedSubdomain.previewsEnabled
|
|
1988
3543
|
) {
|
|
1989
3544
|
yield* session.note(
|
|
1990
|
-
`${
|
|
3545
|
+
`${workersDev.enabled || workersDev.previewsEnabled ? "Enabling" : "Disabling"} workers.dev subdomain...`,
|
|
1991
3546
|
);
|
|
1992
3547
|
// Cloudflare's script registry is eventually consistent — for the
|
|
1993
3548
|
// first few hundred ms after `putScript` returns, POST /subdomain
|
|
@@ -1997,7 +3552,7 @@ export const LiveWorkerProvider = () =>
|
|
|
1997
3552
|
// Retry the subdomain toggle on those transient tags with a short
|
|
1998
3553
|
// exponential backoff; same pattern we use elsewhere in this
|
|
1999
3554
|
// provider for DO-namespace propagation and for `putScript` itself.
|
|
2000
|
-
yield* setWorkerSubdomain(name,
|
|
3555
|
+
yield* setWorkerSubdomain(name, workersDev).pipe(
|
|
2001
3556
|
Effect.retry({
|
|
2002
3557
|
while: (error) =>
|
|
2003
3558
|
error._tag === "WorkerNotFound" ||
|
|
@@ -2010,22 +3565,99 @@ export const LiveWorkerProvider = () =>
|
|
|
2010
3565
|
}),
|
|
2011
3566
|
);
|
|
2012
3567
|
}
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
3568
|
+
// Custom domains are managed only when `domain` is declared on props
|
|
3569
|
+
// (#942): an omitted `domain` leaves live attachments alone — and
|
|
3570
|
+
// spares a plain workers.dev Worker the listDomains call on every
|
|
3571
|
+
// deploy (#926) — while `domain: null` explicitly detaches
|
|
3572
|
+
// everything. When managed, the canonical name + aliases + redirect
|
|
3573
|
+
// hostnames are all attached (DNS + edge certificate); redirect
|
|
3574
|
+
// hostnames additionally get a redirect rule, which runs before the
|
|
3575
|
+
// Worker so those requests never invoke it. Unmanaged domains
|
|
3576
|
+
// persisted in state carry forward so read keeps observing them.
|
|
3577
|
+
const manageCustomDomains = news.domain !== undefined;
|
|
3578
|
+
const domainConfig = yield* resolveWorkerDomain(news.domain);
|
|
3579
|
+
const previousDomain = stateWorkerDomain(output);
|
|
3580
|
+
let effectiveDomain = manageCustomDomains
|
|
3581
|
+
? domainConfig
|
|
3582
|
+
: previousDomain;
|
|
3583
|
+
if (!manageCustomDomains && previousDomain !== undefined) {
|
|
3584
|
+
// Unmanaged, but state remembers domains: observe which of them
|
|
3585
|
+
// are actually still attached and carry only those forward —
|
|
3586
|
+
// stale state entries (e.g. legacy records whose attach never
|
|
3587
|
+
// happened, or hostnames detached out-of-band) must not
|
|
3588
|
+
// resurface in `urls`. Plain workers.dev Workers (no persisted
|
|
3589
|
+
// domains) never pay this listDomains call (#926).
|
|
3590
|
+
const live = new Set(
|
|
3591
|
+
yield* workers.listDomains({ accountId, service: name }).pipe(
|
|
3592
|
+
Effect.map((r) =>
|
|
3593
|
+
(r.result ?? []).flatMap((d) =>
|
|
3594
|
+
d.hostname ? [d.hostname] : [],
|
|
3595
|
+
),
|
|
3596
|
+
),
|
|
3597
|
+
Effect.catch(() => Effect.succeed([] as string[])),
|
|
3598
|
+
),
|
|
3599
|
+
);
|
|
3600
|
+
const serving = [
|
|
3601
|
+
...(live.has(previousDomain.name) ? [previousDomain.name] : []),
|
|
3602
|
+
...previousDomain.aliases.filter((h) => live.has(h)),
|
|
3603
|
+
];
|
|
3604
|
+
effectiveDomain =
|
|
3605
|
+
serving.length > 0
|
|
3606
|
+
? {
|
|
3607
|
+
name: serving[0],
|
|
3608
|
+
aliases: serving.slice(1),
|
|
3609
|
+
redirects: previousDomain.redirects.filter((h) =>
|
|
3610
|
+
live.has(h),
|
|
3611
|
+
),
|
|
3612
|
+
}
|
|
3613
|
+
: undefined;
|
|
3614
|
+
}
|
|
3615
|
+
if (manageCustomDomains) {
|
|
3616
|
+
const desiredHostnames = domainConfig
|
|
3617
|
+
? [
|
|
3618
|
+
domainConfig.name,
|
|
3619
|
+
...domainConfig.aliases,
|
|
3620
|
+
...domainConfig.redirects,
|
|
3621
|
+
]
|
|
3622
|
+
: [];
|
|
2016
3623
|
yield* session.note(
|
|
2017
|
-
`Reconciling custom domains (${
|
|
3624
|
+
`Reconciling custom domains (${desiredHostnames.length}) ...`,
|
|
2018
3625
|
);
|
|
3626
|
+
// Capture hostname → zone for *currently attached* domains before
|
|
3627
|
+
// reconcile detaches removed ones — a removed redirect hostname's
|
|
3628
|
+
// zone is otherwise unresolvable and its rule couldn't be cleaned.
|
|
3629
|
+
const liveBeforeReconcile = yield* workers
|
|
3630
|
+
.listDomains({ accountId, service: name })
|
|
3631
|
+
.pipe(
|
|
3632
|
+
Effect.map((r) =>
|
|
3633
|
+
(r.result ?? []).flatMap((d) =>
|
|
3634
|
+
d.hostname && d.zoneId
|
|
3635
|
+
? [[d.hostname, d.zoneId] as const]
|
|
3636
|
+
: [],
|
|
3637
|
+
),
|
|
3638
|
+
),
|
|
3639
|
+
Effect.catch(() => Effect.succeed([])),
|
|
3640
|
+
);
|
|
3641
|
+
const reconciled = yield* reconcileDomains(name, desiredHostnames);
|
|
3642
|
+
const zoneIdByHostname = new Map([
|
|
3643
|
+
...liveBeforeReconcile,
|
|
3644
|
+
...reconciled.map((d) => [d.hostname, d.zoneId] as const),
|
|
3645
|
+
]);
|
|
3646
|
+
yield* reconcileRedirectRules({
|
|
3647
|
+
scriptName: name,
|
|
3648
|
+
domain: domainConfig,
|
|
3649
|
+
zoneIdByHostname,
|
|
3650
|
+
previousRedirects: previousDomain?.redirects ?? [],
|
|
3651
|
+
});
|
|
3652
|
+
effectiveDomain = domainConfig;
|
|
2019
3653
|
}
|
|
2020
|
-
const
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
...(workersDevUrl ? [workersDevUrl] : []),
|
|
2028
|
-
];
|
|
3654
|
+
const urls = yield* computeWorkerUrls({
|
|
3655
|
+
scriptName: name,
|
|
3656
|
+
workersDev,
|
|
3657
|
+
domain: effectiveDomain,
|
|
3658
|
+
uploadedVersionId: versionId,
|
|
3659
|
+
uploadedVersionAlias: news.version?.alias,
|
|
3660
|
+
});
|
|
2029
3661
|
const desiredRoutes = yield* normalizeRoutes(news.routes);
|
|
2030
3662
|
const previousRoutes = output?.routes ?? [];
|
|
2031
3663
|
if (desiredRoutes.length > 0 || previousRoutes.length > 0) {
|
|
@@ -2038,24 +3670,96 @@ export const LiveWorkerProvider = () =>
|
|
|
2038
3670
|
desiredRoutes,
|
|
2039
3671
|
previousRoutes,
|
|
2040
3672
|
);
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
3673
|
+
// Version-affinity transform rules (`version.affinity`): pin each
|
|
3674
|
+
// user to one version during gradual rollouts by filling the
|
|
3675
|
+
// version-key header from a stable request property, on every zone
|
|
3676
|
+
// this Worker serves on. Runs after the domain/route reconciles so
|
|
3677
|
+
// freshly attached surfaces resolve their zones, and also when only
|
|
3678
|
+
// *previous* state holds rules, so removing affinity converges.
|
|
3679
|
+
const affinity =
|
|
3680
|
+
news.version?.parent == null ? news.version?.affinity : undefined;
|
|
3681
|
+
const previousAffinityZoneIds = output?.affinityZoneIds ?? [];
|
|
3682
|
+
let affinityZoneIds: string[] | undefined;
|
|
3683
|
+
if (affinity !== undefined || previousAffinityZoneIds.length > 0) {
|
|
3684
|
+
const resolvedAffinity =
|
|
3685
|
+
affinity !== undefined
|
|
3686
|
+
? yield* resolveVersionAffinity(affinity)
|
|
3687
|
+
: undefined;
|
|
3688
|
+
let hostsByZone: Map<string, AffinityZoneHost[]> | undefined;
|
|
3689
|
+
if (resolvedAffinity !== undefined) {
|
|
3690
|
+
hostsByZone = new Map();
|
|
3691
|
+
const addHost = (zoneId: string, host: AffinityZoneHost) => {
|
|
3692
|
+
const hosts = hostsByZone!.get(zoneId) ?? [];
|
|
3693
|
+
hosts.push(host);
|
|
3694
|
+
hostsByZone!.set(zoneId, hosts);
|
|
3695
|
+
};
|
|
3696
|
+
// Serving hostnames from *observed* domain attachments (covers
|
|
3697
|
+
// managed, unmanaged, and adopted domains alike); redirect
|
|
3698
|
+
// hostnames 301 before the Worker and never need a key.
|
|
3699
|
+
const redirectHosts = new Set(effectiveDomain?.redirects ?? []);
|
|
3700
|
+
const attached = yield* workers
|
|
3701
|
+
.listDomains({ accountId, service: name })
|
|
3702
|
+
.pipe(Effect.map((r) => r.result ?? []));
|
|
3703
|
+
for (const d of attached) {
|
|
3704
|
+
if (!d.hostname || !d.zoneId || redirectHosts.has(d.hostname)) {
|
|
3705
|
+
continue;
|
|
3706
|
+
}
|
|
3707
|
+
addHost(d.zoneId, { host: d.hostname, wildcard: false });
|
|
3708
|
+
}
|
|
3709
|
+
for (const route of routes) {
|
|
3710
|
+
const host = route.pattern.split("/")[0];
|
|
3711
|
+
if (!host || host.includes('"')) continue;
|
|
3712
|
+
addHost(route.zoneId, { host, wildcard: host.includes("*") });
|
|
3713
|
+
}
|
|
3714
|
+
if (hostsByZone.size === 0) {
|
|
3715
|
+
return yield* Effect.fail(
|
|
3716
|
+
new WorkerVersionConfigError({
|
|
3717
|
+
message:
|
|
3718
|
+
`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\`. ` +
|
|
3719
|
+
`On the bare workers.dev URL, clients must send the header themselves.`,
|
|
3720
|
+
}),
|
|
3721
|
+
);
|
|
3722
|
+
}
|
|
3723
|
+
yield* session.note("Reconciling version-affinity rules ...");
|
|
3724
|
+
}
|
|
3725
|
+
const placed = yield* reconcileAffinityRules({
|
|
3726
|
+
scriptName: name,
|
|
3727
|
+
desired:
|
|
3728
|
+
resolvedAffinity !== undefined && hostsByZone !== undefined
|
|
3729
|
+
? { affinity: resolvedAffinity, hostsByZone }
|
|
3730
|
+
: undefined,
|
|
3731
|
+
previousZoneIds: previousAffinityZoneIds,
|
|
3732
|
+
});
|
|
3733
|
+
affinityZoneIds = placed.length > 0 ? placed : undefined;
|
|
3734
|
+
}
|
|
3735
|
+
const desiredCrons = normalizeCrons([
|
|
3736
|
+
...getCronBindings(bindings),
|
|
3737
|
+
...(news.crons ?? []),
|
|
3738
|
+
]);
|
|
3739
|
+
const previousCrons = output?.crons ?? [];
|
|
3740
|
+
// Same gating as read: skip getScriptSchedule when neither props nor
|
|
3741
|
+
// prior state indicate cron management (#926).
|
|
3742
|
+
const crons =
|
|
3743
|
+
desiredCrons.length > 0 || previousCrons.length > 0
|
|
3744
|
+
? yield* reconcileCrons(name, desiredCrons, previousCrons, session)
|
|
3745
|
+
: [];
|
|
2047
3746
|
return {
|
|
2048
3747
|
workerId: worker.id ?? name,
|
|
2049
3748
|
workerName: name,
|
|
2050
3749
|
namespace: undefined,
|
|
2051
3750
|
logpush: worker.logpush ?? undefined,
|
|
2052
|
-
url:
|
|
3751
|
+
url: urls[0],
|
|
3752
|
+
urls,
|
|
3753
|
+
domain: effectiveDomain,
|
|
2053
3754
|
tags: settings.tags ?? metadata.tags,
|
|
2054
3755
|
durableObjectNamespaces,
|
|
2055
3756
|
accountId,
|
|
2056
|
-
domains,
|
|
2057
3757
|
routes,
|
|
2058
3758
|
crons,
|
|
3759
|
+
versionOf: undefined,
|
|
3760
|
+
versionId,
|
|
3761
|
+
deploymentId,
|
|
3762
|
+
affinityZoneIds,
|
|
2059
3763
|
hash,
|
|
2060
3764
|
} satisfies Worker["Attributes"];
|
|
2061
3765
|
});
|
|
@@ -2074,11 +3778,34 @@ export const LiveWorkerProvider = () =>
|
|
|
2074
3778
|
// unresolved: the hash can't be computed deterministically here, and
|
|
2075
3779
|
// the eventual apply stores it once bindings resolve.
|
|
2076
3780
|
if (bindings) {
|
|
3781
|
+
// For a version worker, `Worker.URL` resolves to the *aliased
|
|
3782
|
+
// preview URL* (see `putWorkerVersion`) — recompute the same
|
|
3783
|
+
// value here so the metadata hash matches what the apply stored
|
|
3784
|
+
// and unchanged deploys stay noops.
|
|
3785
|
+
const versionParent =
|
|
3786
|
+
props.version?.parent != null
|
|
3787
|
+
? (resolveVersionParentName(props.version) ?? output.versionOf)
|
|
3788
|
+
: undefined;
|
|
3789
|
+
const selfUrl = hasSelfUrlBinding(bindings)
|
|
3790
|
+
? versionParent !== undefined
|
|
3791
|
+
? yield* Effect.gen(function* () {
|
|
3792
|
+
const alias = yield* resolveVersionAlias(
|
|
3793
|
+
id,
|
|
3794
|
+
props,
|
|
3795
|
+
versionParent,
|
|
3796
|
+
);
|
|
3797
|
+
return alias !== undefined
|
|
3798
|
+
? `https://${alias}-${versionParent}.${yield* getAccountSubdomain(accountId)}.workers.dev`
|
|
3799
|
+
: undefined;
|
|
3800
|
+
})
|
|
3801
|
+
: yield* resolveSelfUrl(output.workerName, props, accountId)
|
|
3802
|
+
: undefined;
|
|
2077
3803
|
const metadataHash = yield* resolveWorkerMetadataHash({
|
|
2078
3804
|
props,
|
|
2079
3805
|
bindings,
|
|
2080
3806
|
accountId,
|
|
2081
3807
|
stack: { name: stack.name, stage: stack.stage },
|
|
3808
|
+
selfUrl,
|
|
2082
3809
|
});
|
|
2083
3810
|
if (metadataHash !== output.hash?.metadata) {
|
|
2084
3811
|
return true;
|
|
@@ -2108,6 +3835,25 @@ export const LiveWorkerProvider = () =>
|
|
|
2108
3835
|
);
|
|
2109
3836
|
return hash !== output.hash?.input;
|
|
2110
3837
|
}
|
|
3838
|
+
// Assets-only Worker — there is no bundle to hash. A stored bundle
|
|
3839
|
+
// hash means the Worker previously had a script and is being
|
|
3840
|
+
// converted to assets-only, which must deploy.
|
|
3841
|
+
if (props.main === undefined) {
|
|
3842
|
+
if (output.hash?.bundle !== undefined) {
|
|
3843
|
+
return true;
|
|
3844
|
+
}
|
|
3845
|
+
const assetsHash =
|
|
3846
|
+
props.assets && Predicate.hasProperty(props.assets, "hash")
|
|
3847
|
+
? props.assets.hash
|
|
3848
|
+
: undefined;
|
|
3849
|
+
if (assetsHash === undefined) {
|
|
3850
|
+
// Same conservative rule as the directory-shaped `assets` below:
|
|
3851
|
+
// don't read the directory during diff; `putWorker` reads once
|
|
3852
|
+
// and keeps the existing manifest if nothing actually changed.
|
|
3853
|
+
return true;
|
|
3854
|
+
}
|
|
3855
|
+
return assetsHash !== output.hash?.assets;
|
|
3856
|
+
}
|
|
2111
3857
|
const bundleHash = yield* prepareBundle(id, props).pipe(
|
|
2112
3858
|
Effect.map((b) => b.hash),
|
|
2113
3859
|
);
|
|
@@ -2185,7 +3931,8 @@ export const LiveWorkerProvider = () =>
|
|
|
2185
3931
|
url: undefined,
|
|
2186
3932
|
tags: script.tags ?? undefined,
|
|
2187
3933
|
durableObjectNamespaces: {},
|
|
2188
|
-
|
|
3934
|
+
urls: [],
|
|
3935
|
+
domain: undefined,
|
|
2189
3936
|
routes: [],
|
|
2190
3937
|
crons: [],
|
|
2191
3938
|
},
|
|
@@ -2227,6 +3974,27 @@ export const LiveWorkerProvider = () =>
|
|
|
2227
3974
|
if (newNamespace !== oldNamespace) {
|
|
2228
3975
|
return { action: "replace" };
|
|
2229
3976
|
}
|
|
3977
|
+
// A version worker (version.parent) and a script-owning Worker are
|
|
3978
|
+
// distinct cloud resources, and a version belongs to exactly one
|
|
3979
|
+
// parent script — switching mode or parent is a replacement.
|
|
3980
|
+
const newIsVersion = news.version?.parent != null;
|
|
3981
|
+
const oldIsVersion =
|
|
3982
|
+
output?.versionOf !== undefined || olds?.version?.parent != null;
|
|
3983
|
+
if (newIsVersion !== oldIsVersion) {
|
|
3984
|
+
return { action: "replace" };
|
|
3985
|
+
}
|
|
3986
|
+
if (newIsVersion) {
|
|
3987
|
+
const newParent = resolveVersionParentName(news.version);
|
|
3988
|
+
const oldParent =
|
|
3989
|
+
output?.versionOf ?? resolveVersionParentName(olds?.version);
|
|
3990
|
+
if (
|
|
3991
|
+
newParent !== undefined &&
|
|
3992
|
+
oldParent !== undefined &&
|
|
3993
|
+
newParent !== oldParent
|
|
3994
|
+
) {
|
|
3995
|
+
return { action: "replace" };
|
|
3996
|
+
}
|
|
3997
|
+
}
|
|
2230
3998
|
const oldWorkerName =
|
|
2231
3999
|
output?.workerName ?? (yield* createWorkerName(id, olds?.name));
|
|
2232
4000
|
// Auto-generated names are engine-owned: the deployed name stays
|
|
@@ -2241,15 +4009,23 @@ export const LiveWorkerProvider = () =>
|
|
|
2241
4009
|
if (!output) {
|
|
2242
4010
|
return;
|
|
2243
4011
|
}
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
const
|
|
2248
|
-
|
|
2249
|
-
|
|
4012
|
+
// Compare the full domain config — name, aliases (ordered: they
|
|
4013
|
+
// drive `urls` order), and redirects — against persisted state
|
|
4014
|
+
// (with legacy `domains`-list state migrated on the fly).
|
|
4015
|
+
const newDomainConfig = yield* resolveWorkerDomain(news.domain);
|
|
4016
|
+
const oldDomainConfig = stateWorkerDomain(output);
|
|
4017
|
+
const domainKey = (d: ResolvedWorkerDomain | undefined) =>
|
|
4018
|
+
d === undefined
|
|
4019
|
+
? ""
|
|
4020
|
+
: JSON.stringify([d.name, d.aliases, [...d.redirects].sort()]);
|
|
4021
|
+
// An omitted `domain` unmanages the surface (#942): reconcile
|
|
4022
|
+
// carries previously-observed custom domains forward in state, so
|
|
4023
|
+
// comparing the empty desired config against those would report a
|
|
4024
|
+
// dirty plan on every deploy, forever. Only a declared `domain`
|
|
4025
|
+
// (including `null`, the explicit detach-all) participates.
|
|
2250
4026
|
const domainsChanged =
|
|
2251
|
-
|
|
2252
|
-
|
|
4027
|
+
news.domain !== undefined &&
|
|
4028
|
+
domainKey(newDomainConfig) !== domainKey(oldDomainConfig);
|
|
2253
4029
|
const newCrons = normalizeCrons([
|
|
2254
4030
|
...(Array.isArray(newBindings)
|
|
2255
4031
|
? getCronBindings(
|
|
@@ -2269,25 +4045,30 @@ export const LiveWorkerProvider = () =>
|
|
|
2269
4045
|
const routesChanged =
|
|
2270
4046
|
newRouteKeys.length !== oldRouteKeys.length ||
|
|
2271
4047
|
newRouteKeys.some((key, index) => key !== oldRouteKeys[index]);
|
|
2272
|
-
// `url` is `
|
|
2273
|
-
//
|
|
2274
|
-
//
|
|
2275
|
-
//
|
|
2276
|
-
//
|
|
2277
|
-
//
|
|
2278
|
-
//
|
|
2279
|
-
//
|
|
2280
|
-
//
|
|
2281
|
-
//
|
|
2282
|
-
//
|
|
2283
|
-
//
|
|
2284
|
-
const
|
|
2285
|
-
const
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
4048
|
+
// `url` is `urls[0]`: the canonical custom domain when one is
|
|
4049
|
+
// configured (or carried forward from state when the surface is
|
|
4050
|
+
// unmanaged, #942/#975), else the stable workers.dev URL. It's
|
|
4051
|
+
// stable across this update exactly when the recomputed value
|
|
4052
|
+
// matches what's deployed. Carry it forward as a stable only
|
|
4053
|
+
// then, so downstream resources that reference `worker.url`
|
|
4054
|
+
// (e.g. a GitHub Webhook delivery URL built via
|
|
4055
|
+
// `Output.interpolate`) resolve it to a concrete value during
|
|
4056
|
+
// planning instead of an unresolved Output — otherwise every
|
|
4057
|
+
// worker update spuriously re-updates them. Previews-only mode
|
|
4058
|
+
// and version workers derive `url` from version preview URLs —
|
|
4059
|
+
// never assumed stable here.
|
|
4060
|
+
const newWorkersDev = resolveWorkersDev(news.workersDev);
|
|
4061
|
+
const effectiveDomainConfig =
|
|
4062
|
+
news.domain !== undefined ? newDomainConfig : oldDomainConfig;
|
|
4063
|
+
const newUrl = newIsVersion
|
|
4064
|
+
? undefined
|
|
4065
|
+
: effectiveDomainConfig !== undefined
|
|
4066
|
+
? `https://${effectiveDomainConfig.name}`
|
|
4067
|
+
: newWorkersDev.enabled
|
|
4068
|
+
? (output.urls ?? []).find(
|
|
4069
|
+
(u) =>
|
|
4070
|
+
isWorkersDevHostname(urlHostname(u)) &&
|
|
4071
|
+
urlHostname(u).startsWith(`${workerName}.`),
|
|
2291
4072
|
)
|
|
2292
4073
|
: undefined;
|
|
2293
4074
|
const urlStable = newUrl !== undefined && newUrl === output.url;
|
|
@@ -2356,6 +4137,32 @@ export const LiveWorkerProvider = () =>
|
|
|
2356
4137
|
precreate: Effect.fn(function* ({ id, news, session, bindings }) {
|
|
2357
4138
|
const { accountId } = yield* yield* CloudflareEnvironment;
|
|
2358
4139
|
const name = yield* createWorkerName(id, news.name);
|
|
4140
|
+
// A version worker uploads to its parent's script during
|
|
4141
|
+
// reconcile; pre-creating a placeholder script under this
|
|
4142
|
+
// resource's own generated name would leave a stray script behind.
|
|
4143
|
+
// Version workers can't host Durable Object / Workflow classes, so
|
|
4144
|
+
// no placeholder is needed for circular bindings either. (`news`
|
|
4145
|
+
// is raw here — `version.parent` may be an unresolved ref, but its
|
|
4146
|
+
// *presence* is statically known.)
|
|
4147
|
+
if (news.version?.parent != null) {
|
|
4148
|
+
yield* Effect.logInfo(
|
|
4149
|
+
`Cloudflare Worker precreate: skipping stub for version worker ${id}`,
|
|
4150
|
+
);
|
|
4151
|
+
return {
|
|
4152
|
+
workerId: name,
|
|
4153
|
+
workerName: name,
|
|
4154
|
+
namespace: undefined,
|
|
4155
|
+
logpush: undefined,
|
|
4156
|
+
url: undefined,
|
|
4157
|
+
tags: undefined,
|
|
4158
|
+
durableObjectNamespaces: {},
|
|
4159
|
+
accountId,
|
|
4160
|
+
urls: [],
|
|
4161
|
+
domain: undefined,
|
|
4162
|
+
routes: [],
|
|
4163
|
+
crons: [],
|
|
4164
|
+
} satisfies Worker["Attributes"];
|
|
4165
|
+
}
|
|
2359
4166
|
// A Workers for Platforms user worker can't be pre-created: precreate
|
|
2360
4167
|
// runs on raw, *unresolved* props (so resources in a dependency cycle
|
|
2361
4168
|
// can signal early), meaning a `namespace` that references the
|
|
@@ -2378,7 +4185,8 @@ export const LiveWorkerProvider = () =>
|
|
|
2378
4185
|
tags: undefined,
|
|
2379
4186
|
durableObjectNamespaces: {},
|
|
2380
4187
|
accountId,
|
|
2381
|
-
|
|
4188
|
+
urls: [],
|
|
4189
|
+
domain: undefined,
|
|
2382
4190
|
routes: [],
|
|
2383
4191
|
crons: [],
|
|
2384
4192
|
} satisfies Worker["Attributes"];
|
|
@@ -2473,6 +4281,7 @@ export const LiveWorkerProvider = () =>
|
|
|
2473
4281
|
);
|
|
2474
4282
|
} else {
|
|
2475
4283
|
yield* session.note("Pre-creating worker...");
|
|
4284
|
+
const compatibility = getCompatibility(news);
|
|
2476
4285
|
const mainModule = "main.js";
|
|
2477
4286
|
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
|
|
2478
4287
|
.map(
|
|
@@ -2494,7 +4303,11 @@ export const LiveWorkerProvider = () =>
|
|
|
2494
4303
|
className,
|
|
2495
4304
|
}))
|
|
2496
4305
|
: undefined,
|
|
2497
|
-
|
|
4306
|
+
// Spreading `getCompatibility` here would set `date`/`flags`,
|
|
4307
|
+
// which are not metadata keys — the placeholder would upload
|
|
4308
|
+
// with no compatibility date or flags at all.
|
|
4309
|
+
compatibilityDate: compatibility.date,
|
|
4310
|
+
compatibilityFlags: compatibility.flags,
|
|
2498
4311
|
containers,
|
|
2499
4312
|
migrations:
|
|
2500
4313
|
doClasses.length > 0
|
|
@@ -2568,7 +4381,8 @@ export const LiveWorkerProvider = () =>
|
|
|
2568
4381
|
tags: existingSettings?.tags ?? tags,
|
|
2569
4382
|
durableObjectNamespaces,
|
|
2570
4383
|
accountId,
|
|
2571
|
-
|
|
4384
|
+
urls: [],
|
|
4385
|
+
domain: undefined,
|
|
2572
4386
|
routes: [],
|
|
2573
4387
|
crons: [],
|
|
2574
4388
|
} satisfies Worker["Attributes"];
|
|
@@ -2576,6 +4390,34 @@ export const LiveWorkerProvider = () =>
|
|
|
2576
4390
|
read: Effect.fn(
|
|
2577
4391
|
function* ({ id, output, olds }) {
|
|
2578
4392
|
const { accountId } = yield* yield* CloudflareEnvironment;
|
|
4393
|
+
// Version workers don't own a script — their `workerName` is the
|
|
4394
|
+
// *parent's* script, so the normal read below would hydrate (and
|
|
4395
|
+
// potentially brand as adoptable) a resource we don't own. The
|
|
4396
|
+
// version itself is immutable; all we verify is that it still
|
|
4397
|
+
// exists on the parent.
|
|
4398
|
+
if (
|
|
4399
|
+
output?.versionOf !== undefined ||
|
|
4400
|
+
olds?.version?.parent != null
|
|
4401
|
+
) {
|
|
4402
|
+
if (output?.versionOf === undefined || !output.versionId) {
|
|
4403
|
+
// No recorded version (e.g. state was written before the
|
|
4404
|
+
// upload completed) — nothing to observe; reconcile will
|
|
4405
|
+
// upload a fresh version.
|
|
4406
|
+
return undefined;
|
|
4407
|
+
}
|
|
4408
|
+
return yield* workers
|
|
4409
|
+
.getScriptVersion({
|
|
4410
|
+
accountId,
|
|
4411
|
+
scriptName: output.versionOf,
|
|
4412
|
+
versionId: output.versionId,
|
|
4413
|
+
})
|
|
4414
|
+
.pipe(
|
|
4415
|
+
Effect.map(() => output),
|
|
4416
|
+
Effect.catchTag(["WorkerNotFound", "VersionNotFound"], () =>
|
|
4417
|
+
Effect.succeed(undefined),
|
|
4418
|
+
),
|
|
4419
|
+
);
|
|
4420
|
+
}
|
|
2579
4421
|
const workerName =
|
|
2580
4422
|
output?.workerName ?? (yield* createWorkerName(id, olds?.name));
|
|
2581
4423
|
const dispatchNamespace =
|
|
@@ -2605,7 +4447,8 @@ export const LiveWorkerProvider = () =>
|
|
|
2605
4447
|
url: undefined,
|
|
2606
4448
|
tags: settings.tags ?? undefined,
|
|
2607
4449
|
durableObjectNamespaces: getDurableObjects(settings.bindings),
|
|
2608
|
-
|
|
4450
|
+
urls: [],
|
|
4451
|
+
domain: undefined,
|
|
2609
4452
|
routes: [],
|
|
2610
4453
|
crons: [],
|
|
2611
4454
|
} satisfies Worker["Attributes"];
|
|
@@ -2622,6 +4465,18 @@ export const LiveWorkerProvider = () =>
|
|
|
2622
4465
|
// `WorkerNotFound` if the script doesn't exist, which the
|
|
2623
4466
|
// surrounding `Effect.catchTag` turns into `undefined` — that's
|
|
2624
4467
|
// all the existence check we need.
|
|
4468
|
+
//
|
|
4469
|
+
// Domain / route / cron observation is gated on whether Alchemy
|
|
4470
|
+
// manages that surface for this Worker (#926). A plain workers.dev
|
|
4471
|
+
// Worker otherwise pays for listDomains + zone route listings +
|
|
4472
|
+
// getScriptSchedule on every plan/deploy read. Route observation
|
|
4473
|
+
// is additionally scoped to the zones state already associates
|
|
4474
|
+
// with this Worker — see readWorkerRoutes. Empty-array props
|
|
4475
|
+
// (`domain: []`, `routes: []`, `crons: []`) still observe so we
|
|
4476
|
+
// can detect drift and converge deletions.
|
|
4477
|
+
const observeDomains = shouldObserveWorkerDomains(olds, output);
|
|
4478
|
+
const observeRoutes = shouldObserveWorkerRoutes(olds, output);
|
|
4479
|
+
const observeCrons = shouldObserveWorkerCrons(olds, output);
|
|
2625
4480
|
const [subdomain, settings, domainsList, routesList] =
|
|
2626
4481
|
yield* Effect.all(
|
|
2627
4482
|
[
|
|
@@ -2633,41 +4488,73 @@ export const LiveWorkerProvider = () =>
|
|
|
2633
4488
|
accountId,
|
|
2634
4489
|
scriptName: workerName,
|
|
2635
4490
|
}),
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
4491
|
+
observeDomains
|
|
4492
|
+
? workers
|
|
4493
|
+
.listDomains({
|
|
4494
|
+
accountId,
|
|
4495
|
+
service: workerName,
|
|
4496
|
+
})
|
|
4497
|
+
.pipe(Effect.map((r) => r.result ?? []))
|
|
4498
|
+
: Effect.succeed(
|
|
4499
|
+
[] as workers.ListDomainsResponse["result"],
|
|
4500
|
+
),
|
|
4501
|
+
observeRoutes
|
|
4502
|
+
? readWorkerRoutes(workerName, output?.routes)
|
|
4503
|
+
: Effect.succeed([] as Worker["Attributes"]["routes"]),
|
|
2643
4504
|
],
|
|
2644
|
-
|
|
4505
|
+
// Bound concurrency so a single Worker read doesn't stampede
|
|
4506
|
+
// the account alongside every other Worker's lifecycle calls.
|
|
4507
|
+
{ concurrency: 1 },
|
|
2645
4508
|
);
|
|
2646
|
-
//
|
|
2647
|
-
//
|
|
2648
|
-
//
|
|
2649
|
-
//
|
|
2650
|
-
//
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
4509
|
+
// Classify the observed hostnames using the declared config
|
|
4510
|
+
// (`olds.domain`): the canonical name and each alias/redirect
|
|
4511
|
+
// keep their declared role while still attached; drift (attached
|
|
4512
|
+
// hostnames we don't know about) is appended to the aliases. The
|
|
4513
|
+
// Cloudflare API returns domains in non-deterministic order, so
|
|
4514
|
+
// the declared order is what keeps `url`/`urls` from flipping
|
|
4515
|
+
// between reads.
|
|
4516
|
+
const declared = yield* resolveWorkerDomain(olds?.domain).pipe(
|
|
4517
|
+
Effect.orElseSucceed(() => undefined),
|
|
4518
|
+
);
|
|
4519
|
+
const observed = new Set(
|
|
4520
|
+
domainsList.flatMap((d) => (d.hostname ? [d.hostname] : [])),
|
|
4521
|
+
);
|
|
4522
|
+
const keptName =
|
|
4523
|
+
declared !== undefined && observed.has(declared.name)
|
|
4524
|
+
? declared.name
|
|
4525
|
+
: undefined;
|
|
4526
|
+
const keptAliases =
|
|
4527
|
+
declared?.aliases.filter((h) => observed.has(h)) ?? [];
|
|
4528
|
+
const keptRedirects =
|
|
4529
|
+
declared?.redirects.filter((h) => observed.has(h)) ?? [];
|
|
4530
|
+
const classified = new Set([
|
|
4531
|
+
...(keptName ? [keptName] : []),
|
|
4532
|
+
...keptAliases,
|
|
4533
|
+
...keptRedirects,
|
|
4534
|
+
]);
|
|
4535
|
+
const drift = [...observed].filter((h) => !classified.has(h));
|
|
4536
|
+
const serving = [
|
|
4537
|
+
...(keptName ? [keptName] : []),
|
|
4538
|
+
...keptAliases,
|
|
4539
|
+
...drift,
|
|
2662
4540
|
];
|
|
2663
|
-
const
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
4541
|
+
const observedDomain =
|
|
4542
|
+
serving.length > 0
|
|
4543
|
+
? {
|
|
4544
|
+
name: serving[0],
|
|
4545
|
+
aliases: serving.slice(1),
|
|
4546
|
+
redirects: keptRedirects,
|
|
4547
|
+
}
|
|
4548
|
+
: undefined;
|
|
4549
|
+
const urls = [
|
|
4550
|
+
...serving.map((h) => `https://${h}`),
|
|
4551
|
+
...(subdomain.enabled
|
|
4552
|
+
? [
|
|
4553
|
+
`https://${workerName}.${yield* getAccountSubdomain(accountId)}.workers.dev`,
|
|
4554
|
+
]
|
|
4555
|
+
: []),
|
|
2669
4556
|
];
|
|
2670
|
-
const crons = yield* getWorkerCrons(workerName);
|
|
4557
|
+
const crons = observeCrons ? yield* getWorkerCrons(workerName) : [];
|
|
2671
4558
|
yield* Effect.logInfo(
|
|
2672
4559
|
`Cloudflare Worker read: found ${workerName}`,
|
|
2673
4560
|
);
|
|
@@ -2677,12 +4564,17 @@ export const LiveWorkerProvider = () =>
|
|
|
2677
4564
|
workerName,
|
|
2678
4565
|
namespace: undefined,
|
|
2679
4566
|
logpush: settings.logpush ?? undefined,
|
|
2680
|
-
url:
|
|
4567
|
+
url: urls[0],
|
|
4568
|
+
urls,
|
|
4569
|
+
domain: observedDomain,
|
|
2681
4570
|
tags: settings.tags ?? undefined,
|
|
2682
4571
|
durableObjectNamespaces: getDurableObjects(settings.bindings),
|
|
2683
|
-
domains,
|
|
2684
4572
|
routes: routesList,
|
|
2685
4573
|
crons,
|
|
4574
|
+
// Rule placement is provider-managed state, not observed here
|
|
4575
|
+
// (a getPhas call per known zone on every read); carry the
|
|
4576
|
+
// cleanup list forward like any other stable cache.
|
|
4577
|
+
affinityZoneIds: output?.affinityZoneIds,
|
|
2686
4578
|
} satisfies Worker["Attributes"];
|
|
2687
4579
|
|
|
2688
4580
|
// Centralized ownership decision: the engine routes `read`'s
|
|
@@ -2723,6 +4615,12 @@ export const LiveWorkerProvider = () =>
|
|
|
2723
4615
|
output,
|
|
2724
4616
|
session,
|
|
2725
4617
|
}) {
|
|
4618
|
+
// A version worker uploads an immutable version to its parent's
|
|
4619
|
+
// script instead of owning a script of its own — none of the
|
|
4620
|
+
// script-level observation below applies.
|
|
4621
|
+
if (news.version?.parent != null) {
|
|
4622
|
+
return yield* putWorkerVersion(id, news, bindings, session, output);
|
|
4623
|
+
}
|
|
2726
4624
|
const { accountId } = yield* yield* CloudflareEnvironment;
|
|
2727
4625
|
const name =
|
|
2728
4626
|
output?.workerName ?? (yield* createWorkerName(id, news.name));
|
|
@@ -2791,6 +4689,70 @@ export const LiveWorkerProvider = () =>
|
|
|
2791
4689
|
);
|
|
2792
4690
|
}),
|
|
2793
4691
|
delete: Effect.fn(function* ({ output }) {
|
|
4692
|
+
// Version workers own a version, not the script — `workerName` is
|
|
4693
|
+
// the *parent's* script and must never be deleted here. Versions
|
|
4694
|
+
// can't be deleted through the API (they age out of Cloudflare's
|
|
4695
|
+
// retention window); all we clean up is live traffic: if the
|
|
4696
|
+
// current deployment routes traffic to our version, restore 100%
|
|
4697
|
+
// to the other version in the split.
|
|
4698
|
+
if (output.versionOf !== undefined) {
|
|
4699
|
+
// Remove our affinity rules from the parent's zones — the
|
|
4700
|
+
// canary owned them, and with the canary gone there is no
|
|
4701
|
+
// split left to pin. Best-effort: a zone we can no longer
|
|
4702
|
+
// touch must not fail the destroy.
|
|
4703
|
+
if (output.affinityZoneIds?.length) {
|
|
4704
|
+
yield* reconcileAffinityRules({
|
|
4705
|
+
scriptName: output.versionOf,
|
|
4706
|
+
desired: undefined,
|
|
4707
|
+
previousZoneIds: output.affinityZoneIds,
|
|
4708
|
+
}).pipe(Effect.catch(() => Effect.void));
|
|
4709
|
+
}
|
|
4710
|
+
if (!output.versionId) return;
|
|
4711
|
+
yield* Effect.logInfo(
|
|
4712
|
+
`Cloudflare Worker delete: releasing version ${output.versionId} of ${output.versionOf}`,
|
|
4713
|
+
);
|
|
4714
|
+
const { deployments } = yield* workers
|
|
4715
|
+
.listScriptDeployments({
|
|
4716
|
+
accountId: output.accountId,
|
|
4717
|
+
scriptName: output.versionOf,
|
|
4718
|
+
})
|
|
4719
|
+
.pipe(
|
|
4720
|
+
Effect.catchTag("WorkerNotFound", () =>
|
|
4721
|
+
Effect.succeed({
|
|
4722
|
+
deployments:
|
|
4723
|
+
[] as workers.ListScriptDeploymentsResponse["deployments"],
|
|
4724
|
+
}),
|
|
4725
|
+
),
|
|
4726
|
+
);
|
|
4727
|
+
const latest = deployments[0];
|
|
4728
|
+
if (
|
|
4729
|
+
!latest?.versions.some((v) => v.versionId === output.versionId)
|
|
4730
|
+
) {
|
|
4731
|
+
// Not part of the live deployment — the version just ages out.
|
|
4732
|
+
return;
|
|
4733
|
+
}
|
|
4734
|
+
const stable = latest.versions
|
|
4735
|
+
.filter((v) => v.versionId !== output.versionId)
|
|
4736
|
+
.sort((a, b) => b.percentage - a.percentage)[0];
|
|
4737
|
+
if (!stable) {
|
|
4738
|
+
// Our version holds 100% of traffic; there is no other version
|
|
4739
|
+
// to restore. Deleting the resource must not take the parent's
|
|
4740
|
+
// script down — leave the deployment in place.
|
|
4741
|
+
yield* Effect.logWarning(
|
|
4742
|
+
`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.`,
|
|
4743
|
+
);
|
|
4744
|
+
return;
|
|
4745
|
+
}
|
|
4746
|
+
yield* workers
|
|
4747
|
+
.createScriptDeployment({
|
|
4748
|
+
accountId: output.accountId,
|
|
4749
|
+
scriptName: output.versionOf,
|
|
4750
|
+
strategy: "percentage",
|
|
4751
|
+
versions: [{ versionId: stable.versionId, percentage: 100 }],
|
|
4752
|
+
})
|
|
4753
|
+
.pipe(Effect.catchTag("WorkerNotFound", () => Effect.void));
|
|
4754
|
+
return;
|
|
4755
|
+
}
|
|
2794
4756
|
yield* Effect.logInfo(
|
|
2795
4757
|
`Cloudflare Worker delete: deleting ${output.workerName}`,
|
|
2796
4758
|
);
|
|
@@ -2825,6 +4787,36 @@ export const LiveWorkerProvider = () =>
|
|
|
2825
4787
|
Effect.map((r) => r.result ?? []),
|
|
2826
4788
|
Effect.catch(() => Effect.succeed([])),
|
|
2827
4789
|
);
|
|
4790
|
+
// Remove our redirect rules from each affected zone's dynamic
|
|
4791
|
+
// redirect entrypoint *before* detaching the domains — the live
|
|
4792
|
+
// domain list is what resolves each redirect hostname's zone.
|
|
4793
|
+
// Reconciling with an empty domain config removes exactly the
|
|
4794
|
+
// rules tagged `alchemy:worker:<script>:redirect:*`, leaving
|
|
4795
|
+
// everything else in the shared entrypoint untouched.
|
|
4796
|
+
// Remove our version-affinity rules from every zone state says
|
|
4797
|
+
// holds them. Best-effort, like the redirect rules below.
|
|
4798
|
+
if (output.affinityZoneIds?.length) {
|
|
4799
|
+
yield* reconcileAffinityRules({
|
|
4800
|
+
scriptName: output.workerName,
|
|
4801
|
+
desired: undefined,
|
|
4802
|
+
previousZoneIds: output.affinityZoneIds,
|
|
4803
|
+
}).pipe(Effect.catch(() => Effect.void));
|
|
4804
|
+
}
|
|
4805
|
+
const redirects = stateWorkerDomain(output)?.redirects ?? [];
|
|
4806
|
+
if (redirects.length > 0) {
|
|
4807
|
+
yield* reconcileRedirectRules({
|
|
4808
|
+
scriptName: output.workerName,
|
|
4809
|
+
domain: undefined,
|
|
4810
|
+
zoneIdByHostname: new Map(
|
|
4811
|
+
liveDomains.flatMap((d) =>
|
|
4812
|
+
d.hostname && d.zoneId
|
|
4813
|
+
? [[d.hostname, d.zoneId] as const]
|
|
4814
|
+
: [],
|
|
4815
|
+
),
|
|
4816
|
+
),
|
|
4817
|
+
previousRedirects: redirects,
|
|
4818
|
+
}).pipe(Effect.catch(() => Effect.void));
|
|
4819
|
+
}
|
|
2828
4820
|
if (liveDomains.length) {
|
|
2829
4821
|
yield* Effect.all(
|
|
2830
4822
|
liveDomains.flatMap((d) =>
|