title = "SIP 000 - `Component Dependencies`"
template = "main"
date = "2024-05-20"
---
Summary: TODO
Owner(s): brian.hardock@fermyon.com
Created: May 20, 2024
## Background
This proposal outlines a design for how a Spin developer can use WebAssembly components to satisfy dependencies within their Spin applications from a Spin component.
The goal of this SIP is to enable the 80% use-case for composition while outlining a forward compatible path for more sophisticated composition scenarios that we can unlock as broader ecosystem tooling & WASI features become available.
### What are dependencies?
Functionally dependencies are component imports. At the manifest level, dependencies describe which imports of a component to satisfy and how.
### Why not use `wac`?
Spin as developer tool for building and running WebAssembly components currently has no support for allowing developers to specify how a dependency should be satisfied whether be it an import part of the target world or a custom developer-defined import that spin is unaware of. Today, developers can use ecosystem tooling like [wac](https://github.com/bytecodealliance/wac) to pre-compose, however that requires extra tooling and steps to be added as part of a developer's workflow outside of the `spin-{build, up}` workflow.
For advanced composition, it is still recommended to use wac.
## Proposal
### Using dependencies from disk (local)
```toml
[component."infra-dashboard".dependencies]
# Components can satisfy dependencies by import name (e.g. "aws:client/s3")
# using a component from disk. Implicitly it is assumed that the
# `my_aws_client.wasm` component exports an instance of the
# `aws:client/s3`interface. TODO: point resolution doc in wac
"aws:client/s3" = { path = "my_aws_client.wasm" }
# Optionally, explicitly specify the export name to use to satisfy the `aws:client/s3` interface.
"aws:client/s3" = { path = "my_aws_client.wasm", export = "my-s3-client" }
# Without an interface name, emulate ["wac plug"](TODO -- link) functionality.
# Attempts to resolve every import of the "aws:client" package.
"aws:client" = { path = "my_aws_client.wasm" }
```
> NOTE: using `aws:client` and `aws:client/s3` will be forbidden to prevent resolution ambiguities.
### Using depdendencies from a registry (remote)
```toml
[component."infra-dashboard".dependencies]
# Equivalent to { version = "1.0.0" , package = "aws:client"}
"aws:client" = "1.0.0"
# Use the `aws:client` component package to satisfy any number of "wasi:blobstore" imports ...
"wasi:blobstore" = { registry = "my-registry.io", version = "0.1.0", package = "aws:client" }
```
### Manifest Schema
This following is a rough sketch of the `rust` manifest datastructures to be added to support the previous example snippets.
```rust
/// The format for each dependency name is required to be a kebab-cased name (e.g. foo-bar), interface id (e.g. foo:bar/baz), or package name (e.g. foo:bar) as described by the [Component Model Explainer](https://github.com/WebAssembly/component-model/blob/main/design/mvp/Explainer.md#import-and-export-definitions).)
enum DepedencyName {
Interfaces {
package: PackageName,
// Only one specific interface
name: Option<String>,
}
/// A kebab-name "foo-bar"
Plain {
name: String
}
}
struct PackageName {
namespace: String,
name: String,
version: Option<semver::Version>,
}
struct Component {
// ... existing component fields
// This is introduced by this proposal
dependencies: HashMap<DepdendencyName, Dependency>,
}
/// Represents a dependency
enum Dependency {
/// `... = ">= 0.1.0"`
Version(semver::VersionReq),
/// `... = { version = "0.1.0", registry = "registry.io", ...}`
Package {
version: semver::VersionReq,
registry: Option<String>,
package: Option<String>,
export: Option<String>
},
/// `... = { path = "path/to/component.wasm", export = "my-export" }`
Local {
path: PathBuf,
export: Option<String>,
},
// FUTURE! This is how components can attach "capabilities" to their
// dependencies by enveloping a dependency with a component manifest.
// Component {
// component: ComponentIdOrInline,
// }
}
```
## Open Questions:
1. Supporting direct plug via `*` dependencies? E.g.:
```toml
[component."infra-dashboard".dependencies]
"*" = { path = "path/to/my_depedency.wasm" }
```
2. How to configure capability inheritance, e.g. component, dependency, or application level flag? E.g.:
```toml
[component."infra-dashboard"]
# ...
inherit_capabilities = false # opt-out but validation error until we can enforce isolation
inherit_capabilities = true # opt-in all
inherit_capabilities = { http = true } # granular opt-in using wasi-virt (if it can)?
[component."infra-dashboard".dependencies]
# ...
```
## Future Work / Discussion / Other things to mention
- Capability Inheritance?
- Why punting on isolation via virtualization for now and what is the path forward to that future given the proposed schema?