1use std::fmt::Display;
2
3use anyhow::Result;
4use bincode::{Decode, Encode};
5use itertools::Itertools;
6use next_core::app_structure::FileSystemPathVec;
7use turbo_rcstr::RcStr;
8use turbo_tasks::{
9 Completion, FxIndexMap, FxIndexSet, JoinIterExt, NonLocalValue, OperationVc, ResolvedVc,
10 TryJoinIterExt, Vc, debug::ValueDebugFormat,
11};
12use turbopack_core::{
13 module::Module,
14 module_graph::{GraphEntries, ModuleGraph},
15 output::OutputAssets,
16};
17
18use crate::{operation::OptionEndpoint, paths::AssetPath, project::Project};
19
20#[derive(PartialEq, Eq, ValueDebugFormat, Clone, Debug, NonLocalValue, Encode, Decode)]
21pub struct AppPageRoute {
22 pub original_name: RcStr,
23 pub html_endpoint: ResolvedVc<Box<dyn Endpoint>>,
24 pub rsc_hmr_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 has_action_manifest: bool,
42 },
43 Conflict,
44}
45
46#[turbo_tasks::value(transparent)]
47pub struct ModuleGraphs(Vec<ResolvedVc<ModuleGraph>>);
48
49#[turbo_tasks::value(shared)]
51#[derive(Clone, Debug, Default)]
52pub struct AnalyzeClientEntries {
53 pub server_modules: Vec<ResolvedVc<Box<dyn Module>>>,
55 pub bootstrap_modules: Vec<ResolvedVc<Box<dyn Module>>>,
57 pub references: Vec<AnalyzeClientReference>,
59}
60
61#[derive(Clone, Debug, PartialEq, Eq, NonLocalValue, Encode, Decode)]
62pub struct AnalyzeClientReference {
63 pub module: ResolvedVc<Box<dyn Module>>,
65 pub kind: RcStr,
67}
68
69#[derive(Clone, Debug, PartialEq, Eq, NonLocalValue, Encode, Decode)]
72pub struct AnalyzeChunkGroup {
73 pub kind: RcStr,
74 pub trigger: Option<ResolvedVc<Box<dyn Module>>>,
75 pub assets: ResolvedVc<OutputAssets>,
76 pub pages_html: bool,
78}
79
80#[turbo_tasks::value(transparent)]
81pub struct AnalyzeChunkGroups(Vec<AnalyzeChunkGroup>);
82
83#[turbo_tasks::value_trait]
84pub trait Endpoint {
85 #[turbo_tasks::function]
86 fn output(self: Vc<Self>) -> Vc<EndpointOutput>;
87 #[turbo_tasks::function]
89 fn server_changed(self: Vc<Self>) -> Vc<Completion>;
90 #[turbo_tasks::function]
91 fn client_changed(self: Vc<Self>) -> Vc<Completion>;
92 #[turbo_tasks::function]
94 fn entries(self: Vc<Self>) -> Vc<GraphEntries>;
95 #[turbo_tasks::function]
97 fn analyze_client_entries(self: Vc<Self>) -> Vc<AnalyzeClientEntries> {
98 AnalyzeClientEntries::default().cell()
99 }
100 #[turbo_tasks::function]
102 fn analyze_chunk_groups(self: Vc<Self>) -> Vc<AnalyzeChunkGroups> {
103 Vc::cell(vec![])
104 }
105 #[turbo_tasks::function]
108 fn additional_entries(self: Vc<Self>, _graph: Vc<ModuleGraph>) -> Vc<GraphEntries> {
109 GraphEntries::empty()
110 }
111 #[turbo_tasks::function]
112 fn module_graphs(self: Vc<Self>) -> Vc<ModuleGraphs>;
113 #[turbo_tasks::function]
115 fn project(self: Vc<Self>) -> Vc<Project>;
116
117 #[turbo_tasks::function]
121 fn traced_files(self: Vc<Self>) -> Vc<FileSystemPathVec>;
122}
123
124#[derive(PartialEq, Eq, ValueDebugFormat, Clone, Debug, NonLocalValue, Encode, Decode)]
125pub enum EndpointGroupKey {
126 Instrumentation,
127 InstrumentationEdge,
128 Middleware,
129 PagesError,
130 PagesApp,
131 PagesDocument,
132 Route(RcStr),
133}
134
135impl EndpointGroupKey {
136 pub fn as_str(&self) -> &str {
137 match self {
138 EndpointGroupKey::Instrumentation => "instrumentation",
139 EndpointGroupKey::InstrumentationEdge => "instrumentation-edge",
140 EndpointGroupKey::Middleware => "middleware",
141 EndpointGroupKey::PagesError => "_error",
142 EndpointGroupKey::PagesApp => "_app",
143 EndpointGroupKey::PagesDocument => "_document",
144 EndpointGroupKey::Route(route) => route,
145 }
146 }
147}
148
149impl Display for EndpointGroupKey {
150 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
151 match self {
152 EndpointGroupKey::Instrumentation => write!(f, "instrumentation"),
153 EndpointGroupKey::InstrumentationEdge => write!(f, "instrumentation-edge"),
154 EndpointGroupKey::Middleware => write!(f, "middleware"),
155 EndpointGroupKey::PagesError => write!(f, "_error"),
156 EndpointGroupKey::PagesApp => write!(f, "_app"),
157 EndpointGroupKey::PagesDocument => write!(f, "_document"),
158 EndpointGroupKey::Route(route) => write!(f, "{}", route),
159 }
160 }
161}
162
163#[derive(PartialEq, Eq, ValueDebugFormat, Clone, Debug, NonLocalValue, Encode, Decode)]
164pub struct EndpointGroupEntry {
165 pub endpoint: ResolvedVc<Box<dyn Endpoint>>,
166 pub sub_name: Option<RcStr>,
167}
168
169#[derive(PartialEq, Eq, ValueDebugFormat, Clone, Debug, NonLocalValue, Encode, Decode)]
170pub struct EndpointGroup {
171 pub primary: Vec<EndpointGroupEntry>,
172 pub additional: Vec<EndpointGroupEntry>,
173}
174
175impl EndpointGroup {
176 pub fn from(endpoint: ResolvedVc<Box<dyn Endpoint>>) -> Self {
177 Self {
178 primary: vec![EndpointGroupEntry {
179 endpoint,
180 sub_name: None,
181 }],
182 additional: vec![],
183 }
184 }
185
186 pub fn output_assets(&self) -> Vc<OutputAssets> {
187 output_of_endpoints(
188 self.primary
189 .iter()
190 .map(|endpoint| *endpoint.endpoint)
191 .collect(),
192 )
193 }
194
195 pub fn module_graphs(&self) -> Vc<ModuleGraphs> {
196 module_graphs_of_endpoints(
197 self.primary
198 .iter()
199 .map(|endpoint| *endpoint.endpoint)
200 .collect(),
201 )
202 }
203
204 pub fn traced_files(&self) -> Vc<FileSystemPathVec> {
205 traced_files_of_endpoints(
206 self.primary
207 .iter()
208 .map(|endpoint| *endpoint.endpoint)
209 .collect(),
210 )
211 }
212}
213
214#[turbo_tasks::function]
215async fn output_of_endpoints(endpoints: Vec<Vc<Box<dyn Endpoint>>>) -> Result<Vc<OutputAssets>> {
216 let assets = endpoints
217 .iter()
218 .map(async |endpoint| Ok(*endpoint.output().await?.output_assets))
219 .try_join()
220 .await?;
221 Ok(OutputAssets::concat(assets))
222}
223
224#[turbo_tasks::function]
225async fn module_graphs_of_endpoints(
226 endpoints: Vec<Vc<Box<dyn Endpoint>>>,
227) -> Result<Vc<ModuleGraphs>> {
228 let module_graphs = endpoints
230 .iter()
231 .map(async |endpoint| anyhow::Ok(endpoint.module_graphs().await?.into_iter()))
232 .join()
233 .await
234 .into_iter()
235 .flatten_ok()
236 .collect::<Result<FxIndexSet<_>>>()?
237 .into_iter()
238 .collect::<Vec<_>>();
239 Ok(Vc::cell(module_graphs))
240}
241
242#[turbo_tasks::function]
243async fn traced_files_of_endpoints(
244 endpoints: Vec<Vc<Box<dyn Endpoint>>>,
245) -> Result<Vc<FileSystemPathVec>> {
246 let mut modules: FxIndexSet<_> = FxIndexSet::default();
247 for endpoint in endpoints {
248 modules.extend(endpoint.traced_files().await?.iter().cloned());
249 }
250 Ok(Vc::cell(modules.into_iter().collect()))
251}
252
253#[turbo_tasks::value(transparent)]
254pub struct EndpointGroups(Vec<(EndpointGroupKey, EndpointGroup)>);
255
256#[turbo_tasks::value(transparent)]
257pub struct Endpoints(Vec<ResolvedVc<Box<dyn Endpoint>>>);
258
259#[turbo_tasks::function]
260pub async fn endpoint_write_to_disk(
261 endpoint: ResolvedVc<Box<dyn Endpoint>>,
262) -> Result<Vc<EndpointOutputPaths>> {
263 let output_op = output_assets_operation(endpoint);
264 let EndpointOutput {
265 project,
266 output_paths,
267 ..
268 } = *output_op.connect().await?;
269
270 project
271 .emit_all_output_assets(endpoint_output_assets_operation(output_op))
272 .as_side_effect()
273 .await?;
274
275 Ok(*output_paths)
276}
277
278#[turbo_tasks::function(operation)]
279fn output_assets_operation(endpoint: ResolvedVc<Box<dyn Endpoint>>) -> Vc<EndpointOutput> {
280 endpoint.output()
281}
282
283#[turbo_tasks::function(operation)]
284async fn endpoint_output_assets_operation(
285 output: OperationVc<EndpointOutput>,
286) -> Result<Vc<OutputAssets>> {
287 Ok(*output.connect().await?.output_assets)
288}
289
290#[turbo_tasks::function(operation, root)]
291pub async fn endpoint_write_to_disk_operation(
292 endpoint: OperationVc<OptionEndpoint>,
293) -> Result<Vc<EndpointOutputPaths>> {
294 Ok(if let Some(endpoint) = *endpoint.connect().await? {
295 endpoint_write_to_disk(*endpoint)
296 } else {
297 EndpointOutputPaths::NotFound.cell()
298 })
299}
300
301#[turbo_tasks::function(operation, root)]
302pub async fn endpoint_server_changed_operation(
303 endpoint: OperationVc<OptionEndpoint>,
304) -> Result<Vc<Completion>> {
305 Ok(if let Some(endpoint) = *endpoint.connect().await? {
306 endpoint.server_changed()
307 } else {
308 Completion::new()
309 })
310}
311
312#[turbo_tasks::function(operation, root)]
313pub async fn endpoint_client_changed_operation(
314 endpoint: OperationVc<OptionEndpoint>,
315) -> Result<Vc<Completion>> {
316 Ok(if let Some(endpoint) = *endpoint.connect().await? {
317 endpoint.client_changed()
318 } else {
319 Completion::new()
320 })
321}
322
323#[turbo_tasks::value(shared)]
324#[derive(Debug, Clone)]
325pub struct EndpointOutput {
326 pub output_assets: ResolvedVc<OutputAssets>,
327 pub output_paths: ResolvedVc<EndpointOutputPaths>,
328 pub project: ResolvedVc<Project>,
329}
330
331#[turbo_tasks::value(shared)]
332#[derive(Debug, Clone)]
333pub enum EndpointOutputPaths {
334 NodeJs {
335 server_entry_path: RcStr,
337 server_hmr_entry_paths: Vec<RcStr>,
338 server_paths: Vec<AssetPath>,
339 client_paths: Vec<RcStr>,
340 },
341 Edge {
342 server_paths: Vec<AssetPath>,
343 client_paths: Vec<RcStr>,
344 },
345 NotFound,
346}
347
348#[turbo_tasks::value(transparent)]
351pub struct Routes(#[bincode(with = "turbo_bincode::indexmap")] FxIndexMap<RcStr, Route>);