Skip to main content

next_api/
route.rs

1use std::fmt::Display;
2
3use anyhow::Result;
4use bincode::{Decode, Encode};
5use next_core::app_structure::FileSystemPathVec;
6use turbo_rcstr::RcStr;
7use turbo_tasks::{
8    Completion, FxIndexMap, FxIndexSet, NonLocalValue, OperationVc, ResolvedVc, TryFlatJoinIterExt,
9    TryJoinIterExt, Vc, debug::ValueDebugFormat, trace::TraceRawVcs,
10};
11use turbopack_core::{
12    module_graph::{GraphEntries, ModuleGraph},
13    output::OutputAssets,
14};
15
16use crate::{operation::OptionEndpoint, paths::AssetPath, project::Project};
17
18#[derive(
19    TraceRawVcs, PartialEq, Eq, ValueDebugFormat, Clone, Debug, NonLocalValue, Encode, Decode,
20)]
21pub struct AppPageRoute {
22    pub original_name: RcStr,
23    pub html_endpoint: ResolvedVc<Box<dyn Endpoint>>,
24    pub rsc_endpoint: ResolvedVc<Box<dyn Endpoint>>,
25}
26
27#[turbo_tasks::value(shared)]
28#[derive(Clone, Debug)]
29pub enum Route {
30    Page {
31        html_endpoint: ResolvedVc<Box<dyn Endpoint>>,
32        data_endpoint: Option<ResolvedVc<Box<dyn Endpoint>>>,
33    },
34    PageApi {
35        endpoint: ResolvedVc<Box<dyn Endpoint>>,
36    },
37    AppPage(Vec<AppPageRoute>),
38    AppRoute {
39        original_name: RcStr,
40        endpoint: ResolvedVc<Box<dyn Endpoint>>,
41    },
42    Conflict,
43}
44
45#[turbo_tasks::value(transparent)]
46pub struct ModuleGraphs(Vec<ResolvedVc<ModuleGraph>>);
47
48#[turbo_tasks::value_trait]
49pub trait Endpoint {
50    #[turbo_tasks::function]
51    fn output(self: Vc<Self>) -> Vc<EndpointOutput>;
52    // fn write_to_disk(self: Vc<Self>) -> Vc<EndpointOutputPaths>;
53    #[turbo_tasks::function]
54    fn server_changed(self: Vc<Self>) -> Vc<Completion>;
55    #[turbo_tasks::function]
56    fn client_changed(self: Vc<Self>) -> Vc<Completion>;
57    /// The entry modules for the modules graph.
58    #[turbo_tasks::function]
59    fn entries(self: Vc<Self>) -> Vc<GraphEntries>;
60    /// Additional entry modules for the module graph.
61    /// This may read the module graph and return additional modules.
62    #[turbo_tasks::function]
63    fn additional_entries(self: Vc<Self>, _graph: Vc<ModuleGraph>) -> Vc<GraphEntries> {
64        GraphEntries::empty()
65    }
66    #[turbo_tasks::function]
67    fn module_graphs(self: Vc<Self>) -> Vc<ModuleGraphs>;
68    /// The project this endpoint belongs to.
69    #[turbo_tasks::function]
70    fn project(self: Vc<Self>) -> Vc<Project>;
71
72    /// The traced files included by this endpoint. This is only used for analysis purposes.
73    /// Usually, `output()` includes the NFT file and everything else is handled outside of
74    /// Turbopack.
75    #[turbo_tasks::function]
76    fn traced_files(self: Vc<Self>) -> Vc<FileSystemPathVec>;
77}
78
79#[derive(
80    TraceRawVcs, PartialEq, Eq, ValueDebugFormat, Clone, Debug, NonLocalValue, Encode, Decode,
81)]
82pub enum EndpointGroupKey {
83    Instrumentation,
84    InstrumentationEdge,
85    Middleware,
86    PagesError,
87    PagesApp,
88    PagesDocument,
89    Route(RcStr),
90}
91
92impl EndpointGroupKey {
93    pub fn as_str(&self) -> &str {
94        match self {
95            EndpointGroupKey::Instrumentation => "instrumentation",
96            EndpointGroupKey::InstrumentationEdge => "instrumentation-edge",
97            EndpointGroupKey::Middleware => "middleware",
98            EndpointGroupKey::PagesError => "_error",
99            EndpointGroupKey::PagesApp => "_app",
100            EndpointGroupKey::PagesDocument => "_document",
101            EndpointGroupKey::Route(route) => route,
102        }
103    }
104}
105
106impl Display for EndpointGroupKey {
107    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
108        match self {
109            EndpointGroupKey::Instrumentation => write!(f, "instrumentation"),
110            EndpointGroupKey::InstrumentationEdge => write!(f, "instrumentation-edge"),
111            EndpointGroupKey::Middleware => write!(f, "middleware"),
112            EndpointGroupKey::PagesError => write!(f, "_error"),
113            EndpointGroupKey::PagesApp => write!(f, "_app"),
114            EndpointGroupKey::PagesDocument => write!(f, "_document"),
115            EndpointGroupKey::Route(route) => write!(f, "{}", route),
116        }
117    }
118}
119
120#[derive(
121    TraceRawVcs, PartialEq, Eq, ValueDebugFormat, Clone, Debug, NonLocalValue, Encode, Decode,
122)]
123pub struct EndpointGroupEntry {
124    pub endpoint: ResolvedVc<Box<dyn Endpoint>>,
125    pub sub_name: Option<RcStr>,
126}
127
128#[derive(
129    TraceRawVcs, PartialEq, Eq, ValueDebugFormat, Clone, Debug, NonLocalValue, Encode, Decode,
130)]
131pub struct EndpointGroup {
132    pub primary: Vec<EndpointGroupEntry>,
133    pub additional: Vec<EndpointGroupEntry>,
134}
135
136impl EndpointGroup {
137    pub fn from(endpoint: ResolvedVc<Box<dyn Endpoint>>) -> Self {
138        Self {
139            primary: vec![EndpointGroupEntry {
140                endpoint,
141                sub_name: None,
142            }],
143            additional: vec![],
144        }
145    }
146
147    pub fn output_assets(&self) -> Vc<OutputAssets> {
148        output_of_endpoints(
149            self.primary
150                .iter()
151                .map(|endpoint| *endpoint.endpoint)
152                .collect(),
153        )
154    }
155
156    pub fn module_graphs(&self) -> Vc<ModuleGraphs> {
157        module_graphs_of_endpoints(
158            self.primary
159                .iter()
160                .map(|endpoint| *endpoint.endpoint)
161                .collect(),
162        )
163    }
164
165    pub fn traced_files(&self) -> Vc<FileSystemPathVec> {
166        traced_files_of_endpoints(
167            self.primary
168                .iter()
169                .map(|endpoint| *endpoint.endpoint)
170                .collect(),
171        )
172    }
173}
174
175#[turbo_tasks::function]
176async fn output_of_endpoints(endpoints: Vec<Vc<Box<dyn Endpoint>>>) -> Result<Vc<OutputAssets>> {
177    let assets = endpoints
178        .iter()
179        .map(async |endpoint| Ok(*endpoint.output().await?.output_assets))
180        .try_join()
181        .await?;
182    Ok(OutputAssets::concat(assets))
183}
184
185#[turbo_tasks::function]
186async fn module_graphs_of_endpoints(
187    endpoints: Vec<Vc<Box<dyn Endpoint>>>,
188) -> Result<Vc<ModuleGraphs>> {
189    let module_graphs = endpoints
190        .iter()
191        .map(async |endpoint| Ok(endpoint.module_graphs().await?.into_iter()))
192        .try_flat_join()
193        .await?
194        .into_iter()
195        .collect::<FxIndexSet<_>>()
196        .into_iter()
197        .collect::<Vec<_>>();
198    Ok(Vc::cell(module_graphs))
199}
200
201#[turbo_tasks::function]
202async fn traced_files_of_endpoints(
203    endpoints: Vec<Vc<Box<dyn Endpoint>>>,
204) -> Result<Vc<FileSystemPathVec>> {
205    let mut modules: FxIndexSet<_> = FxIndexSet::default();
206    for endpoint in endpoints {
207        modules.extend(endpoint.traced_files().await?.iter().cloned());
208    }
209    Ok(Vc::cell(modules.into_iter().collect()))
210}
211
212#[turbo_tasks::value(transparent)]
213pub struct EndpointGroups(Vec<(EndpointGroupKey, EndpointGroup)>);
214
215#[turbo_tasks::value(transparent)]
216pub struct Endpoints(Vec<ResolvedVc<Box<dyn Endpoint>>>);
217
218#[turbo_tasks::function]
219pub async fn endpoint_write_to_disk(
220    endpoint: ResolvedVc<Box<dyn Endpoint>>,
221) -> Result<Vc<EndpointOutputPaths>> {
222    let output_op = output_assets_operation(endpoint);
223    let EndpointOutput {
224        project,
225        output_paths,
226        ..
227    } = *output_op.connect().await?;
228
229    project
230        .emit_all_output_assets(endpoint_output_assets_operation(output_op))
231        .as_side_effect()
232        .await?;
233
234    Ok(*output_paths)
235}
236
237#[turbo_tasks::function(operation)]
238fn output_assets_operation(endpoint: ResolvedVc<Box<dyn Endpoint>>) -> Vc<EndpointOutput> {
239    endpoint.output()
240}
241
242#[turbo_tasks::function(operation)]
243async fn endpoint_output_assets_operation(
244    output: OperationVc<EndpointOutput>,
245) -> Result<Vc<OutputAssets>> {
246    Ok(*output.connect().await?.output_assets)
247}
248
249#[turbo_tasks::function(operation, root)]
250pub async fn endpoint_write_to_disk_operation(
251    endpoint: OperationVc<OptionEndpoint>,
252) -> Result<Vc<EndpointOutputPaths>> {
253    Ok(if let Some(endpoint) = *endpoint.connect().await? {
254        endpoint_write_to_disk(*endpoint)
255    } else {
256        EndpointOutputPaths::NotFound.cell()
257    })
258}
259
260#[turbo_tasks::function(operation, root)]
261pub async fn endpoint_server_changed_operation(
262    endpoint: OperationVc<OptionEndpoint>,
263) -> Result<Vc<Completion>> {
264    Ok(if let Some(endpoint) = *endpoint.connect().await? {
265        endpoint.server_changed()
266    } else {
267        Completion::new()
268    })
269}
270
271#[turbo_tasks::function(operation, root)]
272pub async fn endpoint_client_changed_operation(
273    endpoint: OperationVc<OptionEndpoint>,
274) -> Result<Vc<Completion>> {
275    Ok(if let Some(endpoint) = *endpoint.connect().await? {
276        endpoint.client_changed()
277    } else {
278        Completion::new()
279    })
280}
281
282#[turbo_tasks::value(shared)]
283#[derive(Debug, Clone)]
284pub struct EndpointOutput {
285    pub output_assets: ResolvedVc<OutputAssets>,
286    pub output_paths: ResolvedVc<EndpointOutputPaths>,
287    pub project: ResolvedVc<Project>,
288}
289
290#[turbo_tasks::value(shared)]
291#[derive(Debug, Clone)]
292pub enum EndpointOutputPaths {
293    NodeJs {
294        /// Relative to the root_path
295        server_entry_path: RcStr,
296        server_paths: Vec<AssetPath>,
297        client_paths: Vec<RcStr>,
298    },
299    Edge {
300        server_paths: Vec<AssetPath>,
301        client_paths: Vec<RcStr>,
302    },
303    NotFound,
304}
305
306/// The routes as map from pathname to route. (pathname includes the leading
307/// slash)
308#[turbo_tasks::value(transparent)]
309pub struct Routes(#[bincode(with = "turbo_bincode::indexmap")] FxIndexMap<RcStr, Route>);