1use anyhow::Result;
2use serde_json::Value as JsonValue;
3use turbo_rcstr::RcStr;
4use turbo_tasks::{FxIndexSet, ResolvedVc, Value, Vc};
5use turbo_tasks_env::ProcessEnv;
6use turbo_tasks_fs::FileSystemPath;
7use turbopack_core::introspect::{
8 Introspectable, IntrospectableChildren, module::IntrospectableModule,
9 output_asset::IntrospectableOutputAsset,
10};
11use turbopack_dev_server::source::{
12 ContentSource, ContentSourceContent, ContentSourceData, ContentSourceDataVary,
13 GetContentSourceContent,
14 route_tree::{BaseSegment, RouteTree, RouteType},
15};
16
17use super::{RenderData, render_proxy::render_proxy_operation};
18use crate::{get_intermediate_asset, node_entry::NodeEntry, route_matcher::RouteMatcher};
19
20#[turbo_tasks::function]
22pub fn create_node_api_source(
23 cwd: ResolvedVc<FileSystemPath>,
24 env: ResolvedVc<Box<dyn ProcessEnv>>,
25 base_segments: Vec<BaseSegment>,
26 route_type: RouteType,
27 server_root: ResolvedVc<FileSystemPath>,
28 route_match: ResolvedVc<Box<dyn RouteMatcher>>,
29 pathname: ResolvedVc<RcStr>,
30 entry: ResolvedVc<Box<dyn NodeEntry>>,
31 render_data: ResolvedVc<JsonValue>,
32 debug: bool,
33) -> Vc<Box<dyn ContentSource>> {
34 Vc::upcast(
35 NodeApiContentSource {
36 cwd,
37 env,
38 base_segments,
39 route_type,
40 server_root,
41 pathname,
42 route_match,
43 entry,
44 render_data,
45 debug,
46 }
47 .cell(),
48 )
49}
50
51#[turbo_tasks::value]
58pub struct NodeApiContentSource {
59 cwd: ResolvedVc<FileSystemPath>,
60 env: ResolvedVc<Box<dyn ProcessEnv>>,
61 base_segments: Vec<BaseSegment>,
62 route_type: RouteType,
63 server_root: ResolvedVc<FileSystemPath>,
64 pathname: ResolvedVc<RcStr>,
65 route_match: ResolvedVc<Box<dyn RouteMatcher>>,
66 entry: ResolvedVc<Box<dyn NodeEntry>>,
67 render_data: ResolvedVc<JsonValue>,
68 debug: bool,
69}
70
71#[turbo_tasks::value_impl]
72impl NodeApiContentSource {
73 #[turbo_tasks::function]
74 pub fn get_pathname(&self) -> Vc<RcStr> {
75 *self.pathname
76 }
77}
78
79#[turbo_tasks::value_impl]
80impl ContentSource for NodeApiContentSource {
81 #[turbo_tasks::function]
82 async fn get_routes(self: Vc<Self>) -> Result<Vc<RouteTree>> {
83 let this = self.await?;
84 Ok(RouteTree::new_route(
85 this.base_segments.clone(),
86 this.route_type.clone(),
87 Vc::upcast(self),
88 ))
89 }
90}
91
92#[turbo_tasks::value_impl]
93impl GetContentSourceContent for NodeApiContentSource {
94 #[turbo_tasks::function]
95 fn vary(&self) -> Vc<ContentSourceDataVary> {
96 ContentSourceDataVary {
97 method: true,
98 url: true,
99 original_url: true,
100 raw_headers: true,
101 raw_query: true,
102 body: true,
103 cache_buster: true,
104 ..Default::default()
105 }
106 .cell()
107 }
108
109 #[turbo_tasks::function]
110 async fn get(
111 &self,
112 path: RcStr,
113 data: Value<ContentSourceData>,
114 ) -> Result<Vc<ContentSourceContent>> {
115 let Some(params) = &*self.route_match.params(path.clone()).await? else {
116 anyhow::bail!("Non matching path provided")
117 };
118 let ContentSourceData {
119 method: Some(method),
120 url: Some(url),
121 original_url: Some(original_url),
122 raw_headers: Some(raw_headers),
123 raw_query: Some(raw_query),
124 body: Some(body),
125 ..
126 } = &*data
127 else {
128 anyhow::bail!("Missing request data")
129 };
130 let entry = (*self.entry).entry(data.clone()).await?;
131 Ok(ContentSourceContent::HttpProxy(render_proxy_operation(
132 self.cwd,
133 self.env,
134 self.server_root.join(path.clone()).to_resolved().await?,
135 ResolvedVc::upcast(entry.module),
136 entry.runtime_entries,
137 entry.chunking_context,
138 entry.intermediate_output_path,
139 entry.output_root,
140 entry.project_dir,
141 RenderData {
142 params: params.clone(),
143 method: method.clone(),
144 url: url.clone(),
145 original_url: original_url.clone(),
146 raw_query: raw_query.clone(),
147 raw_headers: raw_headers.clone(),
148 path: format!("/{path}").into(),
149 data: Some(self.render_data.await?),
150 }
151 .resolved_cell(),
152 *body,
153 self.debug,
154 ))
155 .cell())
156 }
157}
158
159#[turbo_tasks::function]
160fn introspectable_type() -> Vc<RcStr> {
161 Vc::cell("node api content source".into())
162}
163
164#[turbo_tasks::value_impl]
165impl Introspectable for NodeApiContentSource {
166 #[turbo_tasks::function]
167 fn ty(&self) -> Vc<RcStr> {
168 introspectable_type()
169 }
170
171 #[turbo_tasks::function]
172 fn title(&self) -> Vc<RcStr> {
173 *self.pathname
174 }
175
176 #[turbo_tasks::function]
177 async fn details(&self) -> Vc<RcStr> {
178 Vc::cell(
179 format!(
180 "base: {:?}\ntype: {:?}",
181 self.base_segments, self.route_type
182 )
183 .into(),
184 )
185 }
186
187 #[turbo_tasks::function]
188 async fn children(&self) -> Result<Vc<IntrospectableChildren>> {
189 let mut set = FxIndexSet::default();
190 for &entry in self.entry.entries().await?.iter() {
191 let entry = entry.await?;
192 set.insert((
193 ResolvedVc::cell("module".into()),
194 IntrospectableModule::new(Vc::upcast(*entry.module))
195 .to_resolved()
196 .await?,
197 ));
198 set.insert((
199 ResolvedVc::cell("intermediate asset".into()),
200 IntrospectableOutputAsset::new(get_intermediate_asset(
201 *entry.chunking_context,
202 Vc::upcast(*entry.module),
203 *entry.runtime_entries,
204 ))
205 .to_resolved()
206 .await?,
207 ));
208 }
209 Ok(Vc::cell(set))
210 }
211}