Skip to main content

turbopack_core/
version.rs

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