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