Skip to main content

turbopack_core/
version.rs

1use 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/// The content of an [Asset] alongside its version.
17#[turbo_tasks::value_trait]
18pub trait VersionedContent {
19    /// The content of the [Asset].
20    #[turbo_tasks::function]
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    #[turbo_tasks::function]
26    fn version(self: Vc<Self>) -> Vc<Box<dyn Version>>;
27
28    /// Describes how to update the content from an earlier version to the
29    /// latest available one.
30    #[turbo_tasks::function]
31    async fn update(self: Vc<Self>, from: Vc<Box<dyn Version>>) -> Result<Vc<Update>> {
32        // By default, since we can't make any assumptions about the versioning
33        // scheme of the content, we ask for a full invalidation, except in the
34        // case where versions are the same.
35        let to = self.version();
36        let from_ref = from.into_trait_ref().await?;
37        let to_ref = to.into_trait_ref().await?;
38
39        // Fast path: versions are the same.
40        if TraitRef::ptr_eq(&from_ref, &to_ref) {
41            return Ok(Update::None.cell());
42        }
43
44        // The fast path might not always work since `self` might have been converted
45        // from a `ReadRef` or a `ReadRef`, in which case `self.version()` would
46        // return a new `Vc<Box<dyn Version>>`. In this case, we need to compare
47        // version ids.
48        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/// A versioned file content.
61#[turbo_tasks::value]
62pub struct VersionedAssetContent {
63    // We can't store a `Vc<FileContent>` directly because we don't want
64    // `Vc<VersionedAssetContent>` to invalidate when the content changes.
65    // Otherwise, reading `content` and `version` at two different instants in
66    // time might return inconsistent values.
67    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    /// Creates a new [Vc<VersionedAssetContent>] from a [Vc<FileContent>].
89    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/// Describes the current version of an object, and how to update them from an earlier version.
118///
119/// **Important:** Implementations must not contain instances of [`Vc`]! This should describe a
120/// specific version, and the value of a [`Vc`] can change due to invalidations or cache eviction.
121#[turbo_tasks::value_trait]
122pub trait Version {
123    /// Get a unique identifier of the version as a string. There is no way
124    /// to convert an id back to its original `Version`, so the original object
125    /// needs to be stored somewhere.
126    #[turbo_tasks::function]
127    fn id(self: Vc<Self>) -> Vc<RcStr>;
128}
129
130/// This trait allows multiple `VersionedContent` to declare which
131/// [`VersionedContentMerger`] implementation should be used for merging.
132///
133/// [`MergeableVersionedContent`] which return the same merger will be merged
134/// together.
135#[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/// A [`VersionedContentMerger`] merges multiple [`VersionedContent`] into a
142/// single one.
143#[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/// Describes an update to a versioned object.
172#[turbo_tasks::value(serialization = "none", shared)]
173#[derive(Debug)]
174pub enum Update {
175    /// The asset can't be meaningfully updated while the app is running, so the
176    /// whole thing needs to be replaced.
177    Total(TotalUpdate),
178
179    /// The asset can (potentially) be updated to a new version by applying a
180    /// specific set of instructions.
181    Partial(PartialUpdate),
182
183    // The asset is now missing, so it can't be updated. A full reload is required.
184    Missing,
185
186    /// No update required.
187    None,
188}
189
190/// A total update to a versioned object.
191#[derive(PartialEq, Eq, Debug, Clone, TraceRawVcs, ValueDebugFormat, NonLocalValue)]
192pub struct TotalUpdate {
193    /// The version this update will bring the object to.
194    //
195    // TODO: This trace_ignore is wrong, and could cause problems if/when we add a GC. While
196    // `Version` assumes the implementation does not contain `Vc`, `EcmascriptDevChunkListVersion`
197    // is broken and violates this assumption.
198    #[turbo_tasks(trace_ignore)]
199    pub to: TraitRef<Box<dyn Version>>,
200}
201
202/// A partial update to a versioned object.
203#[derive(PartialEq, Eq, Debug, Clone, TraceRawVcs, ValueDebugFormat, NonLocalValue)]
204pub struct PartialUpdate {
205    /// The version this update will bring the object to.
206    // TODO: This trace_ignore is *very* wrong, and could cause problems if/when we add a GC
207    #[turbo_tasks(trace_ignore)]
208    pub to: TraitRef<Box<dyn Version>>,
209    /// The instructions to be passed to a remote system in order to update the
210    /// versioned object.
211    #[turbo_tasks(trace_ignore)]
212    pub instruction: Arc<serde_json::Value>,
213}
214
215/// [`Version`] implementation that hashes a file at a given path and returns
216/// the hex encoded hash as a version identifier.
217#[turbo_tasks::value(operation)]
218#[derive(Clone)]
219pub struct FileHashVersion {
220    hash: RcStr,
221}
222
223impl FileHashVersion {
224    /// Computes a new [`Vc<FileHashVersion>`] from a path.
225    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/// This is a dummy wrapper type to (incorrectly) implement [`OperationValue`] (required by
249/// [`State`]), because the [`Version`] trait is not (yet?) a subtype of [`OperationValue`].
250#[derive(Debug, Eq, PartialEq, TraceRawVcs, NonLocalValue, OperationValue)]
251struct VersionRef(
252    // TODO: This trace_ignore is *very* wrong, and could cause problems if/when we add a GC.
253    // It also allows to `Version`s that don't implement `OperationValue`, which could lead to
254    // incorrect results when attempting to strongly resolve Vcs.
255    #[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}