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