char/avril

(wip) why'd you have to go and make things so complicaaaaated?

git clone https://git.t4t.associates/char/avril

Charlotte Somexperiment: automatic types for npm packages51266f9

main
16.4 KiB482 linesraw
1use std::cell::RefCell;
2use std::collections::{BTreeMap, BTreeSet};
3use std::rc::Rc;
4use std::sync::Arc;
5
6use crate::authority::SourceAuthority;
7use crate::package_config::{PackageConfig, Source};
8use crate::registry::{PackageStore, checksum};
9use avril_core::Error;
10use url::Url;
11
12use crate::local_packages::{
13    PackageMapping, Resolution, declared_name, package_mapping, select_binding,
14};
15
16/// A remote mapping step: an already-decided resolution, or an export of
17/// a directory by key. The adapters acquire (`export`) or reuse prepared
18/// metadata (`resolve_export_meta`) afterwards.
19enum RemoteStep {
20    Done(Resolution),
21    Export(Url, String),
22}
23
24struct Package {
25    requested: Url,
26    directory: Url,
27    identity: (Url, String),
28    config: PackageConfig,
29    source: String,
30    url: Url,
31    bindings: RefCell<Option<BTreeMap<String, Source>>>,
32}
33
34impl Package {
35    fn region(&self, name: &str) -> Option<&Url> {
36        if name.starts_with(self.directory.as_str()) {
37            Some(&self.directory)
38        } else if name.starts_with(self.requested.as_str()) {
39            Some(&self.requested)
40        } else {
41            None
42        }
43    }
44}
45
46#[derive(Clone)]
47struct Owner {
48    package: Rc<Package>,
49    region: Url,
50}
51
52pub struct RemotePackages {
53    authority: SourceAuthority,
54    store: Arc<PackageStore>,
55    packages: RefCell<BTreeMap<Url, Rc<Package>>>,
56    owners: RefCell<BTreeMap<String, Option<Owner>>>,
57    authorities: RefCell<BTreeMap<(Url, String), BTreeSet<Url>>>,
58}
59
60impl RemotePackages {
61    pub fn new(authority: SourceAuthority, store: Arc<PackageStore>) -> Self {
62        Self {
63            authority,
64            store,
65            packages: RefCell::default(),
66            owners: RefCell::default(),
67            authorities: RefCell::default(),
68        }
69    }
70
71    fn check(&self, package: &Package) -> Result<(), Error> {
72        if let Some(urls) = self.authorities.borrow().get(&package.identity) {
73            for url in urls {
74                self.store
75                    .check_acquired_remote(url.as_str(), &self.authority)
76                    .map_err(|error| Error::Module(error.to_string()))?;
77            }
78        }
79        self.store
80            .check_acquired_remote(package.url.as_str(), &self.authority)
81            .map_err(|error| Error::Module(error.to_string()))
82    }
83
84    async fn load(&self, directory: &Url) -> Result<Rc<Package>, Error> {
85        let config_url = directory
86            .join("avril.conf")
87            .map_err(|error| Error::Module(error.to_string()))?;
88        self.authority
89            .check_remote_module(&config_url)
90            .map_err(|error| Error::Module(error.to_string()))?;
91        if let Some(package) = self.packages.borrow().get(directory).cloned() {
92            self.check(&package)?;
93            return Ok(package);
94        }
95        let source = self
96            .store
97            .remote_config(config_url.as_str())
98            .await
99            .map_err(|error| Error::Module(error.to_string()))?;
100        let base = Url::parse(
101            &self
102                .store
103                .remote_base(config_url.as_str())
104                .map_err(|error| Error::Module(error.to_string()))?,
105        )
106        .map_err(|error| Error::Module(error.to_string()))?;
107        let config = PackageConfig::parse(&source, &base)
108            .map_err(|error| Error::Module(error.render(config_url.as_str(), &source)))?;
109        let package = Rc::new(Package {
110            requested: directory.clone(),
111            directory: base.join("./").unwrap(),
112            identity: (base, checksum(source.as_bytes())),
113            config,
114            source,
115            url: config_url,
116            bindings: RefCell::default(),
117        });
118        self.check(&package)?;
119        Ok(self
120            .packages
121            .borrow_mut()
122            .entry(directory.clone())
123            .or_insert(package)
124            .clone())
125    }
126
127    pub async fn bindings(
128        &self,
129        config: &PackageConfig,
130    ) -> Result<BTreeMap<String, Source>, crate::config::Error> {
131        config
132            .bindings(|source| async move {
133                match source {
134                    Source::Local(directory) => declared_name(&directory, &self.authority),
135                    Source::Http(directory) => self
136                        .load(&directory)
137                        .await
138                        .map(|package| package.config.name.clone())
139                        .map_err(|error| error.to_string()),
140                    Source::Jsr(_) => unreachable!(),
141                    Source::Npm(_) => unreachable!(),
142                }
143            })
144            .await
145    }
146
147    fn owner(&self, name: &str) -> Option<Owner> {
148        self.owners.borrow().get(name).cloned().flatten()
149    }
150
151    fn claim(&self, name: &str, owner: Option<Owner>) -> Result<(), Error> {
152        let mut owners = self.owners.borrow_mut();
153        if let Some(previous) = owners.get(name) {
154            let same = match (previous, &owner) {
155                (None, None) => true,
156                (Some(previous), Some(owner)) => {
157                    previous.package.identity == owner.package.identity
158                        && previous.region == owner.region
159                }
160                _ => false,
161            };
162            if !same {
163                let label = |owner: &Option<Owner>| {
164                    owner
165                        .as_ref()
166                        .map(|owner| owner.package.url.as_str())
167                        .unwrap_or("plain HTTP module")
168                        .to_owned()
169                };
170                return Err(Error::Module(format!(
171                    "Conflicting remote package ownership for {name}: {} versus {}",
172                    label(previous),
173                    label(&owner)
174                )));
175            }
176        }
177        if let Some(owner) = &owner {
178            self.check(&owner.package)?;
179            self.authorities
180                .borrow_mut()
181                .entry(owner.package.identity.clone())
182                .or_default()
183                .insert(owner.package.url.clone());
184        }
185        owners.entry(name.to_owned()).or_insert(owner);
186        Ok(())
187    }
188
189    async fn owner_for(&self, name: &str, mut directory: Url) -> Result<Owner, Error> {
190        let mut seen = BTreeSet::new();
191        loop {
192            if !seen.insert(directory.clone()) {
193                return Err(Error::Module(format!(
194                    "Cyclic remote package boundary for {name}: {directory}"
195                )));
196            }
197            let package = self.load(&directory).await?;
198            let region = package
199                .region(name)
200                .ok_or_else(|| {
201                    Error::Module(format!("Module is outside its remote package: {name}"))
202                })?
203                .clone();
204            let length = region.as_str().len();
205            let child = package
206                .config
207                .dependencies
208                .iter()
209                .filter_map(|dependency| match &dependency.source {
210                    Source::Http(directory)
211                        if name.starts_with(directory.as_str())
212                            && directory.as_str().len() > length =>
213                    {
214                        Some(directory)
215                    }
216                    _ => None,
217                })
218                .max_by_key(|directory| directory.as_str().len())
219                .cloned();
220            match child {
221                Some(child) => directory = child,
222                None => return Ok(Owner { package, region }),
223            }
224        }
225    }
226
227    pub async fn import(&self, name: &str, referrer: &str, local: &[Url]) -> Result<(), Error> {
228        if name.starts_with(self.store.options().jsr_registry.as_str()) {
229            return self.claim(name, None);
230        }
231        let owner = self.owner(referrer);
232        let mut selected = owner
233            .as_ref()
234            .filter(|owner| name.starts_with(owner.region.as_str()))
235            .map(|owner| (owner.region.as_str().len(), owner.package.requested.clone()));
236        let declarations: Vec<_> = match &owner {
237            Some(owner) => {
238                self.check(&owner.package)?;
239                owner
240                    .package
241                    .config
242                    .dependencies
243                    .iter()
244                    .filter_map(|dependency| match &dependency.source {
245                        Source::Http(directory) => Some(directory.clone()),
246                        _ => None,
247                    })
248                    .collect()
249            }
250            None => local.to_vec(),
251        };
252        for directory in declarations {
253            let length = directory.as_str().len();
254            if name.starts_with(directory.as_str())
255                && selected
256                    .as_ref()
257                    .is_none_or(|(previous, _)| length > *previous)
258            {
259                selected = Some((length, directory));
260            }
261        }
262        let owner = match selected {
263            Some((_, directory)) => Some(self.owner_for(name, directory).await?),
264            None => None,
265        };
266        self.claim(name, owner)
267    }
268
269    pub async fn export(
270        &self,
271        directory: &Url,
272        key: &str,
273        specifier: &str,
274    ) -> Result<Resolution, Error> {
275        let package = self.load(directory).await?;
276        let name = self.export_target(&package, key, specifier)?;
277        let owner = self.owner_for(&name, directory.clone()).await?;
278        self.claim(&name, Some(owner))?;
279        Ok(Resolution::Remote(name))
280    }
281
282    pub async fn initialize(&self, name: &str) -> Result<(), Error> {
283        let owner = self
284            .owners
285            .borrow_mut()
286            .entry(name.to_owned())
287            .or_default()
288            .clone();
289        if let Some(owner) = owner {
290            let package = owner.package;
291            self.check(&package)?;
292            if package.bindings.borrow().is_none() {
293                let bindings = self.bindings(&package.config).await.map_err(|error| {
294                    Error::Module(error.render(package.url.as_str(), &package.source))
295                })?;
296                self.check(&package)?;
297                *package.bindings.borrow_mut() = Some(bindings);
298            }
299        }
300        Ok(())
301    }
302
303    /// Shared synchronous decision over an owned remote package: what its
304    /// own imports/scopes select, falling back to its prepared bindings.
305    /// `None` selects nothing: the caller resolves the raw specifier, so
306    /// the prepared path still never initializes or loads packages.
307    fn classify(
308        &self,
309        package: &Rc<Package>,
310        specifier: &str,
311        referrer: &str,
312    ) -> Result<Option<RemoteStep>, Error> {
313        let url = Url::parse(
314            &self
315                .store
316                .remote_base(referrer)
317                .map_err(|error| Error::Module(error.to_string()))?,
318        )
319        .unwrap();
320        match package_mapping(&package.config, specifier, &url)
321            .map_err(|error| Error::Module(error.to_string()))?
322        {
323            Some(PackageMapping::Specifier(target)) => {
324                Ok(Some(RemoteStep::Done(Resolution::Specifier(target))))
325            }
326            Some(PackageMapping::Export(key)) => {
327                Ok(Some(RemoteStep::Export(package.requested.clone(), key)))
328            }
329            None => match select_binding(package.bindings.borrow().as_ref(), specifier) {
330                Some((key, Source::Http(directory))) => {
331                    Ok(Some(RemoteStep::Export(directory, key)))
332                }
333                _ => Ok(None),
334            },
335        }
336    }
337
338    pub fn type_override(&self, specifier: &str, referrer: &str) -> Result<Option<String>, Error> {
339        let Some(owner) = self.owner(referrer) else {
340            return Ok(None);
341        };
342        self.check(&owner.package)?;
343        let referrer_url =
344            Url::parse(referrer).map_err(|error| Error::Module(error.to_string()))?;
345        let result = owner
346            .package
347            .config
348            .types
349            .imports
350            .resolve(specifier, &referrer_url)
351            .map_err(|error| Error::Module(error.to_string()))?;
352        self.check(&owner.package)?;
353        Ok(result)
354    }
355
356    pub fn type_globals(&self, referrer: &str) -> Result<Vec<String>, Error> {
357        let Some(owner) = self.owner(referrer) else {
358            return Ok(Vec::new());
359        };
360        self.check(&owner.package)?;
361        let globals = owner.package.config.types.globals.clone();
362        self.check(&owner.package)?;
363        Ok(globals)
364    }
365
366    pub async fn resolve(
367        &self,
368        specifier: &str,
369        referrer: &str,
370    ) -> Result<Option<Resolution>, Error> {
371        let Some(owner) = self.owner(referrer) else {
372            return Ok(None);
373        };
374        let package = owner.package;
375        self.initialize(referrer).await?;
376        self.check(&package)?;
377        match self.classify(&package, specifier, referrer)? {
378            None => Ok(None),
379            Some(RemoteStep::Done(resolution)) => Ok(Some(resolution)),
380            Some(RemoteStep::Export(directory, key)) => {
381                self.export(&directory, &key, specifier).await.map(Some)
382            }
383        }
384    }
385
386    pub fn resolve_export_meta(
387        &self,
388        directory: &Url,
389        key: &str,
390        specifier: &str,
391    ) -> Result<Resolution, Error> {
392        let package = self
393            .packages
394            .borrow()
395            .get(directory)
396            .cloned()
397            .ok_or_else(|| {
398                Error::Module(format!("Package metadata is not prepared: {directory}"))
399            })?;
400        self.export_target(&package, key, specifier)
401            .map(Resolution::Remote)
402    }
403
404    fn export_target(
405        &self,
406        package: &Package,
407        key: &str,
408        specifier: &str,
409    ) -> Result<String, Error> {
410        self.check(package)?;
411        if package.config.exports.is_empty() {
412            return Err(Error::Module(format!(
413                "Dependency {specifier:?} has no exports: {}",
414                package.url
415            )));
416        }
417        let export = package.config.exports.get(key).ok_or_else(|| {
418            Error::Module(format!(
419                "Package has no export {key:?} for {specifier:?}: {}",
420                package.url
421            ))
422        })?;
423        self.authority
424            .check_remote_module(&export.target)
425            .map_err(|error| Error::Module(error.to_string()))?;
426        Ok(export.target.to_string())
427    }
428
429    pub fn resolve_import_meta(
430        &self,
431        specifier: &str,
432        referrer: &str,
433    ) -> Result<Option<Resolution>, Error> {
434        let Some(owner) = self.owner(referrer) else {
435            return Ok(None);
436        };
437        let package = owner.package;
438        self.check(&package)?;
439        match self.classify(&package, specifier, referrer)? {
440            None => Ok(None),
441            Some(RemoteStep::Done(resolution)) => Ok(Some(resolution)),
442            Some(RemoteStep::Export(directory, key)) => self
443                .resolve_export_meta(&directory, &key, specifier)
444                .map(Some),
445        }
446    }
447
448    pub fn metadata(&self, name: &str) -> Result<Option<serde_json::Value>, Error> {
449        let Some(owner) = self.owner(name) else {
450            return Ok(None);
451        };
452        let package = owner.package;
453        self.check(&package)?;
454        let exports: BTreeMap<_, _> = package
455            .config
456            .exports
457            .iter()
458            .map(|(key, export)| (key, export.target.as_str()))
459            .collect();
460        let bindings = package.bindings.borrow();
461        let dependencies: BTreeMap<_, _> = bindings
462            .as_ref()
463            .into_iter()
464            .flatten()
465            .map(|(name, source)| {
466                (
467                    name,
468                    match source {
469                        Source::Http(url) => serde_json::json!(url.as_str()),
470                        Source::Jsr(request) => serde_json::json!(request.specifier),
471                        Source::Npm(request) => serde_json::json!(request.request_key()),
472                        Source::Local(directory) => serde_json::json!(directory),
473                    },
474                )
475            })
476            .collect();
477        Ok(Some(serde_json::json!({
478            "config": package.identity.0.as_str(), "name": package.config.name, "version": package.config.version,
479            "description": package.config.description, "exports": exports, "dependencies": dependencies
480        })))
481    }
482}