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