1use anyhow::Result;
2use serde::{Deserialize, Serialize};
3use turbo_rcstr::RcStr;
4use turbo_tasks::{
5 CollectiblesSource, FxIndexMap, NonLocalValue, OperationValue, OperationVc, ResolvedVc,
6 TaskInput, Vc, debug::ValueDebugFormat, get_effects, trace::TraceRawVcs,
7};
8use turbopack_core::{diagnostics::Diagnostic, issue::CollectibleIssuesExt};
9
10use crate::{
11 entrypoints::Entrypoints,
12 route::{Endpoint, Route},
13};
14
15#[turbo_tasks::value(shared)]
24pub struct EntrypointsOperation {
25 pub routes: FxIndexMap<RcStr, RouteOperation>,
26 pub middleware: Option<MiddlewareOperation>,
27 pub instrumentation: Option<InstrumentationOperation>,
28 pub pages_document_endpoint: OperationVc<OptionEndpoint>,
29 pub pages_app_endpoint: OperationVc<OptionEndpoint>,
30 pub pages_error_endpoint: OperationVc<OptionEndpoint>,
31}
32
33#[turbo_tasks::function(operation)]
36async fn entrypoints_without_collectibles_operation(
37 entrypoints: OperationVc<Entrypoints>,
38) -> Result<Vc<Entrypoints>> {
39 let _ = entrypoints.resolve_strongly_consistent().await?;
40 entrypoints.drop_collectibles::<Box<dyn Diagnostic>>();
41 entrypoints.drop_issues();
42 let _ = get_effects(entrypoints).await?;
43 Ok(entrypoints.connect())
44}
45
46#[turbo_tasks::value_impl]
47impl EntrypointsOperation {
48 #[turbo_tasks::function(operation)]
49 pub async fn new(entrypoints: OperationVc<Entrypoints>) -> Result<Vc<Self>> {
50 let e = entrypoints.connect().await?;
51 let entrypoints = entrypoints_without_collectibles_operation(entrypoints);
52 Ok(Self {
53 routes: e
54 .routes
55 .iter()
56 .map(|(k, v)| (k.clone(), pick_route(entrypoints, k.clone(), v)))
57 .collect(),
58 middleware: e.middleware.as_ref().map(|m| MiddlewareOperation {
59 endpoint: pick_endpoint(entrypoints, EndpointSelector::Middleware),
60 is_proxy: m.is_proxy,
61 }),
62 instrumentation: e
63 .instrumentation
64 .as_ref()
65 .map(|_| InstrumentationOperation {
66 node_js: pick_endpoint(entrypoints, EndpointSelector::InstrumentationNodeJs),
67 edge: pick_endpoint(entrypoints, EndpointSelector::InstrumentationEdge),
68 }),
69 pages_document_endpoint: pick_endpoint(entrypoints, EndpointSelector::PagesDocument),
70 pages_app_endpoint: pick_endpoint(entrypoints, EndpointSelector::PagesApp),
71 pages_error_endpoint: pick_endpoint(entrypoints, EndpointSelector::PagesError),
72 }
73 .cell())
74 }
75}
76
77fn pick_route(entrypoints: OperationVc<Entrypoints>, key: RcStr, route: &Route) -> RouteOperation {
78 match route {
79 Route::Page { .. } => RouteOperation::Page {
80 html_endpoint: pick_endpoint(entrypoints, EndpointSelector::RoutePageHtml(key.clone())),
81 data_endpoint: pick_endpoint(entrypoints, EndpointSelector::RoutePageData(key)),
82 },
83 Route::PageApi { .. } => RouteOperation::PageApi {
84 endpoint: pick_endpoint(entrypoints, EndpointSelector::RoutePageApi(key)),
85 },
86 Route::AppPage(pages) => RouteOperation::AppPage(
87 pages
88 .iter()
89 .enumerate()
90 .map(|(i, p)| AppPageRouteOperation {
91 original_name: p.original_name.clone(),
92 html_endpoint: pick_endpoint(
93 entrypoints,
94 EndpointSelector::RouteAppPageHtml(key.clone(), i),
95 ),
96 rsc_endpoint: pick_endpoint(
97 entrypoints,
98 EndpointSelector::RouteAppPageRsc(key.clone(), i),
99 ),
100 })
101 .collect(),
102 ),
103 Route::AppRoute { original_name, .. } => RouteOperation::AppRoute {
104 original_name: original_name.clone(),
105 endpoint: pick_endpoint(entrypoints, EndpointSelector::RouteAppRoute(key)),
106 },
107 Route::Conflict => RouteOperation::Conflict,
108 }
109}
110
111#[derive(
112 Debug,
113 Clone,
114 TaskInput,
115 Serialize,
116 Deserialize,
117 TraceRawVcs,
118 PartialEq,
119 Eq,
120 Hash,
121 ValueDebugFormat,
122 NonLocalValue,
123 OperationValue,
124)]
125enum EndpointSelector {
126 RoutePageHtml(RcStr),
127 RoutePageData(RcStr),
128 RoutePageApi(RcStr),
129 RouteAppPageHtml(RcStr, usize),
130 RouteAppPageRsc(RcStr, usize),
131 RouteAppRoute(RcStr),
132 InstrumentationNodeJs,
133 InstrumentationEdge,
134 Middleware,
135 PagesDocument,
136 PagesApp,
137 PagesError,
138}
139
140#[turbo_tasks::value(transparent)]
141pub struct OptionEndpoint(Option<ResolvedVc<Box<dyn Endpoint>>>);
142
143#[turbo_tasks::function(operation)]
147async fn pick_endpoint(
148 op: OperationVc<Entrypoints>,
149 selector: EndpointSelector,
150) -> Result<Vc<OptionEndpoint>> {
151 let endpoints = op.connect().strongly_consistent().await?;
152 let endpoint = match selector {
153 EndpointSelector::InstrumentationNodeJs => {
154 endpoints.instrumentation.as_ref().map(|i| i.node_js)
155 }
156 EndpointSelector::InstrumentationEdge => endpoints.instrumentation.as_ref().map(|i| i.edge),
157 EndpointSelector::Middleware => endpoints.middleware.as_ref().map(|m| m.endpoint),
158 EndpointSelector::PagesDocument => Some(endpoints.pages_document_endpoint),
159 EndpointSelector::PagesApp => Some(endpoints.pages_app_endpoint),
160 EndpointSelector::PagesError => Some(endpoints.pages_error_endpoint),
161 EndpointSelector::RoutePageHtml(name) => {
162 if let Some(Route::Page { html_endpoint, .. }) = endpoints.routes.get(&name) {
163 Some(*html_endpoint)
164 } else {
165 None
166 }
167 }
168 EndpointSelector::RoutePageData(name) => {
169 if let Some(Route::Page { data_endpoint, .. }) = endpoints.routes.get(&name) {
170 *data_endpoint
171 } else {
172 None
173 }
174 }
175 EndpointSelector::RoutePageApi(name) => {
176 if let Some(Route::PageApi { endpoint }) = endpoints.routes.get(&name) {
177 Some(*endpoint)
178 } else {
179 None
180 }
181 }
182 EndpointSelector::RouteAppPageHtml(name, i) => {
183 if let Some(Route::AppPage(pages)) = endpoints.routes.get(&name) {
184 pages.get(i).as_ref().map(|p| p.html_endpoint)
185 } else {
186 None
187 }
188 }
189 EndpointSelector::RouteAppPageRsc(name, i) => {
190 if let Some(Route::AppPage(pages)) = endpoints.routes.get(&name) {
191 pages.get(i).as_ref().map(|p| p.rsc_endpoint)
192 } else {
193 None
194 }
195 }
196 EndpointSelector::RouteAppRoute(name) => {
197 if let Some(Route::AppRoute { endpoint, .. }) = endpoints.routes.get(&name) {
198 Some(*endpoint)
199 } else {
200 None
201 }
202 }
203 };
204 Ok(Vc::cell(endpoint))
205}
206
207#[derive(Serialize, Deserialize, TraceRawVcs, PartialEq, Eq, ValueDebugFormat, NonLocalValue)]
208pub struct InstrumentationOperation {
209 pub node_js: OperationVc<OptionEndpoint>,
210 pub edge: OperationVc<OptionEndpoint>,
211}
212
213#[derive(Serialize, Deserialize, TraceRawVcs, PartialEq, Eq, ValueDebugFormat, NonLocalValue)]
214pub struct MiddlewareOperation {
215 pub endpoint: OperationVc<OptionEndpoint>,
216 pub is_proxy: bool,
217}
218
219#[turbo_tasks::value(shared)]
220#[derive(Clone, Debug)]
221pub enum RouteOperation {
222 Page {
223 html_endpoint: OperationVc<OptionEndpoint>,
224 data_endpoint: OperationVc<OptionEndpoint>,
225 },
226 PageApi {
227 endpoint: OperationVc<OptionEndpoint>,
228 },
229 AppPage(Vec<AppPageRouteOperation>),
230 AppRoute {
231 original_name: RcStr,
232 endpoint: OperationVc<OptionEndpoint>,
233 },
234 Conflict,
235}
236
237#[derive(
238 TraceRawVcs,
239 Serialize,
240 Deserialize,
241 PartialEq,
242 Eq,
243 ValueDebugFormat,
244 Clone,
245 Debug,
246 NonLocalValue,
247)]
248pub struct AppPageRouteOperation {
249 pub original_name: RcStr,
250 pub html_endpoint: OperationVc<OptionEndpoint>,
251 pub rsc_endpoint: OperationVc<OptionEndpoint>,
252}