turbopack_core/
version.rs

1use std::sync::Arc;
2
3use anyhow::{Result, anyhow};
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_fs::{FileContent, LinkType};
10use turbo_tasks_hash::{encode_hex, hash_xxh3_hash64};
11
12use crate::asset::AssetContent;
13
14#[turbo_tasks::value(transparent)]
15pub struct OptionVersionedContent(Option<ResolvedVc<Box<dyn VersionedContent>>>);
16
17/// The content of an [Asset] alongside its version.
18#[turbo_tasks::value_trait]
19pub trait VersionedContent {
20    /// The content of the [Asset].
21    #[turbo_tasks::function]
22    fn content(self: Vc<Self>) -> Vc<AssetContent>;
23
24    /// Get a [`Version`] implementor that contains enough information to
25    /// identify and diff a future [`VersionedContent`] against it.
26    #[turbo_tasks::function]
27    fn version(self: Vc<Self>) -> Vc<Box<dyn Version>>;
28
29    /// Describes how to update the content from an earlier version to the
30    /// latest available one.
31    #[turbo_tasks::function]
32    async fn update(self: Vc<Self>, from: Vc<Box<dyn Version>>) -> Result<Vc<Update>> {
33        // By default, since we can't make any assumptions about the versioning
34        // scheme of the content, we ask for a full invalidation, except in the
35        // case where versions are the same.
36        let to = self.version();
37        let from_ref = from.into_trait_ref().await?;
38        let to_ref = to.into_trait_ref().await?;
39
40        // Fast path: versions are the same.
41        if TraitRef::ptr_eq(&from_ref, &to_ref) {
42            return Ok(Update::None.into());
43        }
44
45        // The fast path might not always work since `self` might have been converted
46        // from a `ReadRef` or a `ReadRef`, in which case `self.version()` would
47        // return a new `Vc<Box<dyn Version>>`. In this case, we need to compare
48        // version ids.
49        let from_id = from.id();
50        let to_id = to.id();
51        let from_id = from_id.await?;
52        let to_id = to_id.await?;
53        Ok(if *from_id == *to_id {
54            Update::None.into()
55        } else {
56            Update::Total(TotalUpdate { to: to_ref }).into()
57        })
58    }
59}
60
61/// A versioned file content.
62#[turbo_tasks::value]
63pub struct VersionedAssetContent {
64    // We can't store a `Vc<FileContent>` directly because we don't want
65    // `Vc<VersionedAssetContent>` to invalidate when the content changes.
66    // Otherwise, reading `content` and `version` at two different instants in
67    // time might return inconsistent values.
68    asset_content: ReadRef<AssetContent>,
69}
70
71#[turbo_tasks::value]
72#[derive(Clone)]
73enum AssetContentSnapshot {
74    File(ReadRef<FileContent>),
75    Redirect { target: String, link_type: LinkType },
76}
77
78#[turbo_tasks::value_impl]
79impl VersionedContent for VersionedAssetContent {
80    #[turbo_tasks::function]
81    fn content(&self) -> Vc<AssetContent> {
82        (*self.asset_content).clone().cell()
83    }
84
85    #[turbo_tasks::function]
86    async fn version(&self) -> Result<Vc<Box<dyn Version>>> {
87        Ok(Vc::upcast(
88            FileHashVersion::compute(&self.asset_content).await?,
89        ))
90    }
91}
92
93#[turbo_tasks::value_impl]
94impl VersionedAssetContent {
95    #[turbo_tasks::function]
96    /// Creates a new [Vc<VersionedAssetContent>] from a [Vc<FileContent>].
97    pub async fn new(asset_content: Vc<AssetContent>) -> Result<Vc<Self>> {
98        let asset_content = asset_content.await?;
99        Ok(Self::cell(VersionedAssetContent { asset_content }))
100    }
101}
102
103impl From<AssetContent> for Vc<VersionedAssetContent> {
104    fn from(asset_content: AssetContent) -> Self {
105        VersionedAssetContent::new(asset_content.cell())
106    }
107}
108
109impl From<AssetContent> for Vc<Box<dyn VersionedContent>> {
110    fn from(asset_content: AssetContent) -> Self {
111        Vc::upcast(VersionedAssetContent::new(asset_content.cell()))
112    }
113}
114
115pub trait VersionedContentExt: Send {
116    fn versioned(self: Vc<Self>) -> Vc<Box<dyn VersionedContent>>;
117}
118
119impl VersionedContentExt for AssetContent {
120    fn versioned(self: Vc<Self>) -> Vc<Box<dyn VersionedContent>> {
121        Vc::upcast(VersionedAssetContent::new(self))
122    }
123}
124
125/// Describes the current version of an object, and how to update them from an earlier version.
126///
127/// **Important:** Implementations must not contain instances of [`Vc`]! This should describe a
128/// specific version, and the value of a [`Vc`] can change due to invalidations or cache eviction.
129#[turbo_tasks::value_trait]
130pub trait Version {
131    /// Get a unique identifier of the version as a string. There is no way
132    /// to convert an id back to its original `Version`, so the original object
133    /// needs to be stored somewhere.
134    #[turbo_tasks::function]
135    fn id(self: Vc<Self>) -> Vc<RcStr>;
136}
137
138/// This trait allows multiple `VersionedContent` to declare which
139/// [`VersionedContentMerger`] implementation should be used for merging.
140///
141/// [`MergeableVersionedContent`] which return the same merger will be merged
142/// together.
143#[turbo_tasks::value_trait]
144pub trait MergeableVersionedContent: VersionedContent {
145    #[turbo_tasks::function]
146    fn get_merger(self: Vc<Self>) -> Vc<Box<dyn VersionedContentMerger>>;
147}
148
149/// A [`VersionedContentMerger`] merges multiple [`VersionedContent`] into a
150/// single one.
151#[turbo_tasks::value_trait]
152pub trait VersionedContentMerger {
153    #[turbo_tasks::function]
154    fn merge(self: Vc<Self>, contents: Vc<VersionedContents>) -> Vc<Box<dyn VersionedContent>>;
155}
156
157#[turbo_tasks::value(transparent)]
158pub struct VersionedContents(Vec<ResolvedVc<Box<dyn VersionedContent>>>);
159
160#[turbo_tasks::value(operation)]
161pub struct NotFoundVersion;
162
163#[turbo_tasks::value_impl]
164impl NotFoundVersion {
165    #[turbo_tasks::function]
166    pub fn new() -> Vc<Self> {
167        NotFoundVersion.cell()
168    }
169}
170
171#[turbo_tasks::value_impl]
172impl Version for NotFoundVersion {
173    #[turbo_tasks::function]
174    fn id(&self) -> Vc<RcStr> {
175        Vc::cell(Default::default())
176    }
177}
178
179/// Describes an update to a versioned object.
180#[turbo_tasks::value(serialization = "none", shared)]
181#[derive(Debug)]
182pub enum Update {
183    /// The asset can't be meaningfully updated while the app is running, so the
184    /// whole thing needs to be replaced.
185    Total(TotalUpdate),
186
187    /// The asset can (potentially) be updated to a new version by applying a
188    /// specific set of instructions.
189    Partial(PartialUpdate),
190
191    // The asset is now missing, so it can't be updated. A full reload is required.
192    Missing,
193
194    /// No update required.
195    None,
196}
197
198/// A total update to a versioned object.
199#[derive(PartialEq, Eq, Debug, Clone, TraceRawVcs, ValueDebugFormat, NonLocalValue)]
200pub struct TotalUpdate {
201    /// The version this update will bring the object to.
202    //
203    // TODO: This trace_ignore is wrong, and could cause problems if/when we add a GC. While
204    // `Version` assumes the implementation does not contain `Vc`, `EcmascriptDevChunkListVersion`
205    // is broken and violates this assumption.
206    #[turbo_tasks(trace_ignore)]
207    pub to: TraitRef<Box<dyn Version>>,
208}
209
210/// A partial update to a versioned object.
211#[derive(PartialEq, Eq, Debug, Clone, TraceRawVcs, ValueDebugFormat, NonLocalValue)]
212pub struct PartialUpdate {
213    /// The version this update will bring the object to.
214    // TODO: This trace_ignore is *very* wrong, and could cause problems if/when we add a GC
215    #[turbo_tasks(trace_ignore)]
216    pub to: TraitRef<Box<dyn Version>>,
217    /// The instructions to be passed to a remote system in order to update the
218    /// versioned object.
219    #[turbo_tasks(trace_ignore)]
220    pub instruction: Arc<serde_json::Value>,
221}
222
223/// [`Version`] implementation that hashes a file at a given path and returns
224/// the hex encoded hash as a version identifier.
225#[turbo_tasks::value(operation)]
226#[derive(Clone)]
227pub struct FileHashVersion {
228    hash: RcStr,
229}
230
231impl FileHashVersion {
232    /// Computes a new [`Vc<FileHashVersion>`] from a path.
233    pub async fn compute(asset_content: &AssetContent) -> Result<Vc<Self>> {
234        match asset_content {
235            AssetContent::File(file_vc) => match &*file_vc.await? {
236                FileContent::Content(file) => {
237                    let hash = hash_xxh3_hash64(file.content());
238                    let hex_hash = encode_hex(hash);
239                    Ok(Self::cell(FileHashVersion {
240                        hash: hex_hash.into(),
241                    }))
242                }
243                FileContent::NotFound => Err(anyhow!("file not found")),
244            },
245            AssetContent::Redirect { .. } => Err(anyhow!("not a file")),
246        }
247    }
248}
249
250#[turbo_tasks::value_impl]
251impl Version for FileHashVersion {
252    #[turbo_tasks::function]
253    fn id(&self) -> Vc<RcStr> {
254        Vc::cell(self.hash.clone())
255    }
256}
257
258/// This is a dummy wrapper type to (incorrectly) implement [`OperationValue`] (required by
259/// [`State`]), because the [`Version`] trait is not (yet?) a subtype of [`OperationValue`].
260#[derive(Debug, Eq, PartialEq, TraceRawVcs, NonLocalValue, OperationValue)]
261struct VersionRef(
262    // TODO: This trace_ignore is *very* wrong, and could cause problems if/when we add a GC.
263    // It also allows to `Version`s that don't implement `OperationValue`, which could lead to
264    // incorrect results when attempting to strongly resolve Vcs.
265    #[turbo_tasks(trace_ignore)] TraitRef<Box<dyn Version>>,
266);
267
268#[turbo_tasks::value(serialization = "none")]
269pub struct VersionState {
270    version: State<VersionRef>,
271}
272
273#[turbo_tasks::value_impl]
274impl VersionState {
275    #[turbo_tasks::function]
276    pub fn get(&self) -> Vc<Box<dyn Version>> {
277        TraitRef::cell(self.version.get().0.clone())
278    }
279}
280
281impl VersionState {
282    pub async fn new(version: TraitRef<Box<dyn Version>>) -> Result<Vc<Self>> {
283        Ok(Self::cell(VersionState {
284            version: State::new(VersionRef(version)),
285        }))
286    }
287
288    pub async fn set(self: Vc<Self>, new_version: TraitRef<Box<dyn Version>>) -> Result<()> {
289        let this = self.await?;
290        this.version.set(VersionRef(new_version));
291        Ok(())
292    }
293}