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 #[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}
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 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#[turbo_tasks::value(transparent)]
280pub struct Routes(#[bincode(with = "turbo_bincode::indexmap")] FxIndexMap<RcStr, Route>);