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::IssueDescriptionExt};
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    let _ = entrypoints.take_collectibles::<Box<dyn Diagnostic>>();
41    let _ = entrypoints.take_issues_with_path().await?;
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(|_| MiddlewareOperation {
59                endpoint: pick_endpoint(entrypoints, EndpointSelector::Middleware),
60            }),
61            instrumentation: e
62                .instrumentation
63                .as_ref()
64                .map(|_| InstrumentationOperation {
65                    node_js: pick_endpoint(entrypoints, EndpointSelector::InstrumentationNodeJs),
66                    edge: pick_endpoint(entrypoints, EndpointSelector::InstrumentationEdge),
67                }),
68            pages_document_endpoint: pick_endpoint(entrypoints, EndpointSelector::PagesDocument),
69            pages_app_endpoint: pick_endpoint(entrypoints, EndpointSelector::PagesApp),
70            pages_error_endpoint: pick_endpoint(entrypoints, EndpointSelector::PagesError),
71        }
72        .cell())
73    }
74}
75
76fn pick_route(entrypoints: OperationVc<Entrypoints>, key: RcStr, route: &Route) -> RouteOperation {
77    match route {
78        Route::Page { .. } => RouteOperation::Page {
79            html_endpoint: pick_endpoint(entrypoints, EndpointSelector::RoutePageHtml(key.clone())),
80            data_endpoint: pick_endpoint(entrypoints, EndpointSelector::RoutePageData(key)),
81        },
82        Route::PageApi { .. } => RouteOperation::PageApi {
83            endpoint: pick_endpoint(entrypoints, EndpointSelector::RoutePageApi(key)),
84        },
85        Route::AppPage(pages) => RouteOperation::AppPage(
86            pages
87                .iter()
88                .enumerate()
89                .map(|(i, p)| AppPageRouteOperation {
90                    original_name: p.original_name.clone(),
91                    html_endpoint: pick_endpoint(
92                        entrypoints,
93                        EndpointSelector::RouteAppPageHtml(key.clone(), i),
94                    ),
95                    rsc_endpoint: pick_endpoint(
96                        entrypoints,
97                        EndpointSelector::RouteAppPageRsc(key.clone(), i),
98                    ),
99                })
100                .collect(),
101        ),
102        Route::AppRoute { original_name, .. } => RouteOperation::AppRoute {
103            original_name: original_name.clone(),
104            endpoint: pick_endpoint(entrypoints, EndpointSelector::RouteAppRoute(key)),
105        },
106        Route::Conflict => RouteOperation::Conflict,
107    }
108}
109
110#[derive(
111    Debug,
112    Clone,
113    TaskInput,
114    Serialize,
115    Deserialize,
116    TraceRawVcs,
117    PartialEq,
118    Eq,
119    Hash,
120    ValueDebugFormat,
121    NonLocalValue,
122    OperationValue,
123)]
124enum EndpointSelector {
125    RoutePageHtml(RcStr),
126    RoutePageData(RcStr),
127    RoutePageApi(RcStr),
128    RouteAppPageHtml(RcStr, usize),
129    RouteAppPageRsc(RcStr, usize),
130    RouteAppRoute(RcStr),
131    InstrumentationNodeJs,
132    InstrumentationEdge,
133    Middleware,
134    PagesDocument,
135    PagesApp,
136    PagesError,
137}
138
139#[turbo_tasks::value(transparent)]
140pub struct OptionEndpoint(Option<ResolvedVc<Box<dyn Endpoint>>>);
141
142/// Given a selector and the `Entrypoints` operation that it comes from, connect the operation and
143/// return an `OperationVc` containing the selected value. The returned operation will keep the
144/// entire `Entrypoints` operation alive.
145#[turbo_tasks::function(operation)]
146async fn pick_endpoint(
147    op: OperationVc<Entrypoints>,
148    selector: EndpointSelector,
149) -> Result<Vc<OptionEndpoint>> {
150    let endpoints = op.connect().strongly_consistent().await?;
151    let endpoint = match selector {
152        EndpointSelector::InstrumentationNodeJs => {
153            endpoints.instrumentation.as_ref().map(|i| i.node_js)
154        }
155        EndpointSelector::InstrumentationEdge => endpoints.instrumentation.as_ref().map(|i| i.edge),
156        EndpointSelector::Middleware => endpoints.middleware.as_ref().map(|m| m.endpoint),
157        EndpointSelector::PagesDocument => Some(endpoints.pages_document_endpoint),
158        EndpointSelector::PagesApp => Some(endpoints.pages_app_endpoint),
159        EndpointSelector::PagesError => Some(endpoints.pages_error_endpoint),
160        EndpointSelector::RoutePageHtml(name) => {
161            if let Some(Route::Page { html_endpoint, .. }) = endpoints.routes.get(&name) {
162                Some(*html_endpoint)
163            } else {
164                None
165            }
166        }
167        EndpointSelector::RoutePageData(name) => {
168            if let Some(Route::Page { data_endpoint, .. }) = endpoints.routes.get(&name) {
169                Some(*data_endpoint)
170            } else {
171                None
172            }
173        }
174        EndpointSelector::RoutePageApi(name) => {
175            if let Some(Route::PageApi { endpoint }) = endpoints.routes.get(&name) {
176                Some(*endpoint)
177            } else {
178                None
179            }
180        }
181        EndpointSelector::RouteAppPageHtml(name, i) => {
182            if let Some(Route::AppPage(pages)) = endpoints.routes.get(&name) {
183                pages.get(i).as_ref().map(|p| p.html_endpoint)
184            } else {
185                None
186            }
187        }
188        EndpointSelector::RouteAppPageRsc(name, i) => {
189            if let Some(Route::AppPage(pages)) = endpoints.routes.get(&name) {
190                pages.get(i).as_ref().map(|p| p.rsc_endpoint)
191            } else {
192                None
193            }
194        }
195        EndpointSelector::RouteAppRoute(name) => {
196            if let Some(Route::AppRoute { endpoint, .. }) = endpoints.routes.get(&name) {
197                Some(*endpoint)
198            } else {
199                None
200            }
201        }
202    };
203    Ok(Vc::cell(endpoint))
204}
205
206#[derive(Serialize, Deserialize, TraceRawVcs, PartialEq, Eq, ValueDebugFormat, NonLocalValue)]
207pub struct InstrumentationOperation {
208    pub node_js: OperationVc<OptionEndpoint>,
209    pub edge: OperationVc<OptionEndpoint>,
210}
211
212#[derive(Serialize, Deserialize, TraceRawVcs, PartialEq, Eq, ValueDebugFormat, NonLocalValue)]
213pub struct MiddlewareOperation {
214    pub endpoint: OperationVc<OptionEndpoint>,
215}
216
217#[turbo_tasks::value(shared)]
218#[derive(Clone, Debug)]
219pub enum RouteOperation {
220    Page {
221        html_endpoint: OperationVc<OptionEndpoint>,
222        data_endpoint: OperationVc<OptionEndpoint>,
223    },
224    PageApi {
225        endpoint: OperationVc<OptionEndpoint>,
226    },
227    AppPage(Vec<AppPageRouteOperation>),
228    AppRoute {
229        original_name: RcStr,
230        endpoint: OperationVc<OptionEndpoint>,
231    },
232    Conflict,
233}
234
235#[derive(
236    TraceRawVcs,
237    Serialize,
238    Deserialize,
239    PartialEq,
240    Eq,
241    ValueDebugFormat,
242    Clone,
243    Debug,
244    NonLocalValue,
245)]
246pub struct AppPageRouteOperation {
247    pub original_name: RcStr,
248    pub html_endpoint: OperationVc<OptionEndpoint>,
249    pub rsc_endpoint: OperationVc<OptionEndpoint>,
250}