turbopack_core/
version.rs1use std::sync::Arc;
2
3use anyhow::{Context, Result, bail};
4use turbo_rcstr::RcStr;
5use turbo_tasks::{
6 IntoTraitRef, NonLocalValue, OperationValue, ReadRef, ResolvedVc, State, TraitRef, Vc,
7 debug::ValueDebugFormat, trace::TraceRawVcs,
8};
9use turbo_tasks_hash::HashAlgorithm;
10
11use crate::asset::AssetContent;
12
13#[turbo_tasks::value(transparent)]
14pub struct OptionVersionedContent(Option<ResolvedVc<Box<dyn VersionedContent>>>);
15
16#[turbo_tasks::value_trait]
18pub trait VersionedContent {
19 #[turbo_tasks::function]
21 fn content(self: Vc<Self>) -> Vc<AssetContent>;
22
23 #[turbo_tasks::function]
26 fn version(self: Vc<Self>) -> Vc<Box<dyn Version>>;
27
28 #[turbo_tasks::function]
31 async fn update(self: Vc<Self>, from: Vc<Box<dyn Version>>) -> Result<Vc<Update>> {
32 let to = self.version();
36 let from_ref = from.into_trait_ref().await?;
37 let to_ref = to.into_trait_ref().await?;
38
39 if TraitRef::ptr_eq(&from_ref, &to_ref) {
41 return Ok(Update::None.cell());
42 }
43
44 let from_id = from.id();
49 let to_id = to.id();
50 let from_id = from_id.await?;
51 let to_id = to_id.await?;
52 Ok(if *from_id == *to_id {
53 Update::None.cell()
54 } else {
55 Update::Total(TotalUpdate { to: to_ref }).cell()
56 })
57 }
58}
59
60#[turbo_tasks::value]
62pub struct VersionedAssetContent {
63 asset_content: ReadRef<AssetContent>,
68}
69
70#[turbo_tasks::value_impl]
71impl VersionedContent for VersionedAssetContent {
72 #[turbo_tasks::function]
73 fn content(&self) -> Vc<AssetContent> {
74 (*self.asset_content).clone().cell()
75 }
76
77 #[turbo_tasks::function]
78 async fn version(&self) -> Result<Vc<Box<dyn Version>>> {
79 Ok(Vc::upcast(
80 FileHashVersion::compute(&self.asset_content).await?,
81 ))
82 }
83}
84
85#[turbo_tasks::value_impl]
86impl VersionedAssetContent {
87 #[turbo_tasks::function]
88 pub async fn new(asset_content: Vc<AssetContent>) -> Result<Vc<Self>> {
90 let asset_content = asset_content.await?;
91 Ok(Self::cell(VersionedAssetContent { asset_content }))
92 }
93}
94
95impl From<AssetContent> for Vc<VersionedAssetContent> {
96 fn from(asset_content: AssetContent) -> Self {
97 VersionedAssetContent::new(asset_content.cell())
98 }
99}
100
101impl From<AssetContent> for Vc<Box<dyn VersionedContent>> {
102 fn from(asset_content: AssetContent) -> Self {
103 Vc::upcast(VersionedAssetContent::new(asset_content.cell()))
104 }
105}
106
107pub trait VersionedContentExt: Send {
108 fn versioned(self: Vc<Self>) -> Vc<Box<dyn VersionedContent>>;
109}
110
111impl VersionedContentExt for AssetContent {
112 fn versioned(self: Vc<Self>) -> Vc<Box<dyn VersionedContent>> {
113 Vc::upcast(VersionedAssetContent::new(self))
114 }
115}
116
117#[turbo_tasks::value_trait]
122pub trait Version {
123 #[turbo_tasks::function]
127 fn id(self: Vc<Self>) -> Vc<RcStr>;
128}
129
130#[turbo_tasks::value_trait]
136pub trait MergeableVersionedContent: VersionedContent {
137 #[turbo_tasks::function]
138 fn get_merger(self: Vc<Self>) -> Vc<Box<dyn VersionedContentMerger>>;
139}
140
141#[turbo_tasks::value_trait]
144pub trait VersionedContentMerger {
145 #[turbo_tasks::function]
146 fn merge(self: Vc<Self>, contents: Vc<VersionedContents>) -> Vc<Box<dyn VersionedContent>>;
147}
148
149#[turbo_tasks::value(transparent)]
150pub struct VersionedContents(Vec<ResolvedVc<Box<dyn VersionedContent>>>);
151
152#[turbo_tasks::value(operation)]
153pub struct NotFoundVersion;
154
155#[turbo_tasks::value_impl]
156impl NotFoundVersion {
157 #[turbo_tasks::function]
158 pub fn new() -> Vc<Self> {
159 NotFoundVersion.cell()
160 }
161}
162
163#[turbo_tasks::value_impl]
164impl Version for NotFoundVersion {
165 #[turbo_tasks::function]
166 fn id(&self) -> Vc<RcStr> {
167 Vc::cell(Default::default())
168 }
169}
170
171#[turbo_tasks::value(serialization = "none", shared)]
173#[derive(Debug)]
174pub enum Update {
175 Total(TotalUpdate),
178
179 Partial(PartialUpdate),
182
183 Missing,
185
186 None,
188}
189
190#[derive(PartialEq, Eq, Debug, Clone, TraceRawVcs, ValueDebugFormat, NonLocalValue)]
192pub struct TotalUpdate {
193 #[turbo_tasks(trace_ignore)]
199 pub to: TraitRef<Box<dyn Version>>,
200}
201
202#[derive(PartialEq, Eq, Debug, Clone, TraceRawVcs, ValueDebugFormat, NonLocalValue)]
204pub struct PartialUpdate {
205 #[turbo_tasks(trace_ignore)]
208 pub to: TraitRef<Box<dyn Version>>,
209 #[turbo_tasks(trace_ignore)]
212 pub instruction: Arc<serde_json::Value>,
213}
214
215#[turbo_tasks::value(operation)]
218#[derive(Clone)]
219pub struct FileHashVersion {
220 hash: RcStr,
221}
222
223impl FileHashVersion {
224 pub async fn compute(asset_content: &AssetContent) -> Result<Vc<Self>> {
226 match asset_content {
227 AssetContent::File(file_vc) => {
228 let hash = file_vc
229 .content_hash(HashAlgorithm::Xxh3Hash128Hex)
230 .owned()
231 .await?
232 .context("file not found")?;
233 Ok(Self::cell(FileHashVersion { hash }))
234 }
235 AssetContent::Redirect { .. } => bail!("not a file"),
236 }
237 }
238}
239
240#[turbo_tasks::value_impl]
241impl Version for FileHashVersion {
242 #[turbo_tasks::function]
243 fn id(&self) -> Vc<RcStr> {
244 Vc::cell(self.hash.clone())
245 }
246}
247
248#[derive(Debug, Eq, PartialEq, TraceRawVcs, NonLocalValue, OperationValue)]
251struct VersionRef(
252 #[turbo_tasks(trace_ignore)] TraitRef<Box<dyn Version>>,
256);
257
258#[turbo_tasks::value(serialization = "none")]
259pub struct VersionState {
260 version: State<VersionRef>,
261}
262
263#[turbo_tasks::value_impl]
264impl VersionState {
265 #[turbo_tasks::function]
266 pub fn get(&self) -> Vc<Box<dyn Version>> {
267 TraitRef::cell(self.version.get().0.clone())
268 }
269}
270
271impl VersionState {
272 pub async fn new(version: TraitRef<Box<dyn Version>>) -> Result<Vc<Self>> {
273 Ok(Self::cell(VersionState {
274 version: State::new(VersionRef(version)),
275 }))
276 }
277
278 pub async fn set(self: Vc<Self>, new_version: TraitRef<Box<dyn Version>>) -> Result<()> {
279 let this = self.await?;
280 this.version.set(VersionRef(new_version));
281 Ok(())
282 }
283}