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