From 4c12e86849f94d2b6b3905edde96793fa6970f0c Mon Sep 17 00:00:00 2001 From: Xander Date: Thu, 9 Jul 2026 17:37:12 +0100 Subject: [PATCH 1/2] implement version cache bypass when creating a new instance --- apps/app-frontend/src/helpers/metadata.js | 8 ++--- apps/app-frontend/src/helpers/tags.js | 4 +-- apps/app-frontend/src/providers/setup.ts | 4 +-- .../src/providers/setup/creation-modal.ts | 14 ++++++--- apps/app-frontend/src/providers/setup/tags.ts | 22 ++++++++++---- apps/app/src/api/metadata.rs | 14 ++++++--- apps/app/src/api/tags.rs | 7 +++-- packages/app-lib/src/api/metadata.rs | 15 ++++++---- packages/app-lib/src/api/tags.rs | 21 +++++++++----- packages/app-lib/src/launcher/mod.rs | 5 ++-- .../creation-flow-context.ts | 29 +++++++++++-------- 11 files changed, 92 insertions(+), 51 deletions(-) diff --git a/apps/app-frontend/src/helpers/metadata.js b/apps/app-frontend/src/helpers/metadata.js index 51898dd53e..2ef75e4f4f 100644 --- a/apps/app-frontend/src/helpers/metadata.js +++ b/apps/app-frontend/src/helpers/metadata.js @@ -2,12 +2,12 @@ import { invoke } from '@tauri-apps/api/core' /// Gets the game versions from daedalus // Returns a VersionManifest -export async function get_game_versions() { - return await invoke('plugin:metadata|metadata_get_game_versions') +export async function get_game_versions(cacheBehaviour) { + return await invoke('plugin:metadata|metadata_get_game_versions', { cacheBehaviour }) } // Gets the given loader versions from daedalus // Returns Manifest -export async function get_loader_versions(loader) { - return await invoke('plugin:metadata|metadata_get_loader_versions', { loader }) +export async function get_loader_versions(loader, cacheBehaviour) { + return await invoke('plugin:metadata|metadata_get_loader_versions', { loader, cacheBehaviour }) } diff --git a/apps/app-frontend/src/helpers/tags.js b/apps/app-frontend/src/helpers/tags.js index fdfdb04d07..a183ccbc59 100644 --- a/apps/app-frontend/src/helpers/tags.js +++ b/apps/app-frontend/src/helpers/tags.js @@ -16,8 +16,8 @@ export async function get_loaders() { } // Gets cached game_versions tags -export async function get_game_versions() { - return await invoke('plugin:tags|tags_get_game_versions') +export async function get_game_versions(cacheBehaviour) { + return await invoke('plugin:tags|tags_get_game_versions', { cacheBehaviour }) } // Gets cached donation_platforms tags diff --git a/apps/app-frontend/src/providers/setup.ts b/apps/app-frontend/src/providers/setup.ts index fc49dbcfe7..7e2ae79c7f 100644 --- a/apps/app-frontend/src/providers/setup.ts +++ b/apps/app-frontend/src/providers/setup.ts @@ -10,12 +10,12 @@ export function setupProviders( notificationManager: AbstractWebNotificationManager, _popupNotificationManager: AbstractPopupNotificationManager, ) { - setupTagsProvider(notificationManager) + const { loadGameVersions } = setupTagsProvider(notificationManager) setupFileDropProvider() setupFilePickerProvider() setupInstanceImportProvider(notificationManager) return { - ...setupCreationModal(notificationManager), + ...setupCreationModal(notificationManager, loadGameVersions), } } diff --git a/apps/app-frontend/src/providers/setup/creation-modal.ts b/apps/app-frontend/src/providers/setup/creation-modal.ts index ee900974b5..56a6b6a382 100644 --- a/apps/app-frontend/src/providers/setup/creation-modal.ts +++ b/apps/app-frontend/src/providers/setup/creation-modal.ts @@ -20,10 +20,13 @@ import { } from '@/helpers/install' import { list } from '@/helpers/instance' import { get_loader_versions as getLoaderManifest } from '@/helpers/metadata.js' -import type { InstanceLoader } from '@/helpers/types' +import type { CacheBehaviour, InstanceLoader } from '@/helpers/types' import { useTheming } from '@/store/state' -export function setupCreationModal(notificationManager: AbstractWebNotificationManager) { +export function setupCreationModal( + notificationManager: AbstractWebNotificationManager, + loadGameVersions?: (cacheBehaviour?: CacheBehaviour) => Promise, +) { const { handleError } = notificationManager const router = useRouter() const themeStore = useTheming() @@ -45,8 +48,11 @@ export function setupCreationModal(notificationManager: AbstractWebNotificationM return instances?.map((i) => i.name) ?? [] } - provide('showCreationModal', () => { - installationModal.value?.show() + provide('showCreationModal', async () => { + await installationModal.value?.show() + // bypass cache to get up to date versions + void loadGameVersions?.('bypass').catch(handleError) + void installationModal.value?.ctx.prefetchLoaderMetadata(true) }) async function proceedWithModpackCreation( diff --git a/apps/app-frontend/src/providers/setup/tags.ts b/apps/app-frontend/src/providers/setup/tags.ts index f7cce3c66c..940b885b37 100644 --- a/apps/app-frontend/src/providers/setup/tags.ts +++ b/apps/app-frontend/src/providers/setup/tags.ts @@ -3,22 +3,32 @@ import { provideTags } from '@modrinth/ui' import { ref } from 'vue' import { get_game_versions, get_loaders } from '@/helpers/tags' +import type { CacheBehaviour } from '@/helpers/types' export function setupTagsProvider(notificationManager: AbstractWebNotificationManager) { const { handleError } = notificationManager const gameVersions = ref([]) const loaders = ref([]) - get_game_versions() - .then((v) => { - gameVersions.value = v - }) - .catch(handleError) - get_loaders() + + async function loadGameVersions(cacheBehaviour?: CacheBehaviour) { + const versions = await get_game_versions(cacheBehaviour) + if (versions) { + gameVersions.value = versions + } + } + + // load game versions from cache + void loadGameVersions().catch(handleError) + void get_loaders() .then((v) => { loaders.value = v }) .catch(handleError) provideTags({ gameVersions, loaders }) + + return { + loadGameVersions, + } } diff --git a/apps/app/src/api/metadata.rs b/apps/app/src/api/metadata.rs index 9364924e50..658e0bfec7 100644 --- a/apps/app/src/api/metadata.rs +++ b/apps/app/src/api/metadata.rs @@ -1,6 +1,7 @@ use crate::api::Result; use daedalus::minecraft::VersionManifest; use daedalus::modded::Manifest; +use theseus::prelude::CacheBehaviour; pub fn init() -> tauri::plugin::TauriPlugin { tauri::plugin::Builder::new("metadata") @@ -13,12 +14,17 @@ pub fn init() -> tauri::plugin::TauriPlugin { /// Gets the game versions from daedalus #[tauri::command] -pub async fn metadata_get_game_versions() -> Result { - Ok(theseus::metadata::get_minecraft_versions().await?) +pub async fn metadata_get_game_versions( + cache_behaviour: Option, +) -> Result { + Ok(theseus::metadata::get_minecraft_versions(cache_behaviour).await?) } /// Gets the fabric versions from daedalus #[tauri::command] -pub async fn metadata_get_loader_versions(loader: &str) -> Result { - Ok(theseus::metadata::get_loader_versions(loader).await?) +pub async fn metadata_get_loader_versions( + loader: &str, + cache_behaviour: Option, +) -> Result { + Ok(theseus::metadata::get_loader_versions(loader, cache_behaviour).await?) } diff --git a/apps/app/src/api/tags.rs b/apps/app/src/api/tags.rs index b69d221d71..77e1ed3b10 100644 --- a/apps/app/src/api/tags.rs +++ b/apps/app/src/api/tags.rs @@ -1,4 +1,5 @@ use crate::api::Result; +use theseus::prelude::CacheBehaviour; use theseus::tags::{Category, DonationPlatform, GameVersion, Loader}; pub fn init() -> tauri::plugin::TauriPlugin { @@ -33,8 +34,10 @@ pub async fn tags_get_loaders() -> Result> { /// Gets cached game version tags from the database #[tauri::command] -pub async fn tags_get_game_versions() -> Result> { - Ok(theseus::tags::get_game_version_tags().await?) +pub async fn tags_get_game_versions( + cache_behaviour: Option, +) -> Result> { + Ok(theseus::tags::get_game_version_tags(cache_behaviour).await?) } /// Gets cached donation platform tags from the database diff --git a/packages/app-lib/src/api/metadata.rs b/packages/app-lib/src/api/metadata.rs index 59b3b89c56..6c18c34f8b 100644 --- a/packages/app-lib/src/api/metadata.rs +++ b/packages/app-lib/src/api/metadata.rs @@ -1,13 +1,15 @@ use crate::State; -use crate::state::CachedEntry; +use crate::state::{CacheBehaviour, CachedEntry}; pub use daedalus::minecraft::VersionManifest; pub use daedalus::modded::Manifest; #[tracing::instrument] -pub async fn get_minecraft_versions() -> crate::Result { +pub async fn get_minecraft_versions( + cache_behaviour: Option, +) -> crate::Result { let state = State::get().await?; let minecraft_versions = CachedEntry::get_minecraft_manifest( - None, + cache_behaviour, &state.pool, &state.api_semaphore, ) @@ -20,11 +22,14 @@ pub async fn get_minecraft_versions() -> crate::Result { } // #[tracing::instrument] -pub async fn get_loader_versions(loader: &str) -> crate::Result { +pub async fn get_loader_versions( + loader: &str, + cache_behaviour: Option, +) -> crate::Result { let state = State::get().await?; let loaders = CachedEntry::get_loader_manifest( loader, - None, + cache_behaviour, &state.pool, &state.api_semaphore, ) diff --git a/packages/app-lib/src/api/tags.rs b/packages/app-lib/src/api/tags.rs index f43ad0a0b0..ee41799d3e 100644 --- a/packages/app-lib/src/api/tags.rs +++ b/packages/app-lib/src/api/tags.rs @@ -2,7 +2,7 @@ use crate::state::CachedEntry; pub use crate::{ State, - state::{Category, DonationPlatform, GameVersion, Loader}, + state::{CacheBehaviour, Category, DonationPlatform, GameVersion, Loader}, }; /// Get category tags @@ -49,14 +49,19 @@ pub async fn get_loader_tags() -> crate::Result> { /// Get game version tags #[tracing::instrument] -pub async fn get_game_version_tags() -> crate::Result> { +pub async fn get_game_version_tags( + cache_behaviour: Option, +) -> crate::Result> { let state = State::get().await?; - let game_versions = - CachedEntry::get_game_versions(None, &state.pool, &state.api_semaphore) - .await? - .ok_or_else(|| { - crate::ErrorKind::NoValueFor("game version tags".to_string()) - })?; + let game_versions = CachedEntry::get_game_versions( + cache_behaviour, + &state.pool, + &state.api_semaphore, + ) + .await? + .ok_or_else(|| { + crate::ErrorKind::NoValueFor("game version tags".to_string()) + })?; Ok(game_versions) } diff --git a/packages/app-lib/src/launcher/mod.rs b/packages/app-lib/src/launcher/mod.rs index a3d4a0a8b1..0e9b62a660 100644 --- a/packages/app-lib/src/launcher/mod.rs +++ b/packages/app-lib/src/launcher/mod.rs @@ -173,7 +173,8 @@ pub async fn get_loader_version_from_profile( }; let versions = - crate::api::metadata::get_loader_versions(loader.as_meta_str()).await?; + crate::api::metadata::get_loader_versions(loader.as_meta_str(), None) + .await?; let loaders = versions.game_versions.into_iter().find(|x| { x.id.replace(daedalus::modded::DUMMY_REPLACE_STRING, game_version) @@ -202,7 +203,7 @@ pub async fn resolve_minecraft_manifest( game_version: &str, state: &State, ) -> crate::Result<(d::minecraft::VersionManifest, usize)> { - let minecraft = crate::api::metadata::get_minecraft_versions().await?; + let minecraft = crate::api::metadata::get_minecraft_versions(None).await?; if let Some(idx) = minecraft .versions diff --git a/packages/ui/src/components/flows/creation-flow-modal/creation-flow-context.ts b/packages/ui/src/components/flows/creation-flow-modal/creation-flow-context.ts index 9582c4df8a..90ba053d01 100644 --- a/packages/ui/src/components/flows/creation-flow-modal/creation-flow-context.ts +++ b/packages/ui/src/components/flows/creation-flow-modal/creation-flow-context.ts @@ -24,7 +24,10 @@ export type Gamemode = 'survival' | 'creative' | 'hardcore' export type Difficulty = 'peaceful' | 'easy' | 'normal' | 'hard' export type LoaderVersionType = 'stable' | 'latest' | 'other' export type GeneratorSettingsMode = 'default' | 'flat' | 'custom' -export type LoaderManifestResolver = (loader: string) => Promise +export type LoaderManifestResolver = ( + loader: string, + cacheBehaviour?: 'bypass', +) => Promise export interface LoaderVersionEntry { id: string stable: boolean @@ -207,8 +210,8 @@ export interface CreationFlowContextValue { browseModpacks: () => void finish: () => void buildProperties: () => Archon.Content.v1.PropertiesFields - fetchLoaderMetadata: (loader?: string | null) => Promise - prefetchLoaderMetadata: () => Promise + fetchLoaderMetadata: (loader?: string | null, bypassCache?: boolean) => Promise + prefetchLoaderMetadata: (bypassCache?: boolean) => Promise // Platform-provided search searchModpacks: (query: string, limit?: number) => Promise @@ -352,23 +355,25 @@ export function createCreationFlowContext( return loader === 'neoforge' ? 'neo' : loader } - async function fetchLoaderManifest(loader: string) { + async function fetchLoaderManifest(loader: string, bypassCache = false) { const apiLoader = toApiLoaderName(loader) - if (loaderVersionsCache.value[apiLoader]) return + if (!bypassCache && loaderVersionsCache.value[apiLoader]) return try { const data = await queryClient.fetchQuery({ queryKey: loaderManifestQueryKey(apiLoader), queryFn: async () => - (await getLoaderManifest?.(apiLoader)) ?? + (await getLoaderManifest?.(apiLoader, bypassCache ? 'bypass' : undefined)) ?? (await client.launchermeta.manifest_v0.getManifest(apiLoader)), - staleTime: Infinity, + staleTime: bypassCache ? 0 : Infinity, }) loaderVersionsCache.value[apiLoader] = data.gameVersions debug('fetchLoaderManifest: loaded', apiLoader, 'gameVersions:', data.gameVersions.length) } catch (error) { debug('fetchLoaderManifest: failed', apiLoader, error) - loaderVersionsCache.value[apiLoader] = [] + if (!loaderVersionsCache.value[apiLoader]) { + loaderVersionsCache.value[apiLoader] = [] + } } } @@ -404,7 +409,7 @@ export function createCreationFlowContext( } } - async function fetchLoaderMetadata(loader?: string | null) { + async function fetchLoaderMetadata(loader?: string | null, bypassCache = false) { if (!loader || loader === 'vanilla') return if (loader === 'paper') { await fetchPaperSupportedVersions() @@ -414,14 +419,14 @@ export function createCreationFlowContext( await fetchPurpurSupportedVersions() return } - await fetchLoaderManifest(loader) + await fetchLoaderManifest(loader, bypassCache) } - async function prefetchLoaderMetadata() { + async function prefetchLoaderMetadata(bypassCache = false) { await Promise.allSettled( availableLoaders .filter((loader) => loader !== 'vanilla') - .map((loader) => fetchLoaderMetadata(loader)), + .map((loader) => fetchLoaderMetadata(loader, bypassCache)), ) } From 5708b57c02a1ffc6ca0b5f0683939c513e9fcf6b Mon Sep 17 00:00:00 2001 From: Xander Date: Thu, 9 Jul 2026 18:08:53 +0100 Subject: [PATCH 2/2] prevent tanstack query coalescing stale query --- apps/app-frontend/src/App.vue | 3 ++- apps/app-frontend/src/providers/setup/creation-modal.ts | 7 +++++-- .../flows/creation-flow-modal/creation-flow-context.ts | 9 ++++++--- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/apps/app-frontend/src/App.vue b/apps/app-frontend/src/App.vue index 1f39661978..31cb467469 100644 --- a/apps/app-frontend/src/App.vue +++ b/apps/app-frontend/src/App.vue @@ -243,6 +243,7 @@ const { searchModpacks, getProjectVersions, getLoaderManifest, + showCreationModal, setModpackAlreadyInstalledModal, handleModpackDuplicateCreateAnyway, handleModpackDuplicateGoToInstance, @@ -1462,7 +1463,7 @@ provideAppUpdateDownloadProgress(appUpdateDownload) diff --git a/apps/app-frontend/src/providers/setup/creation-modal.ts b/apps/app-frontend/src/providers/setup/creation-modal.ts index 56a6b6a382..721eef8704 100644 --- a/apps/app-frontend/src/providers/setup/creation-modal.ts +++ b/apps/app-frontend/src/providers/setup/creation-modal.ts @@ -48,12 +48,14 @@ export function setupCreationModal( return instances?.map((i) => i.name) ?? [] } - provide('showCreationModal', async () => { + async function showCreationModal() { await installationModal.value?.show() // bypass cache to get up to date versions void loadGameVersions?.('bypass').catch(handleError) void installationModal.value?.ctx.prefetchLoaderMetadata(true) - }) + } + + provide('showCreationModal', showCreationModal) async function proceedWithModpackCreation( projectId: string, @@ -210,6 +212,7 @@ export function setupCreationModal( searchModpacks, getProjectVersions, getLoaderManifest, + showCreationModal, setModpackAlreadyInstalledModal, handleModpackDuplicateCreateAnyway, handleModpackDuplicateGoToInstance, diff --git a/packages/ui/src/components/flows/creation-flow-modal/creation-flow-context.ts b/packages/ui/src/components/flows/creation-flow-modal/creation-flow-context.ts index 90ba053d01..00c910782d 100644 --- a/packages/ui/src/components/flows/creation-flow-modal/creation-flow-context.ts +++ b/packages/ui/src/components/flows/creation-flow-modal/creation-flow-context.ts @@ -33,8 +33,8 @@ export interface LoaderVersionEntry { stable: boolean } -const loaderManifestQueryKey = (loader: string) => - ['creation-flow', 'loader-manifest', loader] as const +const loaderManifestQueryKey = (loader: string, bypassCache = false) => + ['creation-flow', 'loader-manifest', loader, bypassCache ? 'bypass' : 'cached'] as const const paperSupportedVersionsQueryKey = ['creation-flow', 'paper', 'supported-versions'] as const const purpurSupportedVersionsQueryKey = ['creation-flow', 'purpur', 'supported-versions'] as const @@ -361,12 +361,15 @@ export function createCreationFlowContext( try { const data = await queryClient.fetchQuery({ - queryKey: loaderManifestQueryKey(apiLoader), + queryKey: loaderManifestQueryKey(apiLoader, bypassCache), queryFn: async () => (await getLoaderManifest?.(apiLoader, bypassCache ? 'bypass' : undefined)) ?? (await client.launchermeta.manifest_v0.getManifest(apiLoader)), staleTime: bypassCache ? 0 : Infinity, }) + if (bypassCache) { + queryClient.setQueryData(loaderManifestQueryKey(apiLoader), data) + } loaderVersionsCache.value[apiLoader] = data.gameVersions debug('fetchLoaderManifest: loaded', apiLoader, 'gameVersions:', data.gameVersions.length) } catch (error) {