1pub mod caching;
30mod decoded_component;
31mod loader;
32pub mod local;
33pub mod metadata;
34pub mod oci;
35mod publisher;
36mod release;
37
38use std::{cmp::Ordering, collections::HashMap, path::Path, pin::Pin, sync::Arc};
39
40use anyhow::anyhow;
41use bytes::Bytes;
42use decoded_component::DecodedComponent;
43use futures_util::Stream;
44use tokio::sync::RwLock;
45use wasm_pkg_common::metadata::{LOCAL_PROTOCOL, OCI_PROTOCOL};
46pub use wasm_pkg_common::{
47 Error,
48 config::{Config, CustomConfig, RegistryMapping},
49 digest::ContentDigest,
50 metadata::RegistryMetadata,
51 package::{PackageRef, Version, VersionReq},
52 registry::Registry,
53};
54
55use crate::loader::VersionSort;
56use crate::local::LocalBackend;
57use crate::metadata::RegistryMetadataExt;
58use crate::oci::OciBackend;
59pub use crate::{loader::PackageLoader, publisher::PackagePublisher};
60
61pub use release::{Release, VersionInfo};
62
63pub type ContentStream = Pin<Box<dyn Stream<Item = Result<Bytes, Error>> + Send + 'static>>;
65
66pub type PublishingSource = Pin<Box<dyn ReaderSeeker + Send + Sync + 'static>>;
68
69pub trait ReaderSeeker: tokio::io::AsyncRead + tokio::io::AsyncSeek {}
71
72impl<T> ReaderSeeker for T where T: tokio::io::AsyncRead + tokio::io::AsyncSeek {}
73
74trait LoaderPublisher: PackageLoader + PackagePublisher {}
75
76impl<T> LoaderPublisher for T where T: PackageLoader + PackagePublisher {}
77
78type RegistrySources = HashMap<Registry, Arc<InnerClient>>;
79type InnerClient = Box<dyn LoaderPublisher + Sync>;
80
81#[derive(Clone, Debug, Default)]
83pub struct PublishOpts {
84 pub package: Option<(PackageRef, Version)>,
86 pub registry: Option<Registry>,
88 pub dry_run: bool,
91 pub skip_semver_check: bool,
93}
94
95#[derive(Clone)]
97pub struct Client {
98 config: Arc<Config>,
99 sources: Arc<RwLock<RegistrySources>>,
100}
101
102impl Client {
103 pub fn new(config: Config) -> Self {
105 Self {
106 config: Arc::new(config),
107 sources: Default::default(),
108 }
109 }
110
111 pub fn config(&self) -> &Config {
113 &self.config
114 }
115
116 pub async fn with_global_defaults() -> Result<Self, Error> {
118 let config = Config::global_defaults().await?;
119 Ok(Self::new(config))
120 }
121
122 pub async fn list_all_versions(&self, package: &PackageRef) -> Result<Vec<VersionInfo>, Error> {
124 let source = self.resolve_source(package, None).await?;
125 source.list_all_versions(package).await
126 }
127
128 pub async fn get_release(
130 &self,
131 package: &PackageRef,
132 version: &Version,
133 ) -> Result<Release, Error> {
134 let source = self.resolve_source(package, None).await?;
136 source.get_release(package, version).await
137 }
138
139 pub async fn stream_content<'a>(
142 &'a self,
143 package: &'a PackageRef,
144 release: &'a Release,
145 ) -> Result<ContentStream, Error> {
146 let source = self.resolve_source(package, None).await?;
147 source.stream_content(package, release).await
148 }
149
150 pub async fn publish_release_file(
154 &self,
155 file: impl AsRef<Path>,
156 additional_options: PublishOpts,
157 ) -> Result<(PackageRef, Version), Error> {
158 let data = tokio::fs::OpenOptions::new().read(true).open(file).await?;
159
160 self.publish_release_data(Box::pin(data), additional_options)
161 .await
162 }
163
164 pub async fn publish_release_data(
168 &self,
169 data: PublishingSource,
170 additional_options: PublishOpts,
171 ) -> Result<(PackageRef, Version), Error> {
172 let registry = additional_options.registry;
174 let semver_check: bool = additional_options.skip_semver_check;
175 let pkg_authority = additional_options.package;
176
177 let (data, candidate) =
179 DecodedComponent::from_publishing_source_with_package(data, pkg_authority).await?;
180
181 let (package, version) = (
182 candidate.package().to_owned(),
183 candidate.version().to_owned(),
184 );
185 let source = self.resolve_source(&package, registry).await?;
186
187 if !semver_check {
189 let mut neighbors: [Option<VersionInfo>; 2] = [None, None];
191 for version_info in
192 fetch_semver_series(source.as_ref().as_ref(), &package, &version).await?
193 {
194 match version.cmp(&version_info.version) {
195 Ordering::Equal => {
196 return Err(Error::VersionAlreadyExists(
197 package.clone(),
198 version.to_owned(),
199 ));
200 }
201 Ordering::Greater => {
202 neighbors[0] = Some(version_info);
204 break;
205 }
206 Ordering::Less => {
207 neighbors[1] = Some(version_info);
209 }
210 }
211 }
212
213 let prepare_neighbor_ops: Vec<_> = neighbors
215 .into_iter()
216 .flatten()
217 .map(|v| fetch_and_resolve_package(&**source, &package, v.version))
218 .collect();
219
220 let mut semver_series: Vec<decoded_component::DecodedComponent> =
222 futures_util::future::join_all(prepare_neighbor_ops)
223 .await
224 .into_iter()
225 .collect::<Result<_, _>>()?;
226
227 if !semver_series.is_empty() {
229 semver_series.push(candidate);
230
231 semver_series.sort();
232 for window in semver_series.windows(2) {
233 let [prev, next] = window else { unreachable!() };
234 prev.semver_check(next)?;
235 }
236 }
237 }
238
239 source
240 .publish(&package, &version, data, additional_options.dry_run)
241 .await
242 .map(|_| (package, version))
243 }
244
245 fn resolve_registry(
246 &self,
247 package: &PackageRef,
248 registry_override: Option<Registry>,
249 ) -> Result<Registry, Error> {
250 if let Some(registry) = registry_override {
251 return Ok(registry);
252 }
253 self.config
254 .resolve_registry(package)
255 .cloned()
256 .ok_or_else(|| Error::NoRegistryForNamespace(package.namespace().clone()))
257 }
258
259 async fn resolve_source(
260 &self,
261 package: &PackageRef,
262 registry_override: Option<Registry>,
263 ) -> Result<Arc<InnerClient>, Error> {
264 let is_override = registry_override.is_some();
265 let registry = self.resolve_registry(package, registry_override)?;
266 tracing::debug!(?registry, "resolved registry");
267
268 if let Some(source) = self.sources.read().await.get(®istry) {
269 return Ok(source.clone());
270 }
271
272 let registry_config = self
273 .config
274 .registry_config(®istry)
275 .cloned()
276 .unwrap_or_default();
277
278 let maybe_metadata = self
279 .config
280 .package_registry_override(package)
281 .and_then(|mapping| match mapping {
282 RegistryMapping::Custom(custom) => Some(custom.metadata.clone()),
283 _ => None,
284 })
285 .or_else(|| {
286 self.config
287 .namespace_registry(package.namespace())
288 .and_then(|meta| {
289 match (meta, is_override) {
293 (RegistryMapping::Custom(custom), true)
294 if custom.registry == registry =>
295 {
296 Some(custom.metadata.clone())
297 }
298 (RegistryMapping::Custom(custom), false) => {
299 Some(custom.metadata.clone())
300 }
301 _ => None,
302 }
303 })
304 });
305
306 let registry_meta = if let Some(meta) = maybe_metadata {
307 meta
308 } else if registry_config.default_backend() == LOCAL_PROTOCOL.into() {
309 RegistryMetadata::default()
311 } else {
312 RegistryMetadata::fetch_or_default(®istry).await
313 };
314
315 let backend_type = match registry_config.default_backend() {
317 Some(backend_type) => Some(backend_type),
319 None => {
320 let preferred_protocol = registry_meta.preferred_protocol();
322 if preferred_protocol == Some(LOCAL_PROTOCOL) {
324 return Err(Error::InvalidRegistryMetadata(anyhow!(
325 "registry metadata with 'local' protocol not allowed"
326 )));
327 }
328 preferred_protocol
329 }
330 }
331 .unwrap_or(OCI_PROTOCOL);
333 tracing::debug!(?backend_type, "Resolved backend type");
334
335 let source: InnerClient = match backend_type {
336 LOCAL_PROTOCOL => Box::new(LocalBackend::new(registry_config)?),
337 OCI_PROTOCOL => Box::new(OciBackend::new(
338 ®istry,
339 ®istry_config,
340 ®istry_meta,
341 )?),
342 other => {
343 return Err(Error::InvalidConfig(anyhow!(
344 "unknown backend type {other:?}"
345 )));
346 }
347 };
348 let source = Arc::new(source);
349 self.sources
350 .write()
351 .await
352 .insert(registry.clone(), source.clone());
353
354 Ok(source)
355 }
356}
357
358async fn fetch_semver_series(
365 source: &(dyn LoaderPublisher + Sync),
366 package: &PackageRef,
367 version: &Version,
368) -> Result<Vec<VersionInfo>, Error> {
369 let mask = if version.major > 0 {
370 format!("{}.*", version.major)
371 } else if version.minor > 0 {
372 format!("0.{}.*", version.minor)
373 } else {
374 version.to_string()
375 };
376 let req = VersionReq::parse(&mask)
377 .map_err(|e| Error::InvalidConfig(anyhow!("invalid version mask: {e}")))?;
378
379 source
380 .list_matching_versions(package, req, VersionSort::Descending)
381 .await
382}
383
384async fn fetch_and_resolve_package(
385 source: &(dyn LoaderPublisher + Sync),
386 package: &PackageRef,
387 version: Version,
388) -> Result<decoded_component::DecodedComponent, Error> {
389 let stream = source
390 .stream_content(package, &source.get_release(package, &version).await?)
391 .await
392 .map_err(std::io::Error::other)?;
393
394 DecodedComponent::from_content_stream(stream, package.clone(), version).await
395}