1use anyhow::Result;
2use bincode::{Decode, Encode};
3use turbo_rcstr::RcStr;
4use turbo_tasks::{
5 FxIndexMap, NonLocalValue, OperationValue, OperationVc, ResolvedVc, TaskInput, Vc,
6 debug::ValueDebugFormat, take_effects, trace::TraceRawVcs,
7};
8use turbopack_core::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_issues();
42 let _ = take_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 TraceRawVcs,
116 PartialEq,
117 Eq,
118 Hash,
119 ValueDebugFormat,
120 NonLocalValue,
121 OperationValue,
122 Encode,
123 Decode,
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.read_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(TraceRawVcs, PartialEq, Eq, ValueDebugFormat, NonLocalValue, Encode, Decode)]
208pub struct InstrumentationOperation {
209 pub node_js: OperationVc<OptionEndpoint>,
210 pub edge: OperationVc<OptionEndpoint>,
211}
212
213#[derive(TraceRawVcs, PartialEq, Eq, ValueDebugFormat, NonLocalValue, Encode, Decode)]
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, PartialEq, Eq, ValueDebugFormat, Clone, Debug, NonLocalValue, Encode, Decode,
239)]
240pub struct AppPageRouteOperation {
241 pub original_name: RcStr,
242 pub html_endpoint: OperationVc<OptionEndpoint>,
243 pub rsc_endpoint: OperationVc<OptionEndpoint>,
244}