next_api/
operation.rs

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/// Based on [`Entrypoints`], but with [`OperationVc<Endpoint>`][OperationVc] for every endpoint.
16///
17/// This is used when constructing `ExternalEndpoint`s in the `napi` crate.
18///
19/// This is important as `OperationVc`s can be stored in the VersionedContentMap and can be exposed
20/// to JS via napi.
21///
22/// This is needed to call `write_to_disk` which expects an `OperationVc<Endpoint>`.
23#[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/// Removes diagnostics, issues, and effects from the top-level `entrypoints` operation so that
35/// they're not duplicated across many different individual entrypoints or routes.
36#[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/// Given a selector and the `Entrypoints` operation that it comes from, connect the operation and
145/// return an `OperationVc` containing the selected value. The returned operation will keep the
146/// entire `Entrypoints` operation alive.
147#[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}