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::ServerPath, 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}
68
69#[derive(
70    TraceRawVcs, PartialEq, Eq, ValueDebugFormat, Clone, Debug, NonLocalValue, Encode, Decode,
71)]
72pub enum EndpointGroupKey {
73    Instrumentation,
74    InstrumentationEdge,
75    Middleware,
76    PagesError,
77    PagesApp,
78    PagesDocument,
79    Route(RcStr),
80}
81
82impl EndpointGroupKey {
83    pub fn as_str(&self) -> &str {
84        match self {
85            EndpointGroupKey::Instrumentation => "instrumentation",
86            EndpointGroupKey::InstrumentationEdge => "instrumentation-edge",
87            EndpointGroupKey::Middleware => "middleware",
88            EndpointGroupKey::PagesError => "_error",
89            EndpointGroupKey::PagesApp => "_app",
90            EndpointGroupKey::PagesDocument => "_document",
91            EndpointGroupKey::Route(route) => route,
92        }
93    }
94}
95
96impl Display for EndpointGroupKey {
97    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
98        match self {
99            EndpointGroupKey::Instrumentation => write!(f, "instrumentation"),
100            EndpointGroupKey::InstrumentationEdge => write!(f, "instrumentation-edge"),
101            EndpointGroupKey::Middleware => write!(f, "middleware"),
102            EndpointGroupKey::PagesError => write!(f, "_error"),
103            EndpointGroupKey::PagesApp => write!(f, "_app"),
104            EndpointGroupKey::PagesDocument => write!(f, "_document"),
105            EndpointGroupKey::Route(route) => write!(f, "{}", route),
106        }
107    }
108}
109
110#[derive(
111    TraceRawVcs, PartialEq, Eq, ValueDebugFormat, Clone, Debug, NonLocalValue, Encode, Decode,
112)]
113pub struct EndpointGroupEntry {
114    pub endpoint: ResolvedVc<Box<dyn Endpoint>>,
115    pub sub_name: Option<RcStr>,
116}
117
118#[derive(
119    TraceRawVcs, PartialEq, Eq, ValueDebugFormat, Clone, Debug, NonLocalValue, Encode, Decode,
120)]
121pub struct EndpointGroup {
122    pub primary: Vec<EndpointGroupEntry>,
123    pub additional: Vec<EndpointGroupEntry>,
124}
125
126impl EndpointGroup {
127    pub fn from(endpoint: ResolvedVc<Box<dyn Endpoint>>) -> Self {
128        Self {
129            primary: vec![EndpointGroupEntry {
130                endpoint,
131                sub_name: None,
132            }],
133            additional: vec![],
134        }
135    }
136
137    pub fn output_assets(&self) -> Vc<OutputAssets> {
138        output_of_endpoints(
139            self.primary
140                .iter()
141                .map(|endpoint| *endpoint.endpoint)
142                .collect(),
143        )
144    }
145
146    pub fn module_graphs(&self) -> Vc<ModuleGraphs> {
147        module_graphs_of_endpoints(
148            self.primary
149                .iter()
150                .map(|endpoint| *endpoint.endpoint)
151                .collect(),
152        )
153    }
154}
155
156#[turbo_tasks::function]
157async fn output_of_endpoints(endpoints: Vec<Vc<Box<dyn Endpoint>>>) -> Result<Vc<OutputAssets>> {
158    let assets = endpoints
159        .iter()
160        .map(async |endpoint| Ok(*endpoint.output().await?.output_assets))
161        .try_join()
162        .await?;
163    Ok(OutputAssets::concat(assets))
164}
165
166#[turbo_tasks::function]
167async fn module_graphs_of_endpoints(
168    endpoints: Vec<Vc<Box<dyn Endpoint>>>,
169) -> Result<Vc<ModuleGraphs>> {
170    let module_graphs = endpoints
171        .iter()
172        .map(async |endpoint| Ok(endpoint.module_graphs().await?.into_iter()))
173        .try_flat_join()
174        .await?
175        .into_iter()
176        .copied()
177        .collect::<FxIndexSet<_>>()
178        .into_iter()
179        .collect::<Vec<_>>();
180    Ok(Vc::cell(module_graphs))
181}
182
183#[turbo_tasks::value(transparent)]
184pub struct EndpointGroups(Vec<(EndpointGroupKey, EndpointGroup)>);
185
186#[turbo_tasks::value(transparent)]
187pub struct Endpoints(Vec<ResolvedVc<Box<dyn Endpoint>>>);
188
189#[turbo_tasks::function]
190pub async fn endpoint_write_to_disk(
191    endpoint: ResolvedVc<Box<dyn Endpoint>>,
192) -> Result<Vc<EndpointOutputPaths>> {
193    let output_op = output_assets_operation(endpoint);
194    let EndpointOutput {
195        project,
196        output_paths,
197        ..
198    } = *output_op.connect().await?;
199
200    project
201        .emit_all_output_assets(endpoint_output_assets_operation(output_op))
202        .as_side_effect()
203        .await?;
204
205    Ok(*output_paths)
206}
207
208#[turbo_tasks::function(operation)]
209fn output_assets_operation(endpoint: ResolvedVc<Box<dyn Endpoint>>) -> Vc<EndpointOutput> {
210    endpoint.output()
211}
212
213#[turbo_tasks::function(operation)]
214async fn endpoint_output_assets_operation(
215    output: OperationVc<EndpointOutput>,
216) -> Result<Vc<OutputAssets>> {
217    Ok(*output.connect().await?.output_assets)
218}
219
220#[turbo_tasks::function(operation)]
221pub async fn endpoint_write_to_disk_operation(
222    endpoint: OperationVc<OptionEndpoint>,
223) -> Result<Vc<EndpointOutputPaths>> {
224    Ok(if let Some(endpoint) = *endpoint.connect().await? {
225        endpoint_write_to_disk(*endpoint)
226    } else {
227        EndpointOutputPaths::NotFound.cell()
228    })
229}
230
231#[turbo_tasks::function(operation)]
232pub async fn endpoint_server_changed_operation(
233    endpoint: OperationVc<OptionEndpoint>,
234) -> Result<Vc<Completion>> {
235    Ok(if let Some(endpoint) = *endpoint.connect().await? {
236        endpoint.server_changed()
237    } else {
238        Completion::new()
239    })
240}
241
242#[turbo_tasks::function(operation)]
243pub async fn endpoint_client_changed_operation(
244    endpoint: OperationVc<OptionEndpoint>,
245) -> Result<Vc<Completion>> {
246    Ok(if let Some(endpoint) = *endpoint.connect().await? {
247        endpoint.client_changed()
248    } else {
249        Completion::new()
250    })
251}
252
253#[turbo_tasks::value(shared)]
254#[derive(Debug, Clone)]
255pub struct EndpointOutput {
256    pub output_assets: ResolvedVc<OutputAssets>,
257    pub output_paths: ResolvedVc<EndpointOutputPaths>,
258    pub project: ResolvedVc<Project>,
259}
260
261#[turbo_tasks::value(shared)]
262#[derive(Debug, Clone)]
263pub enum EndpointOutputPaths {
264    NodeJs {
265        /// Relative to the root_path
266        server_entry_path: RcStr,
267        server_paths: Vec<ServerPath>,
268        client_paths: Vec<RcStr>,
269    },
270    Edge {
271        server_paths: Vec<ServerPath>,
272        client_paths: Vec<RcStr>,
273    },
274    NotFound,
275}
276
277/// The routes as map from pathname to route. (pathname includes the leading
278/// slash)
279#[turbo_tasks::value(transparent)]
280pub struct Routes(#[bincode(with = "turbo_bincode::indexmap")] FxIndexMap<RcStr, Route>);