next_api/
operation.rs

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/// 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    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/// Removes diagnostics, issues, and effects from the top-level `entrypoints` operation so that
34/// they're not duplicated across many different individual entrypoints or routes.
35#[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/// Given a selector and the `Entrypoints` operation that it comes from, connect the operation and
144/// return an `OperationVc` containing the selected value. The returned operation will keep the
145/// entire `Entrypoints` operation alive.
146#[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}