Skip to main content

next_api/
route.rs

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