// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. use std::collections::HashMap; use std::collections::HashSet; use std::sync::Arc; use async_trait::async_trait; use deno_core::anyhow::anyhow; use deno_core::error::AnyError; use deno_core::futures::future::BoxFuture; use deno_core::futures::future::Shared; use deno_core::futures::FutureExt; use deno_core::parking_lot::Mutex; use deno_npm::registry::NpmPackageInfo; use deno_npm::registry::NpmRegistryApi; use deno_npm::registry::NpmRegistryPackageInfoLoadError; use deno_npm_cache::NpmCacheSetting; use crate::npm::CliNpmCache; use crate::npm::CliNpmRegistryInfoProvider; use crate::util::sync::AtomicFlag; // todo(#27198): Remove this and move functionality down into // RegistryInfoProvider, which already does most of this. #[derive(Debug)] pub struct CliNpmRegistryApi(Option>); impl CliNpmRegistryApi { pub fn new( cache: Arc, registry_info_provider: Arc, ) -> Self { Self(Some(Arc::new(CliNpmRegistryApiInner { cache, force_reload_flag: Default::default(), mem_cache: Default::default(), previously_reloaded_packages: Default::default(), registry_info_provider, }))) } /// Clears the internal memory cache. pub fn clear_memory_cache(&self) { self.inner().clear_memory_cache(); } fn inner(&self) -> &Arc { // this panicking indicates a bug in the code where this // wasn't initialized self.0.as_ref().unwrap() } } #[async_trait(?Send)] impl NpmRegistryApi for CliNpmRegistryApi { async fn package_info( &self, name: &str, ) -> Result, NpmRegistryPackageInfoLoadError> { match self.inner().maybe_package_info(name).await { Ok(Some(info)) => Ok(info), Ok(None) => Err(NpmRegistryPackageInfoLoadError::PackageNotExists { package_name: name.to_string(), }), Err(err) => { Err(NpmRegistryPackageInfoLoadError::LoadError(Arc::new(err))) } } } fn mark_force_reload(&self) -> bool { self.inner().mark_force_reload() } } type CacheItemPendingResult = Result>, Arc>; #[derive(Debug)] enum CacheItem { Pending(Shared>), Resolved(Option>), } #[derive(Debug)] struct CliNpmRegistryApiInner { cache: Arc, force_reload_flag: AtomicFlag, mem_cache: Mutex>, previously_reloaded_packages: Mutex>, registry_info_provider: Arc, } impl CliNpmRegistryApiInner { pub async fn maybe_package_info( self: &Arc, name: &str, ) -> Result>, AnyError> { let (created, future) = { let mut mem_cache = self.mem_cache.lock(); match mem_cache.get(name) { Some(CacheItem::Resolved(maybe_info)) => { return Ok(maybe_info.clone()); } Some(CacheItem::Pending(future)) => (false, future.clone()), None => { let future = { let api = self.clone(); let name = name.to_string(); async move { if (api.cache.cache_setting().should_use_for_npm_package(&name) && !api.force_reload_flag.is_raised()) // if this has been previously reloaded, then try loading from the // file system cache || !api.previously_reloaded_packages.lock().insert(name.to_string()) { // attempt to load from the file cache if let Some(info) = api.load_file_cached_package_info(&name).await { let result = Some(Arc::new(info)); return Ok(result); } } api.registry_info_provider .load_package_info(&name) .await .map_err(Arc::new) } .boxed() .shared() }; mem_cache .insert(name.to_string(), CacheItem::Pending(future.clone())); (true, future) } } }; if created { match future.await { Ok(maybe_info) => { // replace the cache item to say it's resolved now self .mem_cache .lock() .insert(name.to_string(), CacheItem::Resolved(maybe_info.clone())); Ok(maybe_info) } Err(err) => { // purge the item from the cache so it loads next time self.mem_cache.lock().remove(name); Err(anyhow!("{:#}", err)) } } } else { Ok(future.await.map_err(|err| anyhow!("{:#}", err))?) } } fn mark_force_reload(&self) -> bool { // never force reload the registry information if reloading // is disabled or if we're already reloading if matches!( self.cache.cache_setting(), NpmCacheSetting::Only | NpmCacheSetting::ReloadAll ) { return false; } if self.force_reload_flag.raise() { self.clear_memory_cache(); true } else { false } } async fn load_file_cached_package_info( &self, name: &str, ) -> Option { let result = deno_core::unsync::spawn_blocking({ let cache = self.cache.clone(); let name = name.to_string(); move || cache.load_package_info(&name) }) .await .unwrap(); match result { Ok(value) => value, Err(err) => { if cfg!(debug_assertions) { panic!("error loading cached npm package info for {name}: {err:#}"); } else { None } } } } fn clear_memory_cache(&self) { self.mem_cache.lock().clear(); } }