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 #[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 #[turbo_tasks::function]
59 fn entries(self: Vc<Self>) -> Vc<GraphEntries>;
60 #[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 #[turbo_tasks::function]
70 fn project(self: Vc<Self>) -> Vc<Project>;
71
72 #[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 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#[turbo_tasks::value(transparent)]
309pub struct Routes(#[bincode(with = "turbo_bincode::indexmap")] FxIndexMap<RcStr, Route>);