├── .gitignore ├── examples ├── mysql │ ├── .gitignore │ ├── .cargo │ │ └── config.toml │ ├── db │ │ └── pets.sql │ ├── Cargo.toml │ ├── spin.toml │ └── src │ │ └── model.rs ├── hello-world │ ├── .gitignore │ ├── .cargo │ │ └── config.toml │ ├── src │ │ └── lib.rs │ ├── Cargo.toml │ └── spin.toml ├── http-router │ ├── .gitignore │ ├── .cargo │ │ └── config.toml │ ├── Cargo.toml │ ├── spin.toml │ └── src │ │ └── lib.rs ├── json-http │ ├── .gitignore │ ├── .cargo │ │ └── config.toml │ ├── Cargo.toml │ ├── README.md │ ├── src │ │ └── lib.rs │ └── spin.toml ├── variables │ ├── .gitignore │ ├── .env │ ├── .cargo │ │ └── config.toml │ ├── Cargo.toml │ ├── src │ │ └── lib.rs │ └── spin.toml ├── http-router-macro │ ├── .gitignore │ ├── .cargo │ │ └── config.toml │ ├── Cargo.toml │ ├── spin.toml │ └── src │ │ └── lib.rs ├── http-outbound │ ├── http-hello │ │ ├── .gitignore │ │ ├── .cargo │ │ │ └── config.toml │ │ ├── src │ │ │ └── lib.rs │ │ └── Cargo.toml │ ├── outbound-http │ │ ├── .cargo │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ ├── outbound-http-to-same-app │ │ ├── .cargo │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ └── spin.toml ├── wasi-http-streaming-file │ ├── .gitignore │ ├── .cargo │ │ └── config.toml │ ├── Cargo.toml │ ├── spin.toml │ └── src │ │ └── lib.rs ├── wasip3-streaming │ ├── .gitignore │ ├── Cargo.toml │ └── spin.toml ├── wasi-http-streaming-outgoing-body │ ├── .gitignore │ ├── .cargo │ │ └── config.toml │ ├── input-file.bin │ ├── Cargo.toml │ └── spin.toml ├── wasip3-http-axum-router │ ├── .gitignore │ ├── README.md │ ├── Cargo.toml │ ├── spin.toml │ └── src │ │ └── lib.rs ├── key-value │ ├── .cargo │ │ └── config.toml │ ├── Cargo.toml │ ├── spin.toml │ ├── README.md │ └── src │ │ └── lib.rs ├── postgres │ ├── .cargo │ │ └── config.toml │ ├── Cargo.toml │ ├── spin.toml │ ├── db │ │ └── testdata.sql │ └── README.md ├── redis │ ├── .cargo │ │ └── config.toml │ ├── src │ │ └── lib.rs │ ├── Cargo.toml │ └── spin.toml ├── mqtt-outbound │ ├── .cargo │ │ └── config.toml │ ├── Cargo.toml │ ├── spin.toml │ └── src │ │ └── lib.rs ├── postgres-v3 │ ├── .cargo │ │ └── config.toml │ ├── Cargo.toml │ ├── spin.toml │ ├── db │ │ └── testdata.sql │ └── README.md ├── postgres-v4 │ ├── .cargo │ │ └── config.toml │ ├── Cargo.toml │ ├── db │ │ └── testdata.sql │ ├── spin.toml │ ├── README.md │ └── src │ │ └── lib.rs ├── redis-async │ ├── .cargo │ │ └── config.toml │ ├── src │ │ └── lib.rs │ ├── Cargo.toml │ └── spin.toml ├── redis-outbound │ ├── .cargo │ │ └── config.toml │ ├── Cargo.toml │ ├── spin.toml │ └── src │ │ └── lib.rs ├── wasip3-concurrent-outbound-http-calls │ ├── .gitignore │ ├── Cargo.toml │ └── spin.toml ├── wasip3-http-hello-world │ ├── src │ │ └── lib.rs │ ├── Cargo.toml │ └── spin.toml └── wasip3-http-send-request │ ├── Cargo.toml │ ├── src │ └── lib.rs │ └── spin.toml ├── wit ├── deps │ ├── io-2023-10-18 │ │ ├── world.wit │ │ └── poll.wit │ ├── cli │ │ ├── run.wit │ │ ├── command.wit │ │ ├── exit.wit │ │ ├── stdio.wit │ │ ├── imports.wit │ │ ├── environment.wit │ │ └── terminal.wit │ ├── cli-2023-10-18 │ │ ├── run.wit │ │ ├── command.wit │ │ ├── exit.wit │ │ ├── stdio.wit │ │ ├── environment.wit │ │ ├── reactor.wit │ │ └── terminal.wit │ ├── cli-2023-11-10 │ │ ├── run.wit │ │ ├── command.wit │ │ ├── exit.wit │ │ ├── stdio.wit │ │ ├── environment.wit │ │ ├── reactor.wit │ │ └── terminal.wit │ ├── io │ │ ├── world.wit │ │ ├── error.wit │ │ └── poll.wit │ ├── filesystem │ │ ├── world.wit │ │ └── preopens.wit │ ├── clocks │ │ ├── world.wit │ │ ├── monotonic-clock.wit │ │ └── wall-clock.wit │ ├── io-2023-11-10 │ │ ├── world.wit │ │ ├── error.wit │ │ └── poll.wit │ ├── filesystem-2023-10-18 │ │ ├── world.wit │ │ └── preopens.wit │ ├── filesystem-2023-11-10 │ │ ├── world.wit │ │ └── preopens.wit │ ├── random │ │ ├── world.wit │ │ ├── insecure.wit │ │ ├── insecure-seed.wit │ │ └── random.wit │ ├── clocks-2023-11-10 │ │ ├── world.wit │ │ ├── monotonic-clock.wit │ │ └── wall-clock.wit │ ├── spin@unversioned │ │ ├── inbound-http.wit │ │ ├── http.wit │ │ ├── inbound-redis.wit │ │ ├── config.wit │ │ ├── world.wit │ │ ├── redis-types.wit │ │ ├── mysql.wit │ │ ├── postgres.wit │ │ ├── http-types.wit │ │ ├── rdbms-types.wit │ │ ├── redis.wit │ │ └── sqlite.wit │ ├── random-2023-10-18 │ │ ├── world.wit │ │ ├── insecure.wit │ │ ├── insecure-seed.wit │ │ └── random.wit │ ├── random-2023-11-10 │ │ ├── world.wit │ │ ├── insecure.wit │ │ ├── insecure-seed.wit │ │ └── random.wit │ ├── wasi-runtime-config-2024-09-27 │ │ ├── world.wit │ │ └── store.wit │ ├── clocks-2023-10-18 │ │ ├── world.wit │ │ ├── monotonic-clock.wit │ │ ├── wall-clock.wit │ │ └── timezone.wit │ ├── sockets │ │ ├── world.wit │ │ ├── instance-network.wit │ │ ├── udp-create-socket.wit │ │ └── tcp-create-socket.wit │ ├── sockets-2023-10-18 │ │ ├── instance-network.wit │ │ ├── world.wit │ │ ├── udp-create-socket.wit │ │ └── tcp-create-socket.wit │ ├── sockets-2023-11-10 │ │ ├── instance-network.wit │ │ ├── world.wit │ │ ├── udp-create-socket.wit │ │ └── tcp-create-socket.wit │ ├── spin@3.0.0 │ │ └── world.wit │ ├── spin@3.2.0 │ │ └── world.wit │ ├── spin@2.0.0 │ │ ├── postgres.wit │ │ ├── mysql.wit │ │ ├── variables.wit │ │ ├── mqtt.wit │ │ ├── world.wit │ │ ├── sqlite.wit │ │ ├── rdbms-types.wit │ │ ├── key-value.wit │ │ └── llm.wit │ ├── keyvalue-2024-10-17 │ │ ├── watch.wit │ │ ├── world.wit │ │ └── atomic.wit │ ├── http-2023-10-18 │ │ ├── outgoing-handler.wit │ │ ├── incoming-handler.wit │ │ └── proxy.wit │ ├── http │ │ ├── proxy.wit │ │ └── handler.wit │ ├── http-2023-11-10 │ │ ├── proxy.wit │ │ └── handler.wit │ └── spin-sqlite@3.0.0 │ │ └── sqlite.wit └── world.wit ├── src ├── bin │ └── version.rs ├── mqtt.rs └── variables.rs ├── crates ├── executor │ ├── README.md │ └── Cargo.toml ├── macro │ ├── wit │ │ ├── deps │ │ │ ├── cli │ │ │ │ ├── run.wit │ │ │ │ ├── command.wit │ │ │ │ ├── exit.wit │ │ │ │ ├── stdio.wit │ │ │ │ ├── imports.wit │ │ │ │ ├── environment.wit │ │ │ │ └── terminal.wit │ │ │ ├── io │ │ │ │ ├── world.wit │ │ │ │ ├── error.wit │ │ │ │ └── poll.wit │ │ │ ├── filesystem │ │ │ │ ├── world.wit │ │ │ │ └── preopens.wit │ │ │ ├── clocks │ │ │ │ ├── world.wit │ │ │ │ ├── monotonic-clock.wit │ │ │ │ └── wall-clock.wit │ │ │ ├── random │ │ │ │ ├── world.wit │ │ │ │ ├── insecure.wit │ │ │ │ ├── insecure-seed.wit │ │ │ │ └── random.wit │ │ │ ├── sockets │ │ │ │ ├── world.wit │ │ │ │ ├── instance-network.wit │ │ │ │ ├── udp-create-socket.wit │ │ │ │ └── tcp-create-socket.wit │ │ │ └── http │ │ │ │ ├── proxy.wit │ │ │ │ └── handler.wit │ │ ├── inbound-redis.wit │ │ ├── world.wit │ │ └── redis-types.wit │ ├── README.md │ └── Cargo.toml ├── spin-wasip3-http-macro │ ├── Cargo.toml │ └── src │ │ └── lib.rs └── spin-wasip3-http │ └── Cargo.toml ├── adapters ├── ab5a4484 │ └── wasi_snapshot_preview1.reactor.wasm └── README.md ├── test-cases ├── simple-redis │ ├── src │ │ └── lib.rs │ └── Cargo.toml └── simple-http │ ├── Cargo.toml │ └── src │ └── lib.rs ├── MAINTAINERS.md ├── .github └── workflows │ ├── build-examples.yml │ ├── build.yml │ └── release.yml ├── scripts └── build_examples.sh └── release-process.md /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | .spin 3 | 4 | -------------------------------------------------------------------------------- /examples/mysql/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | -------------------------------------------------------------------------------- /examples/hello-world/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | -------------------------------------------------------------------------------- /examples/http-router/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | -------------------------------------------------------------------------------- /examples/json-http/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | -------------------------------------------------------------------------------- /examples/variables/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | -------------------------------------------------------------------------------- /examples/http-router-macro/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | -------------------------------------------------------------------------------- /examples/http-outbound/http-hello/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | -------------------------------------------------------------------------------- /examples/wasi-http-streaming-file/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | -------------------------------------------------------------------------------- /examples/variables/.env: -------------------------------------------------------------------------------- 1 | SPIN_VARIABLE_DOTENV = "dotenv" 2 | -------------------------------------------------------------------------------- /examples/wasip3-streaming/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .spin/ 3 | -------------------------------------------------------------------------------- /examples/wasi-http-streaming-outgoing-body/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | -------------------------------------------------------------------------------- /examples/wasip3-http-axum-router/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .spin/ 3 | -------------------------------------------------------------------------------- /wit/deps/io-2023-10-18/world.wit: -------------------------------------------------------------------------------- 1 | package wasi:io@0.2.0-rc-2023-10-18; 2 | -------------------------------------------------------------------------------- /examples/json-http/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "wasm32-wasip1" 3 | -------------------------------------------------------------------------------- /examples/key-value/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "wasm32-wasip1" 3 | -------------------------------------------------------------------------------- /examples/mysql/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "wasm32-wasip1" 3 | -------------------------------------------------------------------------------- /examples/postgres/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "wasm32-wasip1" 3 | -------------------------------------------------------------------------------- /examples/redis/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "wasm32-wasip1" 3 | -------------------------------------------------------------------------------- /examples/variables/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "wasm32-wasip1" 3 | -------------------------------------------------------------------------------- /examples/hello-world/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "wasm32-wasip1" 3 | -------------------------------------------------------------------------------- /examples/http-router/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "wasm32-wasip1" 3 | -------------------------------------------------------------------------------- /examples/mqtt-outbound/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "wasm32-wasip1" 3 | -------------------------------------------------------------------------------- /examples/postgres-v3/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "wasm32-wasip1" 3 | -------------------------------------------------------------------------------- /examples/postgres-v4/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "wasm32-wasip1" 3 | -------------------------------------------------------------------------------- /examples/redis-async/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "wasm32-wasip1" 3 | -------------------------------------------------------------------------------- /examples/redis-outbound/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "wasm32-wasip1" 3 | -------------------------------------------------------------------------------- /examples/wasip3-concurrent-outbound-http-calls/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .spin/ 3 | -------------------------------------------------------------------------------- /src/bin/version.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | print!("{}", env!("SDK_VERSION")); 3 | } 4 | -------------------------------------------------------------------------------- /examples/http-router-macro/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "wasm32-wasip1" 3 | -------------------------------------------------------------------------------- /examples/http-outbound/http-hello/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "wasm32-wasip1" 3 | -------------------------------------------------------------------------------- /examples/wasi-http-streaming-file/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "wasm32-wasip1" 3 | -------------------------------------------------------------------------------- /examples/http-outbound/outbound-http/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "wasm32-wasip1" 3 | -------------------------------------------------------------------------------- /examples/http-outbound/outbound-http-to-same-app/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "wasm32-wasip1" -------------------------------------------------------------------------------- /examples/wasi-http-streaming-outgoing-body/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "wasm32-wasip1" 3 | -------------------------------------------------------------------------------- /wit/deps/cli/run.wit: -------------------------------------------------------------------------------- 1 | interface run { 2 | /// Run the program. 3 | run: func() -> result; 4 | } 5 | -------------------------------------------------------------------------------- /crates/executor/README.md: -------------------------------------------------------------------------------- 1 | # The spin-executor crate 2 | 3 | This crate contains the spin-sdk's async executor. -------------------------------------------------------------------------------- /wit/deps/cli-2023-10-18/run.wit: -------------------------------------------------------------------------------- 1 | interface run { 2 | /// Run the program. 3 | run: func() -> result; 4 | } 5 | -------------------------------------------------------------------------------- /wit/deps/cli-2023-11-10/run.wit: -------------------------------------------------------------------------------- 1 | interface run { 2 | /// Run the program. 3 | run: func() -> result; 4 | } 5 | -------------------------------------------------------------------------------- /crates/macro/wit/deps/cli/run.wit: -------------------------------------------------------------------------------- 1 | interface run { 2 | /// Run the program. 3 | run: func() -> result; 4 | } 5 | -------------------------------------------------------------------------------- /crates/macro/README.md: -------------------------------------------------------------------------------- 1 | # The spin-macro crate 2 | 3 | This crate contains procedural macros for Spin and associated WIT files. -------------------------------------------------------------------------------- /wit/deps/io/world.wit: -------------------------------------------------------------------------------- 1 | package wasi:io@0.2.0; 2 | 3 | world imports { 4 | import streams; 5 | import poll; 6 | } 7 | -------------------------------------------------------------------------------- /wit/deps/cli/command.wit: -------------------------------------------------------------------------------- 1 | package wasi:cli@0.2.0; 2 | 3 | world command { 4 | include imports; 5 | 6 | export run; 7 | } 8 | -------------------------------------------------------------------------------- /crates/macro/wit/deps/io/world.wit: -------------------------------------------------------------------------------- 1 | package wasi:io@0.2.0; 2 | 3 | world imports { 4 | import streams; 5 | import poll; 6 | } 7 | -------------------------------------------------------------------------------- /crates/macro/wit/deps/cli/command.wit: -------------------------------------------------------------------------------- 1 | package wasi:cli@0.2.0; 2 | 3 | world command { 4 | include imports; 5 | 6 | export run; 7 | } 8 | -------------------------------------------------------------------------------- /wit/deps/filesystem/world.wit: -------------------------------------------------------------------------------- 1 | package wasi:filesystem@0.2.0; 2 | 3 | world imports { 4 | import types; 5 | import preopens; 6 | } 7 | -------------------------------------------------------------------------------- /wit/deps/cli/exit.wit: -------------------------------------------------------------------------------- 1 | interface exit { 2 | /// Exit the current instance and any linked instances. 3 | exit: func(status: result); 4 | } 5 | -------------------------------------------------------------------------------- /wit/deps/clocks/world.wit: -------------------------------------------------------------------------------- 1 | package wasi:clocks@0.2.0; 2 | 3 | world imports { 4 | import monotonic-clock; 5 | import wall-clock; 6 | } 7 | -------------------------------------------------------------------------------- /wit/deps/io-2023-11-10/world.wit: -------------------------------------------------------------------------------- 1 | package wasi:io@0.2.0-rc-2023-11-10; 2 | 3 | world imports { 4 | import streams; 5 | import poll; 6 | } 7 | -------------------------------------------------------------------------------- /crates/macro/wit/deps/cli/exit.wit: -------------------------------------------------------------------------------- 1 | interface exit { 2 | /// Exit the current instance and any linked instances. 3 | exit: func(status: result); 4 | } 5 | -------------------------------------------------------------------------------- /crates/macro/wit/deps/filesystem/world.wit: -------------------------------------------------------------------------------- 1 | package wasi:filesystem@0.2.0; 2 | 3 | world imports { 4 | import types; 5 | import preopens; 6 | } 7 | -------------------------------------------------------------------------------- /wit/deps/cli-2023-10-18/command.wit: -------------------------------------------------------------------------------- 1 | package wasi:cli@0.2.0-rc-2023-10-18; 2 | 3 | world command { 4 | include reactor; 5 | 6 | export run; 7 | } 8 | -------------------------------------------------------------------------------- /wit/deps/cli-2023-10-18/exit.wit: -------------------------------------------------------------------------------- 1 | interface exit { 2 | /// Exit the current instance and any linked instances. 3 | exit: func(status: result); 4 | } 5 | -------------------------------------------------------------------------------- /wit/deps/cli-2023-11-10/command.wit: -------------------------------------------------------------------------------- 1 | package wasi:cli@0.2.0-rc-2023-11-10; 2 | 3 | world command { 4 | include reactor; 5 | 6 | export run; 7 | } 8 | -------------------------------------------------------------------------------- /wit/deps/cli-2023-11-10/exit.wit: -------------------------------------------------------------------------------- 1 | interface exit { 2 | /// Exit the current instance and any linked instances. 3 | exit: func(status: result); 4 | } 5 | -------------------------------------------------------------------------------- /crates/macro/wit/deps/clocks/world.wit: -------------------------------------------------------------------------------- 1 | package wasi:clocks@0.2.0; 2 | 3 | world imports { 4 | import monotonic-clock; 5 | import wall-clock; 6 | } 7 | -------------------------------------------------------------------------------- /wit/deps/filesystem-2023-10-18/world.wit: -------------------------------------------------------------------------------- 1 | package wasi:filesystem@0.2.0-rc-2023-10-18; 2 | 3 | world imports { 4 | import types; 5 | import preopens; 6 | } 7 | -------------------------------------------------------------------------------- /wit/deps/filesystem-2023-11-10/world.wit: -------------------------------------------------------------------------------- 1 | package wasi:filesystem@0.2.0-rc-2023-11-10; 2 | 3 | world imports { 4 | import types; 5 | import preopens; 6 | } 7 | -------------------------------------------------------------------------------- /wit/deps/random/world.wit: -------------------------------------------------------------------------------- 1 | package wasi:random@0.2.0; 2 | 3 | world imports { 4 | import random; 5 | import insecure; 6 | import insecure-seed; 7 | } 8 | -------------------------------------------------------------------------------- /adapters/ab5a4484/wasi_snapshot_preview1.reactor.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spinframework/spin-rust-sdk/main/adapters/ab5a4484/wasi_snapshot_preview1.reactor.wasm -------------------------------------------------------------------------------- /wit/deps/clocks-2023-11-10/world.wit: -------------------------------------------------------------------------------- 1 | package wasi:clocks@0.2.0-rc-2023-11-10; 2 | 3 | world imports { 4 | import monotonic-clock; 5 | import wall-clock; 6 | } 7 | -------------------------------------------------------------------------------- /crates/macro/wit/deps/random/world.wit: -------------------------------------------------------------------------------- 1 | package wasi:random@0.2.0; 2 | 3 | world imports { 4 | import random; 5 | import insecure; 6 | import insecure-seed; 7 | } 8 | -------------------------------------------------------------------------------- /wit/deps/spin@unversioned/inbound-http.wit: -------------------------------------------------------------------------------- 1 | interface inbound-http { 2 | use http-types.{request, response}; 3 | 4 | handle-request: func(req: request) -> response; 5 | } 6 | -------------------------------------------------------------------------------- /wit/deps/random-2023-10-18/world.wit: -------------------------------------------------------------------------------- 1 | package wasi:random@0.2.0-rc-2023-10-18; 2 | 3 | world imports { 4 | import random; 5 | import insecure; 6 | import insecure-seed; 7 | } 8 | -------------------------------------------------------------------------------- /wit/deps/random-2023-11-10/world.wit: -------------------------------------------------------------------------------- 1 | package wasi:random@0.2.0-rc-2023-11-10; 2 | 3 | world imports { 4 | import random; 5 | import insecure; 6 | import insecure-seed; 7 | } 8 | -------------------------------------------------------------------------------- /wit/deps/wasi-runtime-config-2024-09-27/world.wit: -------------------------------------------------------------------------------- 1 | package wasi:config@0.2.0-draft-2024-09-27; 2 | 3 | world imports { 4 | /// The interface for wasi:config/store 5 | import store; 6 | } -------------------------------------------------------------------------------- /wit/deps/clocks-2023-10-18/world.wit: -------------------------------------------------------------------------------- 1 | package wasi:clocks@0.2.0-rc-2023-10-18; 2 | 3 | world imports { 4 | import monotonic-clock; 5 | import wall-clock; 6 | import timezone; 7 | } 8 | -------------------------------------------------------------------------------- /wit/deps/spin@unversioned/http.wit: -------------------------------------------------------------------------------- 1 | interface http { 2 | use http-types.{request, response, http-error}; 3 | 4 | send-request: func(req: request) -> result; 5 | } 6 | -------------------------------------------------------------------------------- /adapters/README.md: -------------------------------------------------------------------------------- 1 | The subdirectory of this directory contains a build of the WASI Preview 1 2 | component adapter. It was built from commit `ab5a4484` of 3 | https://github.com/bytecodealliance/wasmtime 4 | -------------------------------------------------------------------------------- /examples/wasi-http-streaming-outgoing-body/input-file.bin: -------------------------------------------------------------------------------- 1 | ’Twas brillig, and the slithy toves 2 | Did gyre and gimble in the wabe: 3 | All mimsy were the borogoves, 4 | And the mome raths outgrabe. 5 | -------------------------------------------------------------------------------- /crates/macro/wit/inbound-redis.wit: -------------------------------------------------------------------------------- 1 | interface inbound-redis { 2 | use redis-types.{payload, error}; 3 | 4 | // The entrypoint for a Redis handler. 5 | handle-message: func(message: payload) -> result<_, error>; 6 | } 7 | -------------------------------------------------------------------------------- /wit/deps/spin@unversioned/inbound-redis.wit: -------------------------------------------------------------------------------- 1 | interface inbound-redis { 2 | use redis-types.{payload, error}; 3 | 4 | // The entrypoint for a Redis handler. 5 | handle-message: func(message: payload) -> result<_, error>; 6 | } 7 | -------------------------------------------------------------------------------- /wit/deps/filesystem-2023-10-18/preopens.wit: -------------------------------------------------------------------------------- 1 | interface preopens { 2 | use types.{descriptor}; 3 | 4 | /// Return the set of preopened directories, and their path. 5 | get-directories: func() -> list>; 6 | } 7 | -------------------------------------------------------------------------------- /examples/wasip3-http-hello-world/src/lib.rs: -------------------------------------------------------------------------------- 1 | use spin_sdk::http_wasip3::{http_service, Request}; 2 | 3 | /// A simple Spin HTTP component. 4 | #[http_service] 5 | async fn hello_world(_req: Request) -> &'static str { 6 | "Hello, world!" 7 | } 8 | -------------------------------------------------------------------------------- /crates/macro/wit/world.wit: -------------------------------------------------------------------------------- 1 | package fermyon:spin; 2 | 3 | world redis-trigger { 4 | export inbound-redis; 5 | } 6 | 7 | world wasi-http-trigger { 8 | import wasi:http/outgoing-handler@0.2.0; 9 | export wasi:http/incoming-handler@0.2.0; 10 | } 11 | -------------------------------------------------------------------------------- /test-cases/simple-redis/src/lib.rs: -------------------------------------------------------------------------------- 1 | use {anyhow::Result, bytes::Bytes, spin_sdk::redis_component, std::ops::Deref}; 2 | 3 | #[redis_component] 4 | fn on_message(message: Bytes) -> Result<()> { 5 | assert_eq!(message.deref(), b"foo"); 6 | Ok(()) 7 | } 8 | -------------------------------------------------------------------------------- /examples/key-value/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust-key-value" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | 9 | [dependencies] 10 | anyhow = "1" 11 | http = "1.3.1" 12 | spin-sdk = { path = "../.." } 13 | 14 | -------------------------------------------------------------------------------- /examples/wasip3-http-hello-world/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wasip3-hello-world" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | 9 | [dependencies] 10 | spin-sdk = { path = "../..", features = ["wasip3-unstable"] } -------------------------------------------------------------------------------- /test-cases/simple-http/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "simple-http" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | 9 | [dependencies] 10 | anyhow = "1.0.100" 11 | http = "1.3.1" 12 | spin-sdk = { path = "../.." } 13 | -------------------------------------------------------------------------------- /test-cases/simple-redis/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "simple-redis" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | 9 | [dependencies] 10 | anyhow = "1.0.100" 11 | bytes = "1.10.1" 12 | spin-sdk = { path = "../.." } 13 | -------------------------------------------------------------------------------- /wit/deps/filesystem/preopens.wit: -------------------------------------------------------------------------------- 1 | package wasi:filesystem@0.2.0; 2 | 3 | interface preopens { 4 | use types.{descriptor}; 5 | 6 | /// Return the set of preopened directories, and their path. 7 | get-directories: func() -> list>; 8 | } 9 | -------------------------------------------------------------------------------- /examples/http-outbound/outbound-http/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "http-rust-outbound-http" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | 9 | [dependencies] 10 | anyhow = "1" 11 | spin-sdk = { path = "../../../" } 12 | 13 | -------------------------------------------------------------------------------- /crates/macro/wit/deps/filesystem/preopens.wit: -------------------------------------------------------------------------------- 1 | package wasi:filesystem@0.2.0; 2 | 3 | interface preopens { 4 | use types.{descriptor}; 5 | 6 | /// Return the set of preopened directories, and their path. 7 | get-directories: func() -> list>; 8 | } 9 | -------------------------------------------------------------------------------- /examples/http-outbound/outbound-http-to-same-app/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "outbound-http-to-same-app" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | 9 | [dependencies] 10 | anyhow = "1" 11 | spin-sdk = { path = "../../../" } 12 | 13 | -------------------------------------------------------------------------------- /examples/wasip3-http-axum-router/README.md: -------------------------------------------------------------------------------- 1 | # axum-router 2 | 3 | This example shows how to use [axum](https://github.com/tokio-rs/axum) with the `spin-sdk` 4 | 5 | ``` 6 | spin up --build 7 | curl --json '{"username": "jiggs"}' localhost:3000/users 8 | {"id":1337,"username":"jiggs"} 9 | ``` -------------------------------------------------------------------------------- /test-cases/simple-http/src/lib.rs: -------------------------------------------------------------------------------- 1 | use spin_sdk::{ 2 | http::{IntoResponse, Response}, 3 | http_component, 4 | }; 5 | 6 | #[http_component] 7 | fn hello_world(_req: http::Request<()>) -> anyhow::Result { 8 | Ok(Response::new(200, "Hello, world!")) 9 | } 10 | -------------------------------------------------------------------------------- /wit/deps/sockets/world.wit: -------------------------------------------------------------------------------- 1 | package wasi:sockets@0.2.0; 2 | 3 | world imports { 4 | import instance-network; 5 | import network; 6 | import udp; 7 | import udp-create-socket; 8 | import tcp; 9 | import tcp-create-socket; 10 | import ip-name-lookup; 11 | } 12 | -------------------------------------------------------------------------------- /wit/deps/filesystem-2023-11-10/preopens.wit: -------------------------------------------------------------------------------- 1 | package wasi:filesystem@0.2.0-rc-2023-11-10; 2 | 3 | interface preopens { 4 | use types.{descriptor}; 5 | 6 | /// Return the set of preopened directories, and their path. 7 | get-directories: func() -> list>; 8 | } 9 | -------------------------------------------------------------------------------- /wit/deps/sockets-2023-10-18/instance-network.wit: -------------------------------------------------------------------------------- 1 | 2 | /// This interface provides a value-export of the default network handle.. 3 | interface instance-network { 4 | use network.{network}; 5 | 6 | /// Get a handle to the default network. 7 | instance-network: func() -> network; 8 | 9 | } 10 | -------------------------------------------------------------------------------- /wit/deps/sockets/instance-network.wit: -------------------------------------------------------------------------------- 1 | 2 | /// This interface provides a value-export of the default network handle.. 3 | interface instance-network { 4 | use network.{network}; 5 | 6 | /// Get a handle to the default network. 7 | instance-network: func() -> network; 8 | 9 | } 10 | -------------------------------------------------------------------------------- /crates/macro/wit/deps/sockets/world.wit: -------------------------------------------------------------------------------- 1 | package wasi:sockets@0.2.0; 2 | 3 | world imports { 4 | import instance-network; 5 | import network; 6 | import udp; 7 | import udp-create-socket; 8 | import tcp; 9 | import tcp-create-socket; 10 | import ip-name-lookup; 11 | } 12 | -------------------------------------------------------------------------------- /examples/http-router/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "http-rust-router" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | 9 | [dependencies] 10 | # Useful crate to handle errors. 11 | anyhow = "1" 12 | # The Spin SDK. 13 | spin-sdk = { path = "../.." } 14 | 15 | -------------------------------------------------------------------------------- /examples/variables/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "spin-variables-example" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | 9 | [dependencies] 10 | # Useful crate to handle errors. 11 | anyhow = "1" 12 | # The Spin SDK. 13 | spin-sdk = { path = "../.." } 14 | -------------------------------------------------------------------------------- /crates/macro/wit/deps/sockets/instance-network.wit: -------------------------------------------------------------------------------- 1 | 2 | /// This interface provides a value-export of the default network handle.. 3 | interface instance-network { 4 | use network.{network}; 5 | 6 | /// Get a handle to the default network. 7 | instance-network: func() -> network; 8 | 9 | } 10 | -------------------------------------------------------------------------------- /examples/hello-world/src/lib.rs: -------------------------------------------------------------------------------- 1 | use spin_sdk::http::{IntoResponse, Response}; 2 | use spin_sdk::http_component; 3 | 4 | /// A simple Spin HTTP component. 5 | #[http_component] 6 | fn hello_world(_req: http::Request<()>) -> anyhow::Result { 7 | Ok(Response::new(200, "Hello, world!")) 8 | } 9 | -------------------------------------------------------------------------------- /examples/mqtt-outbound/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust-outbound-mqtt" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | 9 | [dependencies] 10 | # Useful crate to handle errors. 11 | anyhow = "1" 12 | # The Spin SDK. 13 | spin-sdk = { path = "../.." } 14 | 15 | -------------------------------------------------------------------------------- /wit/deps/sockets-2023-11-10/instance-network.wit: -------------------------------------------------------------------------------- 1 | 2 | /// This interface provides a value-export of the default network handle.. 3 | interface instance-network { 4 | use network.{network}; 5 | 6 | /// Get a handle to the default network. 7 | instance-network: func() -> network; 8 | 9 | } 10 | -------------------------------------------------------------------------------- /examples/hello-world/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hello-world" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | 9 | [dependencies] 10 | anyhow = "1" 11 | http = "1.3.1" 12 | serde = { version = "1.0", features = ["derive"] } 13 | spin-sdk = { path = "../.." } 14 | -------------------------------------------------------------------------------- /examples/json-http/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "json-http-rust" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | 9 | [dependencies] 10 | anyhow = "1" 11 | http = "1.3.1" 12 | serde = { version = "1.0", features = ["derive"] } 13 | spin-sdk = { path = "../.." } 14 | -------------------------------------------------------------------------------- /examples/mysql/db/pets.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE pets (id INT PRIMARY KEY, name VARCHAR(100) NOT NULL, prey VARCHAR(100), is_finicky BOOL NOT NULL); 2 | INSERT INTO pets VALUES (1, 'Splodge', NULL, false); 3 | INSERT INTO pets VALUES (2, 'Kiki', 'Cicadas', false); 4 | INSERT INTO pets VALUES (3, 'Slats', 'Temptations', true); 5 | -------------------------------------------------------------------------------- /examples/redis-outbound/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust-outbound-redis" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | 9 | [dependencies] 10 | # Useful crate to handle errors. 11 | anyhow = "1" 12 | # The Spin SDK. 13 | spin-sdk = { path = "../.." } 14 | 15 | -------------------------------------------------------------------------------- /wit/deps/sockets-2023-10-18/world.wit: -------------------------------------------------------------------------------- 1 | package wasi:sockets@0.2.0-rc-2023-10-18; 2 | 3 | world imports { 4 | import instance-network; 5 | import network; 6 | import udp; 7 | import udp-create-socket; 8 | import tcp; 9 | import tcp-create-socket; 10 | import ip-name-lookup; 11 | } 12 | -------------------------------------------------------------------------------- /wit/deps/sockets-2023-11-10/world.wit: -------------------------------------------------------------------------------- 1 | package wasi:sockets@0.2.0-rc-2023-11-10; 2 | 3 | world imports { 4 | import instance-network; 5 | import network; 6 | import udp; 7 | import udp-create-socket; 8 | import tcp; 9 | import tcp-create-socket; 10 | import ip-name-lookup; 11 | } 12 | -------------------------------------------------------------------------------- /examples/http-router-macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "http-rust-router-macro" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | 9 | [dependencies] 10 | # Useful crate to handle errors. 11 | anyhow = "1" 12 | # The Spin SDK. 13 | spin-sdk = { path = "../.." } 14 | 15 | -------------------------------------------------------------------------------- /examples/redis/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use bytes::Bytes; 3 | use spin_sdk::redis_component; 4 | use std::str::from_utf8; 5 | 6 | /// A simple Spin Redis component. 7 | #[redis_component] 8 | fn on_message(message: Bytes) -> Result<()> { 9 | println!("{}", from_utf8(&message)?); 10 | Ok(()) 11 | } 12 | -------------------------------------------------------------------------------- /examples/redis-async/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use bytes::Bytes; 3 | use spin_sdk::redis_component; 4 | use std::str::from_utf8; 5 | 6 | /// A simple Spin Redis component. 7 | #[redis_component] 8 | async fn on_message(message: Bytes) -> Result<()> { 9 | println!("{}", from_utf8(&message)?); 10 | Ok(()) 11 | } 12 | -------------------------------------------------------------------------------- /examples/redis/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "spinredis" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = [ "cdylib" ] 8 | 9 | [dependencies] 10 | # Useful crate to handle errors. 11 | anyhow = "1" 12 | # Crate to simplify working with bytes. 13 | bytes = "1" 14 | # The Spin SDK. 15 | spin-sdk = { path = "../.." } 16 | -------------------------------------------------------------------------------- /examples/redis-async/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "async-spin-redis" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = [ "cdylib" ] 8 | 9 | [dependencies] 10 | # Useful crate to handle errors. 11 | anyhow = "1" 12 | # Crate to simplify working with bytes. 13 | bytes = "1" 14 | # The Spin SDK. 15 | spin-sdk = { path = "../.." } -------------------------------------------------------------------------------- /examples/http-outbound/http-hello/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use spin_sdk::http_component; 3 | 4 | /// A simple Spin HTTP component. 5 | #[http_component] 6 | fn hello_world(_req: http::Request<()>) -> Result> { 7 | Ok(http::Response::builder() 8 | .status(200) 9 | .body("Hello, Fermyon!\n")?) 10 | } 11 | -------------------------------------------------------------------------------- /examples/wasi-http-streaming-file/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "spin-wasi-http-streaming-file" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | 9 | [dependencies] 10 | anyhow = "1.0.100" 11 | futures = "0.3.31" 12 | hex = "0.4.3" 13 | sha2 = "0.10.9" 14 | url = "2.5.7" 15 | spin-sdk = { path = "../.." } 16 | 17 | -------------------------------------------------------------------------------- /examples/postgres-v4/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust-outbound-pg-v4" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | 9 | [dependencies] 10 | # Useful crate to handle errors. 11 | anyhow = "1" 12 | # General-purpose crate with common HTTP types. 13 | http = "1.3.1" 14 | # The Spin SDK. 15 | spin-sdk = { path = "../.." } 16 | -------------------------------------------------------------------------------- /examples/postgres/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust-outbound-pg" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | 9 | [dependencies] 10 | # Useful crate to handle errors. 11 | anyhow = "1" 12 | # General-purpose crate with common HTTP types. 13 | http = "1.3.1" 14 | # The Spin SDK. 15 | spin-sdk = { path = "../.." } 16 | 17 | -------------------------------------------------------------------------------- /examples/wasi-http-streaming-outgoing-body/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wasi-http-rust-streaming-outgoing-body" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | 9 | [dependencies] 10 | anyhow = "1.0.100" 11 | futures = "0.3.31" 12 | hex = "0.4.3" 13 | sha2 = "0.10.9" 14 | url = "2.5.7" 15 | spin-sdk = { path = "../.." } 16 | -------------------------------------------------------------------------------- /MAINTAINERS.md: -------------------------------------------------------------------------------- 1 | # MAINTAINERS 2 | 3 | ## Current Maintainers 4 | 5 | _Listed in alphabetical order by first name_ 6 | 7 | | Name | GitHub Username | 8 | | --- | --- | 9 | | Brian Hardock | fibonacci1729 | 10 | | Ivan Towlson | itowlson | 11 | | Joel Dice | dicej | 12 | | Lann Martin | lann | 13 | | Ryan Levick | rylev | 14 | 15 | 16 | ## Emeritus Maintainers 17 | 18 | None -------------------------------------------------------------------------------- /examples/wasip3-http-send-request/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "send-request" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | 9 | [dependencies] 10 | anyhow = "1" 11 | axum = { version = "0.8.6", default-features = false, features = ["macros"] } 12 | http = "1.3.1" 13 | spin-sdk = { path = "../..", features = ["wasip3-unstable"] } 14 | -------------------------------------------------------------------------------- /examples/http-outbound/http-hello/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "http-hello" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | 9 | [dependencies] 10 | # Useful crate to handle errors. 11 | anyhow = "1" 12 | # General-purpose crate with common HTTP types. 13 | http = "1.3.1" 14 | # The Spin SDK. 15 | spin-sdk = { path = "../../.." } 16 | 17 | -------------------------------------------------------------------------------- /examples/wasip3-http-send-request/src/lib.rs: -------------------------------------------------------------------------------- 1 | use spin_sdk::http_wasip3::{http_service, send, EmptyBody, IntoResponse, Request, Result}; 2 | 3 | /// Sends a request to a URL. 4 | #[http_service] 5 | async fn send_request(_req: Request) -> Result { 6 | let outgoing = http::Request::get("https://bytecodealliance.org").body(EmptyBody::new())?; 7 | 8 | Ok(send(outgoing).await?) 9 | } 10 | -------------------------------------------------------------------------------- /wit/deps/cli/stdio.wit: -------------------------------------------------------------------------------- 1 | interface stdin { 2 | use wasi:io/streams@0.2.0.{input-stream}; 3 | 4 | get-stdin: func() -> input-stream; 5 | } 6 | 7 | interface stdout { 8 | use wasi:io/streams@0.2.0.{output-stream}; 9 | 10 | get-stdout: func() -> output-stream; 11 | } 12 | 13 | interface stderr { 14 | use wasi:io/streams@0.2.0.{output-stream}; 15 | 16 | get-stderr: func() -> output-stream; 17 | } 18 | -------------------------------------------------------------------------------- /wit/deps/spin@unversioned/config.wit: -------------------------------------------------------------------------------- 1 | interface config { 2 | // Get a configuration value for the current component. 3 | // The config key must match one defined in in the component manifest. 4 | get-config: func(key: string) -> result; 5 | 6 | variant error { 7 | provider(string), 8 | invalid-key(string), 9 | invalid-schema(string), 10 | other(string), 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /crates/macro/wit/deps/cli/stdio.wit: -------------------------------------------------------------------------------- 1 | interface stdin { 2 | use wasi:io/streams@0.2.0.{input-stream}; 3 | 4 | get-stdin: func() -> input-stream; 5 | } 6 | 7 | interface stdout { 8 | use wasi:io/streams@0.2.0.{output-stream}; 9 | 10 | get-stdout: func() -> output-stream; 11 | } 12 | 13 | interface stderr { 14 | use wasi:io/streams@0.2.0.{output-stream}; 15 | 16 | get-stderr: func() -> output-stream; 17 | } 18 | -------------------------------------------------------------------------------- /examples/postgres-v3/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust-outbound-pg-v3" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | 9 | [dependencies] 10 | # Useful crate to handle errors. 11 | anyhow = "1" 12 | # General-purpose crate with common HTTP types. 13 | http = "1.3.1" 14 | # The Spin SDK. 15 | spin-sdk = { path = "../.." } 16 | # For handling date/time types 17 | chrono = "0.4.42" 18 | -------------------------------------------------------------------------------- /examples/json-http/README.md: -------------------------------------------------------------------------------- 1 | ## Automatically deserializing JSON request bodies in Rust HTTP 2 | 3 | This sample shows using the `http::Request>` request type to accept JSON and automatically deserialize it into a Rust struct that implements `serde::Deserialize`. 4 | 5 | To test it, run `spin up --build` and then POST to localhost:3000 e.g.: 6 | 7 | ``` 8 | curl -X POST -d '{"name": "Vyvyan"}' localhost:3000 9 | ``` 10 | -------------------------------------------------------------------------------- /examples/wasip3-http-axum-router/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "axum-router" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | 9 | [dependencies] 10 | axum = { version = "0.8.6", default-features = false, features = ["json", "macros"] } 11 | serde = { version = "1.0.228", features = ["derive"] } 12 | spin-sdk = { path = "../..", features = ["wasip3-unstable"] } 13 | tower-service = "0.3.3" 14 | -------------------------------------------------------------------------------- /examples/json-http/src/lib.rs: -------------------------------------------------------------------------------- 1 | use spin_sdk::http::{IntoResponse, Json, Response}; 2 | use spin_sdk::http_component; 3 | 4 | #[derive(serde::Deserialize, Debug)] 5 | struct Greeted { 6 | name: String, 7 | } 8 | 9 | /// A simple Spin HTTP component. 10 | #[http_component] 11 | async fn hello_world(req: http::Request>) -> anyhow::Result { 12 | Ok(Response::new(200, format!("Hello, {}", req.body().name))) 13 | } 14 | -------------------------------------------------------------------------------- /wit/deps/cli-2023-10-18/stdio.wit: -------------------------------------------------------------------------------- 1 | interface stdin { 2 | use wasi:io/streams@0.2.0-rc-2023-10-18.{input-stream}; 3 | 4 | get-stdin: func() -> input-stream; 5 | } 6 | 7 | interface stdout { 8 | use wasi:io/streams@0.2.0-rc-2023-10-18.{output-stream}; 9 | 10 | get-stdout: func() -> output-stream; 11 | } 12 | 13 | interface stderr { 14 | use wasi:io/streams@0.2.0-rc-2023-10-18.{output-stream}; 15 | 16 | get-stderr: func() -> output-stream; 17 | } 18 | -------------------------------------------------------------------------------- /wit/deps/cli-2023-11-10/stdio.wit: -------------------------------------------------------------------------------- 1 | interface stdin { 2 | use wasi:io/streams@0.2.0-rc-2023-11-10.{input-stream}; 3 | 4 | get-stdin: func() -> input-stream; 5 | } 6 | 7 | interface stdout { 8 | use wasi:io/streams@0.2.0-rc-2023-11-10.{output-stream}; 9 | 10 | get-stdout: func() -> output-stream; 11 | } 12 | 13 | interface stderr { 14 | use wasi:io/streams@0.2.0-rc-2023-11-10.{output-stream}; 15 | 16 | get-stderr: func() -> output-stream; 17 | } 18 | -------------------------------------------------------------------------------- /examples/postgres-v4/db/testdata.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE cats ( 2 | name text not null, 3 | reign int4range not null 4 | ); 5 | 6 | INSERT INTO cats (name, reign) VALUES 7 | ('Smoke', '[2005, 2013]'::int4range), 8 | ('Splodge', '[2005, 2019]'::int4range), 9 | ('Fang', '[2005, 2016]'::int4range), 10 | ('Kiki', '[2005, 2020]'::int4range), 11 | ('Slats', '[2005, 2021]'::int4range), 12 | ('Rosie', '[2021,)'::int4range), 13 | ('Hobbes', '[2021,)'::int4range) 14 | ; 15 | -------------------------------------------------------------------------------- /wit/deps/spin@3.0.0/world.wit: -------------------------------------------------------------------------------- 1 | package fermyon:spin@3.0.0; 2 | 3 | /// The full world of a guest targeting an http-trigger 4 | world http-trigger { 5 | include platform; 6 | export wasi:http/incoming-handler@0.2.0; 7 | } 8 | 9 | /// The imports needed for a guest to run on a Spin host 10 | world platform { 11 | include fermyon:spin/platform@2.0.0; 12 | include wasi:keyvalue/imports@0.2.0-draft2; 13 | import spin:postgres/postgres@3.0.0; 14 | import wasi:config/store@0.2.0-draft-2024-09-27; 15 | } 16 | -------------------------------------------------------------------------------- /examples/wasip3-streaming/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wasip3-streaming" 3 | description = "Demonstrates streaming processing of requests and responses" 4 | version = "0.1.0" 5 | rust-version = "1.90" 6 | edition = "2021" 7 | 8 | [lib] 9 | crate-type = ["cdylib"] 10 | 11 | [dependencies] 12 | anyhow = "1" 13 | bytes = "1.10.1" 14 | futures = "0.3.31" 15 | http = "1.3" 16 | http-body = "1" 17 | http-body-util = "0.1.3" 18 | spin-sdk = { path = "../..", features = ["wasip3-unstable"] } 19 | 20 | [workspace] 21 | -------------------------------------------------------------------------------- /examples/key-value/spin.toml: -------------------------------------------------------------------------------- 1 | spin_manifest_version = 2 2 | 3 | [application] 4 | authors = ["The Spin authors"] 5 | description = "A simple application that exercises key-value storage." 6 | name = "spin-key-value" 7 | version = "1.0.0" 8 | 9 | [[trigger.http]] 10 | route = "/..." 11 | component = "hello" 12 | 13 | [component.hello] 14 | source = "../../target/wasm32-wasip1/release/rust_key_value.wasm" 15 | key_value_stores = ["default"] 16 | [component.hello.build] 17 | command = "cargo build --target wasm32-wasip1 --release" 18 | -------------------------------------------------------------------------------- /examples/mysql/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust-outbound-mysql" 3 | authors = ["itowlson "] 4 | description = "Demo of calling MySQL from a Spin application" 5 | version = "0.1.0" 6 | edition = "2021" 7 | 8 | [lib] 9 | crate-type = ["cdylib"] 10 | 11 | [dependencies] 12 | # Useful crate to handle errors. 13 | anyhow = "1" 14 | # General-purpose crate with common HTTP types. 15 | http = "1.3.1" 16 | serde = "1.0.228" 17 | serde_json = "1.0.145" 18 | # The Spin SDK. 19 | spin-sdk = { path = "../.." } 20 | 21 | -------------------------------------------------------------------------------- /wit/deps/spin@3.2.0/world.wit: -------------------------------------------------------------------------------- 1 | package spin:up@3.2.0; 2 | 3 | /// The full world of a guest targeting an http-trigger 4 | world http-trigger { 5 | include platform; 6 | export wasi:http/incoming-handler@0.2.0; 7 | } 8 | 9 | /// The imports needed for a guest to run on a Spin host 10 | world platform { 11 | include fermyon:spin/platform@2.0.0; 12 | include wasi:keyvalue/imports@0.2.0-draft2; 13 | import spin:postgres/postgres@3.0.0; 14 | import spin:sqlite/sqlite@3.0.0; 15 | import wasi:config/store@0.2.0-draft-2024-09-27; 16 | } 17 | -------------------------------------------------------------------------------- /examples/redis/spin.toml: -------------------------------------------------------------------------------- 1 | spin_manifest_version = 2 2 | 3 | [application] 4 | authors = ["The Spin authors"] 5 | description = "A redis application." 6 | name = "spin-redis" 7 | version = "0.1.0" 8 | 9 | [application.trigger.redis] 10 | address = "redis://localhost:6379" 11 | 12 | [[trigger.redis]] 13 | channel = "messages" 14 | component = "echo-message" 15 | 16 | [component.echo-message] 17 | source = "../../target/wasm32-wasip1/release/spinredis.wasm" 18 | [component.echo-message.build] 19 | command = "cargo build --target wasm32-wasip1 --release" 20 | -------------------------------------------------------------------------------- /wit/deps/cli/imports.wit: -------------------------------------------------------------------------------- 1 | package wasi:cli@0.2.0; 2 | 3 | world imports { 4 | include wasi:clocks/imports@0.2.0; 5 | include wasi:filesystem/imports@0.2.0; 6 | include wasi:sockets/imports@0.2.0; 7 | include wasi:random/imports@0.2.0; 8 | include wasi:io/imports@0.2.0; 9 | 10 | import environment; 11 | import exit; 12 | import stdin; 13 | import stdout; 14 | import stderr; 15 | import terminal-input; 16 | import terminal-output; 17 | import terminal-stdin; 18 | import terminal-stdout; 19 | import terminal-stderr; 20 | } 21 | -------------------------------------------------------------------------------- /wit/deps/spin@unversioned/world.wit: -------------------------------------------------------------------------------- 1 | package fermyon:spin; 2 | 3 | world host { 4 | include platform; 5 | 6 | export inbound-http; 7 | export inbound-redis; 8 | } 9 | 10 | world redis-trigger { 11 | include platform; 12 | export inbound-redis; 13 | } 14 | 15 | world http-trigger { 16 | include platform; 17 | export inbound-http; 18 | } 19 | 20 | world platform { 21 | import config; 22 | import http; 23 | import postgres; 24 | import mysql; 25 | import sqlite; 26 | import redis; 27 | import key-value; 28 | import llm; 29 | } 30 | -------------------------------------------------------------------------------- /crates/macro/wit/deps/cli/imports.wit: -------------------------------------------------------------------------------- 1 | package wasi:cli@0.2.0; 2 | 3 | world imports { 4 | include wasi:clocks/imports@0.2.0; 5 | include wasi:filesystem/imports@0.2.0; 6 | include wasi:sockets/imports@0.2.0; 7 | include wasi:random/imports@0.2.0; 8 | include wasi:io/imports@0.2.0; 9 | 10 | import environment; 11 | import exit; 12 | import stdin; 13 | import stdout; 14 | import stderr; 15 | import terminal-input; 16 | import terminal-output; 17 | import terminal-stdin; 18 | import terminal-stdout; 19 | import terminal-stderr; 20 | } 21 | -------------------------------------------------------------------------------- /crates/executor/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "spin-executor" 3 | version = { workspace = true } 4 | authors = { workspace = true } 5 | edition = { workspace = true } 6 | license = { workspace = true } 7 | repository = { workspace = true } 8 | rust-version = { workspace = true } 9 | homepage = { workspace = true } 10 | documentation = "https://docs.rs/spin-executor" 11 | readme = "README.md" 12 | 13 | description = """ 14 | Spin SDK async executor 15 | """ 16 | 17 | [dependencies] 18 | once_cell = "1.21.3" 19 | futures = "0.3.31" 20 | wasi = { workspace = true } 21 | -------------------------------------------------------------------------------- /crates/spin-wasip3-http-macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "spin-wasip3-http-macro" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | repository.workspace = true 8 | rust-version.workspace = true 9 | homepage.workspace = true 10 | description = """ 11 | Rust procedural macros for Spin and WASIp3 12 | """ 13 | 14 | [lib] 15 | name = "spin_wasip3_http_macro" 16 | proc-macro = true 17 | 18 | [dependencies] 19 | proc-macro2 = "1" 20 | quote = "1.0" 21 | syn = { version = "1.0", features = [ "full" ]} -------------------------------------------------------------------------------- /wit/world.wit: -------------------------------------------------------------------------------- 1 | package spin:up@3.4.0; 2 | 3 | /// The full world of a guest targeting an http-trigger 4 | world http-trigger { 5 | include platform; 6 | export wasi:http/incoming-handler@0.2.0; 7 | } 8 | 9 | /// The imports needed for a guest to run on a Spin host 10 | world platform { 11 | include fermyon:spin/platform@2.0.0; 12 | include wasi:keyvalue/imports@0.2.0-draft2; 13 | import spin:postgres/postgres@3.0.0; 14 | import spin:postgres/postgres@4.0.0; 15 | import spin:sqlite/sqlite@3.0.0; 16 | import wasi:config/store@0.2.0-draft-2024-09-27; 17 | } 18 | -------------------------------------------------------------------------------- /examples/hello-world/spin.toml: -------------------------------------------------------------------------------- 1 | spin_manifest_version = 2 2 | 3 | [application] 4 | authors = ["The Spin authors"] 5 | description = "A simple application that returns hello." 6 | name = "hello-world" 7 | version = "1.0.0" 8 | 9 | [[trigger.http]] 10 | route = "/hello" 11 | component = "hello" 12 | 13 | [component.hello] 14 | source = "../../target/wasm32-wasip1/release/hello_world.wasm" 15 | description = "A simple component that returns hello." 16 | [component.hello.build] 17 | command = "cargo build --target wasm32-wasip1 --release" 18 | watch = ["src/**/*.rs", "Cargo.toml"] 19 | -------------------------------------------------------------------------------- /examples/postgres/spin.toml: -------------------------------------------------------------------------------- 1 | spin_manifest_version = 2 2 | 3 | [application] 4 | authors = ["The Spin authors"] 5 | name = "rust-outbound-pg-example" 6 | version = "0.1.0" 7 | 8 | [[trigger.http]] 9 | route = "/..." 10 | component = "outbound-pg" 11 | 12 | [component.outbound-pg] 13 | environment = { DB_URL = "host=localhost user=postgres dbname=spin_dev" } 14 | source = "../../target/wasm32-wasip1/release/rust_outbound_pg.wasm" 15 | allowed_outbound_hosts = ["postgres://localhost"] 16 | [component.outbound-pg.build] 17 | command = "cargo build --target wasm32-wasip1 --release" 18 | -------------------------------------------------------------------------------- /examples/redis-async/spin.toml: -------------------------------------------------------------------------------- 1 | spin_manifest_version = 2 2 | 3 | [application] 4 | authors = ["The Spin authors"] 5 | description = "An async redis application." 6 | name = "async-spin-redis" 7 | version = "0.1.0" 8 | 9 | [application.trigger.redis] 10 | address = "redis://localhost:6379" 11 | 12 | [[trigger.redis]] 13 | channel = "messages" 14 | component = "echo-message" 15 | 16 | [component.echo-message] 17 | source = "../../target/wasm32-wasip1/release/async_spin_redis.wasm" 18 | [component.echo-message.build] 19 | command = "cargo build --target wasm32-wasip1 --release" 20 | -------------------------------------------------------------------------------- /examples/variables/src/lib.rs: -------------------------------------------------------------------------------- 1 | use spin_sdk::{ 2 | http::{Request, Response}, 3 | http_component, variables, 4 | }; 5 | 6 | /// This endpoint returns the config value specified by key. 7 | #[http_component] 8 | fn get(req: Request) -> anyhow::Result { 9 | if req.path().contains("dotenv") { 10 | let val = variables::get("dotenv").expect("Failed to acquire dotenv from spin.toml"); 11 | return Ok(Response::new(200, val)); 12 | } 13 | let val = format!("message: {}", variables::get("message")?); 14 | Ok(Response::new(200, val)) 15 | } 16 | -------------------------------------------------------------------------------- /crates/macro/wit/redis-types.wit: -------------------------------------------------------------------------------- 1 | interface redis-types { 2 | // General purpose error. 3 | enum error { 4 | success, 5 | error, 6 | } 7 | 8 | // The message payload. 9 | type payload = list; 10 | 11 | // A parameter type for the general-purpose `execute` function. 12 | variant redis-parameter { 13 | int64(s64), 14 | binary(payload) 15 | } 16 | 17 | // A return type for the general-purpose `execute` function. 18 | variant redis-result { 19 | nil, 20 | status(string), 21 | int64(s64), 22 | binary(payload) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/postgres-v3/spin.toml: -------------------------------------------------------------------------------- 1 | spin_manifest_version = 2 2 | 3 | [application] 4 | authors = ["The Spin authors"] 5 | name = "rust-outbound-pg-v3-example" 6 | version = "0.1.0" 7 | 8 | [[trigger.http]] 9 | route = "/..." 10 | component = "outbound-pg" 11 | 12 | [component.outbound-pg] 13 | environment = { DB_URL = "host=localhost user=postgres dbname=spin_dev" } 14 | source = "../../target/wasm32-wasip1/release/rust_outbound_pg_v3.wasm" 15 | allowed_outbound_hosts = ["postgres://localhost"] 16 | [component.outbound-pg.build] 17 | command = "cargo build --target wasm32-wasip1 --release" 18 | -------------------------------------------------------------------------------- /examples/postgres-v4/spin.toml: -------------------------------------------------------------------------------- 1 | spin_manifest_version = 2 2 | 3 | [application] 4 | authors = ["The Spin authors"] 5 | name = "rust-outbound-pg-v4-example" 6 | version = "0.1.0" 7 | 8 | [[trigger.http]] 9 | route = "/:year" 10 | component = "outbound-pg" 11 | 12 | [component.outbound-pg] 13 | environment = { DB_URL = "host=localhost user=postgres dbname=spin_dev" } 14 | source = "../../target/wasm32-wasip1/release/rust_outbound_pg_v4.wasm" 15 | allowed_outbound_hosts = ["postgres://localhost"] 16 | [component.outbound-pg.build] 17 | command = "cargo build --target wasm32-wasip1 --release" 18 | -------------------------------------------------------------------------------- /examples/wasip3-concurrent-outbound-http-calls/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "concurrent-outbound-http-calls" 3 | authors = ["itowlson "] 4 | description = "" 5 | version = "0.1.0" 6 | rust-version = "1.90" # required for `wasm32-wasip2` target to build WASIp3 7 | edition = "2021" 8 | 9 | [lib] 10 | crate-type = ["cdylib"] 11 | 12 | [dependencies] 13 | anyhow = "1" 14 | bytes = "1.10.1" 15 | futures = "0.3.31" 16 | http = "1.3" 17 | http-body = "1" 18 | http-body-util = "0.1.3" 19 | spin-sdk = { path = "../..", features = ["wasip3-unstable"] } 20 | 21 | [workspace] 22 | -------------------------------------------------------------------------------- /examples/http-outbound/outbound-http-to-same-app/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use spin_sdk::{ 3 | http::{IntoResponse, Request, Response}, 4 | http_component, 5 | }; 6 | 7 | /// Send an HTTP request and return the response. 8 | #[http_component] 9 | async fn send_outbound(_req: Request) -> Result { 10 | let resp: Response = spin_sdk::http::send(Request::get("/hello")).await?; 11 | let resp = resp 12 | .into_builder() 13 | .header("spin-component", "rust-outbound-http") 14 | .build(); 15 | println!("{resp:?}"); 16 | Ok(resp) 17 | } 18 | -------------------------------------------------------------------------------- /wit/deps/spin@unversioned/redis-types.wit: -------------------------------------------------------------------------------- 1 | interface redis-types { 2 | // General purpose error. 3 | enum error { 4 | success, 5 | error, 6 | } 7 | 8 | /// The message payload. 9 | type payload = list; 10 | 11 | /// A parameter type for the general-purpose `execute` function. 12 | variant redis-parameter { 13 | int64(s64), 14 | binary(payload) 15 | } 16 | 17 | /// A return type for the general-purpose `execute` function. 18 | variant redis-result { 19 | nil, 20 | status(string), 21 | int64(s64), 22 | binary(payload) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /crates/spin-wasip3-http/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "spin-wasip3-http" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | repository.workspace = true 8 | rust-version.workspace = true 9 | homepage.workspace = true 10 | description = """ 11 | Spin WASIp3 HTTP SDK 12 | """ 13 | 14 | [dependencies] 15 | anyhow = { workspace = true } 16 | bytes = { workspace = true } 17 | http-body = { workspace = true } 18 | http-body-util = { workspace = true } 19 | hyperium = { workspace = true } 20 | wasip3 = { version = "0.2.2", features = ["http-compat"] } 21 | -------------------------------------------------------------------------------- /examples/http-router/spin.toml: -------------------------------------------------------------------------------- 1 | spin_manifest_version = 2 2 | 3 | [application] 4 | authors = ["The Spin authors"] 5 | description = "An application that demonstrates HTTP routing." 6 | name = "spin-rust-router" 7 | version = "1.0.0" 8 | 9 | [[trigger.http]] 10 | route = "/..." 11 | component = "route" 12 | 13 | [component.route] 14 | source = "../../target/wasm32-wasip1/release/http_rust_router.wasm" 15 | description = "A component that internally routes HTTP requests." 16 | [component.route.build] 17 | command = "cargo build --target wasm32-wasip1 --release" 18 | watch = ["src/**/*.rs", "Cargo.toml", "spin.toml"] 19 | -------------------------------------------------------------------------------- /examples/json-http/spin.toml: -------------------------------------------------------------------------------- 1 | spin_manifest_version = 2 2 | 3 | [application] 4 | authors = ["The Spin authors"] 5 | description = "An application which parses a JSON POST body." 6 | name = "json-http-rust" 7 | version = "1.0.0" 8 | 9 | [[trigger.http]] 10 | route = "/..." 11 | component = "json-demo" 12 | 13 | [component.json-demo] 14 | source = "../../target/wasm32-wasip1/release/json_http_rust.wasm" 15 | description = "Parses 'name' from the POST body and responds using it." 16 | [component.json-demo.build] 17 | command = "cargo build --target wasm32-wasip1 --release" 18 | watch = ["src/**/*.rs", "Cargo.toml"] 19 | -------------------------------------------------------------------------------- /examples/redis-outbound/spin.toml: -------------------------------------------------------------------------------- 1 | spin_manifest_version = 2 2 | 3 | [application] 4 | authors = ["The Spin authors"] 5 | name = "rust-outbound-redis-example" 6 | version = "0.1.0" 7 | 8 | [[trigger.http]] 9 | route = "/publish" 10 | component = "outbound-redis" 11 | 12 | [component.outbound-redis] 13 | environment = { REDIS_ADDRESS = "redis://127.0.0.1:6379", REDIS_CHANNEL = "messages" } 14 | source = "../../target/wasm32-wasip1/release/rust_outbound_redis.wasm" 15 | allowed_outbound_hosts = ["redis://127.0.0.1"] 16 | [component.outbound-redis.build] 17 | command = "cargo build --target wasm32-wasip1 --release" 18 | -------------------------------------------------------------------------------- /wit/deps/spin@2.0.0/postgres.wit: -------------------------------------------------------------------------------- 1 | interface postgres { 2 | use rdbms-types.{parameter-value, row-set, error}; 3 | 4 | /// A connection to a postgres database. 5 | resource connection { 6 | /// Open a connection to the Postgres instance at `address`. 7 | open: static func(address: string) -> result; 8 | 9 | /// Query the database. 10 | query: func(statement: string, params: list) -> result; 11 | 12 | /// Execute command to the database. 13 | execute: func(statement: string, params: list) -> result; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/http-router-macro/spin.toml: -------------------------------------------------------------------------------- 1 | spin_manifest_version = 2 2 | 3 | [application] 4 | authors = ["The Spin authors"] 5 | description = "An application that demonstrates HTTP routing." 6 | name = "spin-rust-router" 7 | version = "1.0.0" 8 | 9 | [[trigger.http]] 10 | route = "/..." 11 | component = "route" 12 | 13 | [component.route] 14 | source = "../../target/wasm32-wasip1/release/http_rust_router_macro.wasm" 15 | description = "A component that internally routes HTTP requests." 16 | [component.route.build] 17 | command = "cargo build --target wasm32-wasip1 --release" 18 | watch = ["src/**/*.rs", "Cargo.toml", "spin.toml"] 19 | -------------------------------------------------------------------------------- /wit/deps/spin@2.0.0/mysql.wit: -------------------------------------------------------------------------------- 1 | interface mysql { 2 | use rdbms-types.{parameter-value, row-set, error}; 3 | 4 | /// A connection to a MySQL database. 5 | resource connection { 6 | /// Open a connection to the MySQL instance at `address`. 7 | open: static func(address: string) -> result; 8 | 9 | /// query the database: select 10 | query: func(statement: string, params: list) -> result; 11 | 12 | /// execute command to the database: insert, update, delete 13 | execute: func(statement: string, params: list) -> result<_, error>; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/http-outbound/outbound-http/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use spin_sdk::{ 3 | http::{IntoResponse, Request, Response}, 4 | http_component, 5 | }; 6 | 7 | /// Send an HTTP request and return the response. 8 | #[http_component] 9 | async fn send_outbound(_req: Request) -> Result { 10 | let resp: Response = spin_sdk::http::send(Request::get( 11 | "https://random-data-api.fermyon.app/animals/json", 12 | )) 13 | .await?; 14 | let resp = resp 15 | .into_builder() 16 | .header("spin-component", "rust-outbound-http") 17 | .build(); 18 | println!("{resp:?}"); 19 | Ok(resp) 20 | } 21 | -------------------------------------------------------------------------------- /examples/mysql/spin.toml: -------------------------------------------------------------------------------- 1 | spin_manifest_version = 2 2 | 3 | [application] 4 | authors = ["itowlson "] 5 | description = "Demo of calling MySQL from a Spin application" 6 | name = "rust-outbound-mysql" 7 | version = "0.1.0" 8 | 9 | [[trigger.http]] 10 | route = "/..." 11 | component = "rust-outbound-mysql" 12 | 13 | [component.rust-outbound-mysql] 14 | environment = { DB_URL = "mysql://spin:spin@127.0.0.1/spin_dev" } 15 | source = "../../target/wasm32-wasip1/release/rust_outbound_mysql.wasm" 16 | allowed_outbound_hosts = ["mysql://127.0.0.1"] 17 | [component.rust-outbound-mysql.build] 18 | command = "cargo build --target wasm32-wasip1 --release" 19 | -------------------------------------------------------------------------------- /examples/wasi-http-streaming-outgoing-body/spin.toml: -------------------------------------------------------------------------------- 1 | spin_manifest_version = 2 2 | 3 | [application] 4 | authors = ["The Spin authors"] 5 | description = "An application that makes concurrent outbound HTTP requests." 6 | name = "spin-wasi-http-async" 7 | version = "1.0.0" 8 | 9 | [[trigger.http]] 10 | route = "/..." 11 | component = "wasi-http-async" 12 | 13 | [component.wasi-http-async] 14 | source = "../../target/wasm32-wasip1/release/wasi_http_rust_streaming_outgoing_body.wasm" 15 | allowed_outbound_hosts = ["http://*:*", "https://*:*"] 16 | [component.wasi-http-async.build] 17 | command = "cargo build --target wasm32-wasip1 --release" 18 | watch = ["src/**/*.rs", "Cargo.toml"] 19 | -------------------------------------------------------------------------------- /examples/mysql/src/model.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use spin_sdk::mysql::{self, Decode}; 3 | 4 | // Such logic, very business 5 | 6 | #[allow(dead_code)] 7 | #[derive(Debug, Clone)] 8 | pub(crate) struct Pet { 9 | id: i32, 10 | name: String, 11 | prey: Option, 12 | is_finicky: bool, 13 | } 14 | 15 | pub(crate) fn as_pet(row: &mysql::Row) -> Result { 16 | let id = i32::decode(&row[0])?; 17 | let name = String::decode(&row[1])?; 18 | let prey = Option::::decode(&row[2])?; 19 | let is_finicky = bool::decode(&row[3])?; 20 | 21 | Ok(Pet { 22 | id, 23 | name, 24 | prey, 25 | is_finicky, 26 | }) 27 | } 28 | -------------------------------------------------------------------------------- /crates/macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "spin-macro" 3 | version = { workspace = true } 4 | authors = { workspace = true } 5 | edition = { workspace = true } 6 | license = { workspace = true } 7 | repository = { workspace = true } 8 | rust-version = { workspace = true } 9 | homepage = { workspace = true } 10 | documentation = "https://docs.rs/spin-macro" 11 | readme = "README.md" 12 | 13 | description = """ 14 | Rust procedural macros for Spin and associated WIT files 15 | """ 16 | 17 | [lib] 18 | name = "spin_macro" 19 | proc-macro = true 20 | 21 | [dependencies] 22 | anyhow = "1" 23 | bytes = "1" 24 | proc-macro2 = "1" 25 | quote = "1.0" 26 | syn = { version = "1.0", features = [ "full" ]} 27 | -------------------------------------------------------------------------------- /examples/wasip3-http-axum-router/spin.toml: -------------------------------------------------------------------------------- 1 | #:schema https://schemas.spinframework.dev/spin/manifest-v2/latest.json 2 | 3 | spin_manifest_version = 2 4 | 5 | [application] 6 | name = "axum-router" 7 | version = "0.1.0" 8 | authors = ["The Spin authors"] 9 | description = "An example application using axum" 10 | 11 | [[trigger.http]] 12 | route = "/..." 13 | component = "axum-router" 14 | executor = { type = "wasip3-unstable" } 15 | 16 | [component.axum-router] 17 | source = "../../target/wasm32-wasip2/release/axum_router.wasm" 18 | allowed_outbound_hosts = [] 19 | [component.axum-router.build] 20 | command = "cargo build --target wasm32-wasip2 --release" 21 | watch = ["src/**/*.rs", "Cargo.toml"] 22 | -------------------------------------------------------------------------------- /examples/wasip3-http-hello-world/spin.toml: -------------------------------------------------------------------------------- 1 | #:schema https://schemas.spinframework.dev/spin/manifest-v2/latest.json 2 | 3 | spin_manifest_version = 2 4 | 5 | [application] 6 | authors = ["The Spin authors"] 7 | description = "An application that returns hello." 8 | name = "hello-world" 9 | version = "1.0.0" 10 | 11 | [[trigger.http]] 12 | route = "/hello" 13 | component = "hello" 14 | executor = { type = "wasip3-unstable" } 15 | 16 | [component.hello] 17 | source = "../../target/wasm32-wasip2/release/wasip3_hello_world.wasm" 18 | description = "A component that returns hello." 19 | [component.hello.build] 20 | command = "cargo build --target wasm32-wasip2 --release" 21 | watch = ["src/**/*.rs", "Cargo.toml"] -------------------------------------------------------------------------------- /examples/wasi-http-streaming-file/spin.toml: -------------------------------------------------------------------------------- 1 | spin_manifest_version = 2 2 | 3 | [application] 4 | authors = ["The Spin authors"] 5 | description = "An application that streams a chunked file as an HTTP response" 6 | name = "spin-wasi-http-streaming-file" 7 | version = "1.0.0" 8 | 9 | [[trigger.http]] 10 | route = "/..." 11 | component = "spin-wasi-http-streaming-file" 12 | 13 | [component.spin-wasi-http-streaming-file] 14 | source = "../../target/wasm32-wasip1/release/spin_wasi_http_streaming_file.wasm" 15 | files = [{ source = "../..", destination = "/" }] 16 | [component.spin-wasi-http-streaming-file.build] 17 | command = "cargo build --target wasm32-wasip1 --release" 18 | watch = ["src/**/*.rs", "Cargo.toml"] 19 | -------------------------------------------------------------------------------- /wit/deps/spin@unversioned/mysql.wit: -------------------------------------------------------------------------------- 1 | interface mysql { 2 | use rdbms-types.{parameter-value, row-set}; 3 | 4 | // General purpose error. 5 | variant mysql-error { 6 | success, 7 | connection-failed(string), 8 | bad-parameter(string), 9 | query-failed(string), 10 | value-conversion-failed(string), 11 | other-error(string) 12 | } 13 | 14 | // query the database: select 15 | query: func(address: string, statement: string, params: list) -> result; 16 | 17 | // execute command to the database: insert, update, delete 18 | execute: func(address: string, statement: string, params: list) -> result<_, mysql-error>; 19 | } 20 | -------------------------------------------------------------------------------- /wit/deps/spin@unversioned/postgres.wit: -------------------------------------------------------------------------------- 1 | interface postgres { 2 | use rdbms-types.{parameter-value, row-set}; 3 | 4 | // General purpose error. 5 | variant pg-error { 6 | success, 7 | connection-failed(string), 8 | bad-parameter(string), 9 | query-failed(string), 10 | value-conversion-failed(string), 11 | other-error(string) 12 | } 13 | 14 | // query the database: select 15 | query: func(address: string, statement: string, params: list) -> result; 16 | 17 | // execute command to the database: insert, update, delete 18 | execute: func(address: string, statement: string, params: list) -> result; 19 | } 20 | -------------------------------------------------------------------------------- /examples/wasip3-streaming/spin.toml: -------------------------------------------------------------------------------- 1 | #:schema https://schemas.spinframework.dev/spin/manifest-v2/latest.json 2 | 3 | spin_manifest_version = 2 4 | 5 | [application] 6 | name = "wasip3-streaming" 7 | version = "0.1.0" 8 | authors = ["The Spin authors"] 9 | description = "Demonstrates streaming processing of requests and responses" 10 | 11 | [[trigger.http]] 12 | route = "/..." 13 | component = "wasip3-streaming" 14 | executor = { type = "wasip3-unstable" } 15 | 16 | [component.wasip3-streaming] 17 | source = "target/wasm32-wasip2/release/wasip3_streaming.wasm" 18 | allowed_outbound_hosts = [] 19 | [component.wasip3-streaming.build] 20 | command = "cargo build --target wasm32-wasip2 --release" 21 | watch = ["src/**/*.rs", "Cargo.toml"] 22 | -------------------------------------------------------------------------------- /examples/postgres/db/testdata.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE articletest ( 2 | id integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, 3 | title varchar(40) NOT NULL, 4 | content text NOT NULL, 5 | authorname varchar(40) NOT NULL, 6 | coauthor text, 7 | authorage real, 8 | authorheight double precision 9 | ); 10 | 11 | INSERT INTO articletest (title, content, authorname, authorage, authorheight) VALUES 12 | ( 13 | 'My Life as a Goat', 14 | 'I went to Nepal to live as a goat, and it was much better than being a butler.', 15 | 'E. Blackadder', 16 | '22.1', 17 | '72.23' 18 | ), 19 | ( 20 | 'Magnificent Octopus', 21 | 'Once upon a time there was a lovely little sausage.', 22 | 'S. Baldrick', 23 | '11.2', 24 | '50.48' 25 | ); 26 | -------------------------------------------------------------------------------- /wit/deps/spin@2.0.0/variables.wit: -------------------------------------------------------------------------------- 1 | interface variables { 2 | /// Get an application variable value for the current component. 3 | /// 4 | /// The name must match one defined in in the component manifest. 5 | get: func(name: string) -> result; 6 | 7 | /// The set of errors which may be raised by functions in this interface. 8 | variant error { 9 | /// The provided variable name is invalid. 10 | invalid-name(string), 11 | /// The provided variable is undefined. 12 | undefined(string), 13 | /// A variables provider specific error has occurred. 14 | provider(string), 15 | /// Some implementation-specific error has occurred. 16 | other(string), 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/variables/spin.toml: -------------------------------------------------------------------------------- 1 | spin_manifest_version = 2 2 | 3 | [application] 4 | authors = ["The Spin authors"] 5 | description = "A Spin Rust application demonstrating the variables sdk." 6 | name = "spin-variables-rust" 7 | version = "0.1.0" 8 | 9 | [variables] 10 | object = { default = "teapot" } 11 | dotenv = { default = "should-be-replaced" } 12 | 13 | [[trigger.http]] 14 | route = "/..." 15 | component = "spin-variables-rust" 16 | 17 | [component.spin-variables-rust] 18 | source = "../../target/wasm32-wasip1/release/spin_variables_example.wasm" 19 | [component.spin-variables-rust.variables] 20 | message = "I'm a {{object}}" 21 | dotenv = "{{dotenv}}" 22 | [component.spin-variables-rust.build] 23 | command = "cargo build --target wasm32-wasip1 --release" 24 | -------------------------------------------------------------------------------- /wit/deps/keyvalue-2024-10-17/watch.wit: -------------------------------------------------------------------------------- 1 | /// A keyvalue interface that provides watch operations. 2 | /// 3 | /// This interface is used to provide event-driven mechanisms to handle 4 | /// keyvalue changes. 5 | interface watcher { 6 | /// A keyvalue interface that provides handle-watch operations. 7 | use store.{bucket}; 8 | 9 | /// Handle the `set` event for the given bucket and key. It includes a reference to the `bucket` 10 | /// that can be used to interact with the store. 11 | on-set: func(bucket: bucket, key: string, value: list); 12 | 13 | /// Handle the `delete` event for the given bucket and key. It includes a reference to the 14 | /// `bucket` that can be used to interact with the store. 15 | on-delete: func(bucket: bucket, key: string); 16 | } 17 | -------------------------------------------------------------------------------- /examples/wasip3-http-send-request/spin.toml: -------------------------------------------------------------------------------- 1 | #:schema https://schemas.spinframework.dev/spin/manifest-v2/latest.json 2 | 3 | spin_manifest_version = 2 4 | 5 | [application] 6 | authors = ["The Spin authors"] 7 | description = "An application that sends an HTTP request" 8 | name = "send-request" 9 | version = "1.0.0" 10 | 11 | [[trigger.http]] 12 | route = "/..." 13 | component = "send" 14 | executor = { type = "wasip3-unstable" } 15 | 16 | [component.send] 17 | source = "../../target/wasm32-wasip2/release/send_request.wasm" 18 | description = "A component that sends a request." 19 | allowed_outbound_hosts = [ 20 | "https://bytecodealliance.org", 21 | ] 22 | [component.send.build] 23 | command = "cargo build --target wasm32-wasip2 --release" 24 | watch = ["src/**/*.rs", "Cargo.toml"] -------------------------------------------------------------------------------- /examples/key-value/README.md: -------------------------------------------------------------------------------- 1 | # Spin Key Value component in Rust 2 | 3 | ```shell 4 | $ RUST_LOG=spin=trace spin build --up 5 | ``` 6 | 7 | The application can now receive requests on `http://localhost:3000`: 8 | 9 | ```shell 10 | $ curl -i -X POST -d "ok!" localhost:3000/test 11 | HTTP/1.1 200 OK 12 | content-length: 0 13 | date: Tue, 25 Apr 2023 14:25:43 GMT 14 | 15 | $ curl -i -X GET localhost:3000/test 16 | HTTP/1.1 200 OK 17 | content-length: 3 18 | date: Tue, 25 Apr 2023 14:25:54 GMT 19 | 20 | ok! 21 | 22 | $ curl -i -X DELETE localhost:3000/test 23 | HTTP/1.1 200 OK 24 | content-length: 0 25 | date: Tue, 25 Apr 2023 14:26:30 GMT 26 | 27 | $ curl -i -X GET localhost:3000/test 28 | HTTP/1.1 404 Not Found 29 | content-length: 0 30 | date: Tue, 25 Apr 2023 14:31:53 GMT 31 | ``` 32 | -------------------------------------------------------------------------------- /examples/postgres-v3/db/testdata.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE articletest ( 2 | id integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, 3 | title varchar(40) NOT NULL, 4 | content text NOT NULL, 5 | authorname varchar(40) NOT NULL , 6 | publisheddate date NOT NULL, 7 | publishedtime time, 8 | publisheddatetime timestamp, 9 | readtime bigint, 10 | coauthor text 11 | ); 12 | 13 | INSERT INTO articletest (title, content, authorname, publisheddate) VALUES 14 | ( 15 | 'My Life as a Goat', 16 | 'I went to Nepal to live as a goat, and it was much better than being a butler.', 17 | 'E. Blackadder', 18 | '2024-11-05' 19 | ), 20 | ( 21 | 'Magnificent Octopus', 22 | 'Once upon a time there was a lovely little sausage.', 23 | 'S. Baldrick', 24 | '2024-11-06' 25 | ); 26 | -------------------------------------------------------------------------------- /wit/deps/cli/environment.wit: -------------------------------------------------------------------------------- 1 | interface environment { 2 | /// Get the POSIX-style environment variables. 3 | /// 4 | /// Each environment variable is provided as a pair of string variable names 5 | /// and string value. 6 | /// 7 | /// Morally, these are a value import, but until value imports are available 8 | /// in the component model, this import function should return the same 9 | /// values each time it is called. 10 | get-environment: func() -> list>; 11 | 12 | /// Get the POSIX-style arguments to the program. 13 | get-arguments: func() -> list; 14 | 15 | /// Return a path that programs should use as their initial current working 16 | /// directory, interpreting `.` as shorthand for this. 17 | initial-cwd: func() -> option; 18 | } 19 | -------------------------------------------------------------------------------- /crates/macro/wit/deps/cli/environment.wit: -------------------------------------------------------------------------------- 1 | interface environment { 2 | /// Get the POSIX-style environment variables. 3 | /// 4 | /// Each environment variable is provided as a pair of string variable names 5 | /// and string value. 6 | /// 7 | /// Morally, these are a value import, but until value imports are available 8 | /// in the component model, this import function should return the same 9 | /// values each time it is called. 10 | get-environment: func() -> list>; 11 | 12 | /// Get the POSIX-style arguments to the program. 13 | get-arguments: func() -> list; 14 | 15 | /// Return a path that programs should use as their initial current working 16 | /// directory, interpreting `.` as shorthand for this. 17 | initial-cwd: func() -> option; 18 | } 19 | -------------------------------------------------------------------------------- /wit/deps/cli-2023-10-18/environment.wit: -------------------------------------------------------------------------------- 1 | interface environment { 2 | /// Get the POSIX-style environment variables. 3 | /// 4 | /// Each environment variable is provided as a pair of string variable names 5 | /// and string value. 6 | /// 7 | /// Morally, these are a value import, but until value imports are available 8 | /// in the component model, this import function should return the same 9 | /// values each time it is called. 10 | get-environment: func() -> list>; 11 | 12 | /// Get the POSIX-style arguments to the program. 13 | get-arguments: func() -> list; 14 | 15 | /// Return a path that programs should use as their initial current working 16 | /// directory, interpreting `.` as shorthand for this. 17 | initial-cwd: func() -> option; 18 | } 19 | -------------------------------------------------------------------------------- /wit/deps/cli-2023-11-10/environment.wit: -------------------------------------------------------------------------------- 1 | interface environment { 2 | /// Get the POSIX-style environment variables. 3 | /// 4 | /// Each environment variable is provided as a pair of string variable names 5 | /// and string value. 6 | /// 7 | /// Morally, these are a value import, but until value imports are available 8 | /// in the component model, this import function should return the same 9 | /// values each time it is called. 10 | get-environment: func() -> list>; 11 | 12 | /// Get the POSIX-style arguments to the program. 13 | get-arguments: func() -> list; 14 | 15 | /// Return a path that programs should use as their initial current working 16 | /// directory, interpreting `.` as shorthand for this. 17 | initial-cwd: func() -> option; 18 | } 19 | -------------------------------------------------------------------------------- /.github/workflows/build-examples.yml: -------------------------------------------------------------------------------- 1 | name: Build Examples 2 | 3 | on: 4 | push: 5 | branches: ["main", "v*"] 6 | tags: ["v*"] 7 | pull_request: 8 | branches: ["main", "v*"] 9 | paths-ignore: 10 | - "README.md" 11 | 12 | env: 13 | RUST_VERSION: "1.86" 14 | SPIN_VERSION: "" 15 | 16 | jobs: 17 | examples: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@v4 21 | - name: Install Rust 22 | uses: dtolnay/rust-toolchain@stable 23 | with: 24 | toolchain: "${{ env.RUST_VERSION }}" 25 | targets: wasm32-wasip1 26 | - name: Install Spin 27 | uses: fermyon/actions/spin/setup@v1 28 | - name: Run build_examples.sh 29 | run: | 30 | chmod +x scripts/build_examples.sh 31 | scripts/build_examples.sh 32 | -------------------------------------------------------------------------------- /examples/mqtt-outbound/spin.toml: -------------------------------------------------------------------------------- 1 | spin_manifest_version = 2 2 | 3 | [application] 4 | authors = ["Suneet Nangia "] 5 | name = "rust-outbound-mqtt-example" 6 | version = "0.1.0" 7 | 8 | [[trigger.http]] 9 | route = "/publish" 10 | component = "outbound-mqtt" 11 | 12 | [component.outbound-mqtt] 13 | # To test anonymous MQTT authentication, remove the values from MQTT_USERNAME and MQTT_PASSWORD env variables. 14 | environment = { MQTT_ADDRESS = "mqtt://127.0.0.1:1883?client_id=client001", MQTT_USERNAME = "user", MQTT_PASSWORD = "password", MQTT_KEEP_ALIVE_INTERVAL = "30", MQTT_TOPIC = "telemetry" } 15 | source = "../../target/wasm32-wasip1/release/rust_outbound_mqtt.wasm" 16 | allowed_outbound_hosts = ["mqtt://127.0.0.1:1883"] 17 | [component.outbound-mqtt.build] 18 | command = "cargo build --target wasm32-wasip1 --release" 19 | -------------------------------------------------------------------------------- /examples/wasip3-concurrent-outbound-http-calls/spin.toml: -------------------------------------------------------------------------------- 1 | #:schema https://schemas.spinframework.dev/spin/manifest-v2/latest.json 2 | 3 | spin_manifest_version = 2 4 | 5 | [application] 6 | name = "concurrent-outbound-http-calls" 7 | version = "0.1.0" 8 | authors = ["The Spin project"] 9 | description = "Demonstrates making concurrent outbound HTTP calls in WASIp3" 10 | 11 | [[trigger.http]] 12 | route = "/..." 13 | component = "concurrent-outbound-http-calls" 14 | executor = { type = "wasip3-unstable" } 15 | 16 | [component.concurrent-outbound-http-calls] 17 | source = "target/wasm32-wasip2/release/concurrent_outbound_http_calls.wasm" 18 | allowed_outbound_hosts = ["https://spinframework.dev", "https://component-model.bytecodealliance.org/"] 19 | [component.concurrent-outbound-http-calls.build] 20 | command = "cargo build --target wasm32-wasip2 --release" 21 | watch = ["src/**/*.rs", "Cargo.toml"] 22 | -------------------------------------------------------------------------------- /examples/http-router-macro/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code, unused_imports)] 2 | use spin_sdk::{ 3 | http::{IntoResponse, Params, Request, Response}, 4 | http_component, http_router, 5 | }; 6 | 7 | #[http_component] 8 | fn handle_route(req: Request) -> impl IntoResponse { 9 | let router = http_router! { 10 | GET "/hello/:planet" => api::hello_planet, 11 | _ "/*" => |_req: Request, params| { 12 | let capture = params.wildcard().unwrap_or_default(); 13 | Response::new(200, capture.to_string()) 14 | } 15 | }; 16 | router.handle(req) 17 | } 18 | 19 | mod api { 20 | use super::*; 21 | 22 | // /hello/:planet 23 | pub fn hello_planet(_req: Request, params: Params) -> anyhow::Result { 24 | let planet = params.get("planet").expect("PLANET"); 25 | 26 | Ok(Response::new(200, planet.to_string())) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /wit/deps/random-2023-10-18/insecure.wit: -------------------------------------------------------------------------------- 1 | /// The insecure interface for insecure pseudo-random numbers. 2 | /// 3 | /// It is intended to be portable at least between Unix-family platforms and 4 | /// Windows. 5 | interface insecure { 6 | /// Return `len` insecure pseudo-random bytes. 7 | /// 8 | /// This function is not cryptographically secure. Do not use it for 9 | /// anything related to security. 10 | /// 11 | /// There are no requirements on the values of the returned bytes, however 12 | /// implementations are encouraged to return evenly distributed values with 13 | /// a long period. 14 | get-insecure-random-bytes: func(len: u64) -> list; 15 | 16 | /// Return an insecure pseudo-random `u64` value. 17 | /// 18 | /// This function returns the same type of pseudo-random data as 19 | /// `get-insecure-random-bytes`, represented as a `u64`. 20 | get-insecure-random-u64: func() -> u64; 21 | } 22 | -------------------------------------------------------------------------------- /wit/deps/random/insecure.wit: -------------------------------------------------------------------------------- 1 | package wasi:random@0.2.0; 2 | /// The insecure interface for insecure pseudo-random numbers. 3 | /// 4 | /// It is intended to be portable at least between Unix-family platforms and 5 | /// Windows. 6 | interface insecure { 7 | /// Return `len` insecure pseudo-random bytes. 8 | /// 9 | /// This function is not cryptographically secure. Do not use it for 10 | /// anything related to security. 11 | /// 12 | /// There are no requirements on the values of the returned bytes, however 13 | /// implementations are encouraged to return evenly distributed values with 14 | /// a long period. 15 | get-insecure-random-bytes: func(len: u64) -> list; 16 | 17 | /// Return an insecure pseudo-random `u64` value. 18 | /// 19 | /// This function returns the same type of pseudo-random data as 20 | /// `get-insecure-random-bytes`, represented as a `u64`. 21 | get-insecure-random-u64: func() -> u64; 22 | } 23 | -------------------------------------------------------------------------------- /crates/macro/wit/deps/random/insecure.wit: -------------------------------------------------------------------------------- 1 | package wasi:random@0.2.0; 2 | /// The insecure interface for insecure pseudo-random numbers. 3 | /// 4 | /// It is intended to be portable at least between Unix-family platforms and 5 | /// Windows. 6 | interface insecure { 7 | /// Return `len` insecure pseudo-random bytes. 8 | /// 9 | /// This function is not cryptographically secure. Do not use it for 10 | /// anything related to security. 11 | /// 12 | /// There are no requirements on the values of the returned bytes, however 13 | /// implementations are encouraged to return evenly distributed values with 14 | /// a long period. 15 | get-insecure-random-bytes: func(len: u64) -> list; 16 | 17 | /// Return an insecure pseudo-random `u64` value. 18 | /// 19 | /// This function returns the same type of pseudo-random data as 20 | /// `get-insecure-random-bytes`, represented as a `u64`. 21 | get-insecure-random-u64: func() -> u64; 22 | } 23 | -------------------------------------------------------------------------------- /wit/deps/http-2023-10-18/outgoing-handler.wit: -------------------------------------------------------------------------------- 1 | // The `wasi:http/outgoing-handler` interface is meant to be imported by 2 | // components and implemented by the host. 3 | // 4 | // NOTE: in Preview3, this interface will be merged with 5 | // `wasi:http/outgoing-handler` into a single `wasi:http/handler` interface 6 | // that takes a `request` parameter and returns a `response` result. 7 | // 8 | interface outgoing-handler { 9 | use types.{outgoing-request, request-options, future-incoming-response, error}; 10 | 11 | // The parameter and result types of the `handle` function allow the caller 12 | // to concurrently stream the bodies of the outgoing request and the incoming 13 | // response. 14 | // Consumes the outgoing-request. Gives an error if the outgoing-request 15 | // is invalid or cannot be satisfied by this handler. 16 | handle: func( 17 | request: outgoing-request, 18 | options: option 19 | ) -> result; 20 | } 21 | -------------------------------------------------------------------------------- /wit/deps/random-2023-11-10/insecure.wit: -------------------------------------------------------------------------------- 1 | package wasi:random@0.2.0-rc-2023-11-10; 2 | /// The insecure interface for insecure pseudo-random numbers. 3 | /// 4 | /// It is intended to be portable at least between Unix-family platforms and 5 | /// Windows. 6 | interface insecure { 7 | /// Return `len` insecure pseudo-random bytes. 8 | /// 9 | /// This function is not cryptographically secure. Do not use it for 10 | /// anything related to security. 11 | /// 12 | /// There are no requirements on the values of the returned bytes, however 13 | /// implementations are encouraged to return evenly distributed values with 14 | /// a long period. 15 | get-insecure-random-bytes: func(len: u64) -> list; 16 | 17 | /// Return an insecure pseudo-random `u64` value. 18 | /// 19 | /// This function returns the same type of pseudo-random data as 20 | /// `get-insecure-random-bytes`, represented as a `u64`. 21 | get-insecure-random-u64: func() -> u64; 22 | } 23 | -------------------------------------------------------------------------------- /wit/deps/spin@unversioned/http-types.wit: -------------------------------------------------------------------------------- 1 | interface http-types { 2 | type http-status = u16; 3 | 4 | type body = list; 5 | 6 | type headers = list>; 7 | 8 | type params = list>; 9 | 10 | type uri = string; 11 | 12 | enum method { 13 | get, 14 | post, 15 | put, 16 | delete, 17 | patch, 18 | head, 19 | options, 20 | } 21 | 22 | record request { 23 | method: method, 24 | uri: uri, 25 | headers: headers, 26 | params: params, 27 | body: option, 28 | } 29 | 30 | record response { 31 | status: http-status, 32 | headers: option, 33 | body: option, 34 | } 35 | 36 | enum http-error { 37 | success, 38 | destination-not-allowed, 39 | invalid-url, 40 | request-error, 41 | runtime-error, 42 | too-many-requests, 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /scripts/build_examples.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | EXAMPLES_DIR="examples" 4 | 5 | if [[ ! -d "$EXAMPLES_DIR" ]]; then 6 | echo "Directory $EXAMPLES_DIR does not exist." 7 | exit 1 8 | fi 9 | 10 | for example in "$EXAMPLES_DIR"/*; do 11 | if [[ -d "$example" ]]; then 12 | example_name=$(basename "$example") 13 | 14 | # tells GitHub Action to start a new collapsible log section 15 | echo "::group::Building example: $example_name" 16 | 17 | # cd into example app directory 18 | cd "$example" || { echo "Failed to change directory to $example"; continue; } 19 | 20 | build_output=$(spin build 2>&1) 21 | build_status=$? 22 | 23 | echo "$build_output" 24 | 25 | if [[ $build_status -eq 0 ]]; then 26 | echo "✅ spin build succeeded for $example_name" 27 | else 28 | echo "❌ spin build failed for $example_name" 29 | fi 30 | 31 | echo "::endgroup::" 32 | 33 | # return to the parent directory 34 | cd - >/dev/null 35 | fi 36 | done 37 | -------------------------------------------------------------------------------- /wit/deps/spin@2.0.0/mqtt.wit: -------------------------------------------------------------------------------- 1 | interface mqtt { 2 | /// Errors related to interacting with Mqtt 3 | variant error { 4 | /// An invalid address string 5 | invalid-address, 6 | /// There are too many open connections 7 | too-many-connections, 8 | /// Connection failure e.g. address not allowed. 9 | connection-failed(string), 10 | /// Some other error occurred 11 | other(string), 12 | } 13 | 14 | /// QoS for publishing Mqtt messages 15 | enum qos { 16 | at-most-once, 17 | at-least-once, 18 | exactly-once, 19 | } 20 | 21 | resource connection { 22 | /// Open a connection to the Mqtt instance at `address`. 23 | open: static func(address: string, username: string, password: string, keep-alive-interval-in-secs: u64) -> result; 24 | 25 | /// Publish an Mqtt message to the specified `topic`. 26 | publish: func(topic: string, payload: payload, qos: qos) -> result<_, error>; 27 | } 28 | 29 | /// The message payload. 30 | type payload = list; 31 | } 32 | -------------------------------------------------------------------------------- /wit/deps/keyvalue-2024-10-17/world.wit: -------------------------------------------------------------------------------- 1 | package wasi:keyvalue@0.2.0-draft2; 2 | 3 | /// The `wasi:keyvalue/imports` world provides common APIs for interacting with key-value stores. 4 | /// Components targeting this world will be able to do: 5 | /// 6 | /// 1. CRUD (create, read, update, delete) operations on key-value stores. 7 | /// 2. Atomic `increment` and CAS (compare-and-swap) operations. 8 | /// 3. Batch operations that can reduce the number of round trips to the network. 9 | world imports { 10 | /// The `store` capability allows the component to perform eventually consistent operations on 11 | /// the key-value store. 12 | import store; 13 | 14 | /// The `atomic` capability allows the component to perform atomic / `increment` and CAS 15 | /// (compare-and-swap) operations. 16 | import atomics; 17 | 18 | /// The `batch` capability allows the component to perform eventually consistent batch 19 | /// operations that can reduce the number of round trips to the network. 20 | import batch; 21 | } 22 | 23 | world watch-service { 24 | include imports; 25 | export watcher; 26 | } 27 | -------------------------------------------------------------------------------- /examples/postgres-v4/README.md: -------------------------------------------------------------------------------- 1 | # Spin Outbound PostgreSQL example 2 | 3 | This example shows how to access a PostgreSQL database from a Spin component. 4 | It shows the new PostgreSQL range support in the v4 interface. 5 | 6 | ## Prerequisite: Postgres 7 | 8 | This example assumes postgres is running and accessible locally via its standard 5432 port. 9 | 10 | We suggest running the `postgres` Docker container which has the necessary postgres user permissions 11 | already configured. For example: 12 | 13 | ``` 14 | docker run --rm -h 127.0.0.1 -p 5432:5432 -e POSTGRES_HOST_AUTH_METHOD=trust postgres 15 | ``` 16 | 17 | ## Spin up 18 | 19 | Then, run the following from the root of this example: 20 | 21 | ``` 22 | createdb -h localhost -U postgres spin_dev 23 | psql -h localhost -U postgres -d spin_dev -f db/testdata.sql 24 | spin build --up 25 | ``` 26 | 27 | Curl with a year between 2005 and today as the path: 28 | 29 | ``` 30 | $ curl -i localhost:3000/2016 31 | HTTP/1.1 200 OK 32 | transfer-encoding: chunked 33 | date: Mon, 18 Aug 2025 05:02:29 GMT 34 | 35 | Splodge and Fang and Kiki and Slats 36 | ``` 37 | -------------------------------------------------------------------------------- /wit/deps/clocks-2023-10-18/monotonic-clock.wit: -------------------------------------------------------------------------------- 1 | /// WASI Monotonic Clock is a clock API intended to let users measure elapsed 2 | /// time. 3 | /// 4 | /// It is intended to be portable at least between Unix-family platforms and 5 | /// Windows. 6 | /// 7 | /// A monotonic clock is a clock which has an unspecified initial value, and 8 | /// successive reads of the clock will produce non-decreasing values. 9 | /// 10 | /// It is intended for measuring elapsed time. 11 | interface monotonic-clock { 12 | use wasi:io/poll@0.2.0-rc-2023-10-18.{pollable}; 13 | 14 | /// A timestamp in nanoseconds. 15 | type instant = u64; 16 | 17 | /// Read the current value of the clock. 18 | /// 19 | /// The clock is monotonic, therefore calling this function repeatedly will 20 | /// produce a sequence of non-decreasing values. 21 | now: func() -> instant; 22 | 23 | /// Query the resolution of the clock. 24 | resolution: func() -> instant; 25 | 26 | /// Create a `pollable` which will resolve once the specified time has been 27 | /// reached. 28 | subscribe: func( 29 | when: instant, 30 | absolute: bool 31 | ) -> pollable; 32 | } 33 | -------------------------------------------------------------------------------- /wit/deps/spin@2.0.0/world.wit: -------------------------------------------------------------------------------- 1 | package fermyon:spin@2.0.0; 2 | 3 | /// The full world of a guest targeting an http-trigger 4 | world http-trigger { 5 | include platform; 6 | export wasi:http/incoming-handler@0.2.0; 7 | } 8 | 9 | /// Like `http-trigger`, but using WASI 0.2.0-rc-2023-10-18 10 | world http-trigger-rc20231018 { 11 | include platform-rc20231018; 12 | export wasi:http/incoming-handler@0.2.0-rc-2023-10-18; 13 | } 14 | 15 | /// The imports needed for a guest to run on a Spin host 16 | world platform { 17 | include wasi:cli/imports@0.2.0; 18 | import wasi:http/outgoing-handler@0.2.0; 19 | import llm; 20 | import redis; 21 | import mqtt; 22 | import postgres; 23 | import mysql; 24 | import sqlite; 25 | import key-value; 26 | import variables; 27 | } 28 | 29 | /// Like `platform`, but using WASI 0.2.0-rc-2023-10-18 30 | world platform-rc20231018 { 31 | include wasi:cli/reactor@0.2.0-rc-2023-10-18; 32 | import wasi:http/outgoing-handler@0.2.0-rc-2023-10-18; 33 | import llm; 34 | import redis; 35 | import mqtt; 36 | import postgres; 37 | import mysql; 38 | import sqlite; 39 | import key-value; 40 | import variables; 41 | } 42 | -------------------------------------------------------------------------------- /wit/deps/random-2023-10-18/insecure-seed.wit: -------------------------------------------------------------------------------- 1 | /// The insecure-seed interface for seeding hash-map DoS resistance. 2 | /// 3 | /// It is intended to be portable at least between Unix-family platforms and 4 | /// Windows. 5 | interface insecure-seed { 6 | /// Return a 128-bit value that may contain a pseudo-random value. 7 | /// 8 | /// The returned value is not required to be computed from a CSPRNG, and may 9 | /// even be entirely deterministic. Host implementations are encouraged to 10 | /// provide pseudo-random values to any program exposed to 11 | /// attacker-controlled content, to enable DoS protection built into many 12 | /// languages' hash-map implementations. 13 | /// 14 | /// This function is intended to only be called once, by a source language 15 | /// to initialize Denial Of Service (DoS) protection in its hash-map 16 | /// implementation. 17 | /// 18 | /// # Expected future evolution 19 | /// 20 | /// This will likely be changed to a value import, to prevent it from being 21 | /// called multiple times and potentially used for purposes other than DoS 22 | /// protection. 23 | insecure-seed: func() -> tuple; 24 | } 25 | -------------------------------------------------------------------------------- /wit/deps/random/insecure-seed.wit: -------------------------------------------------------------------------------- 1 | package wasi:random@0.2.0; 2 | /// The insecure-seed interface for seeding hash-map DoS resistance. 3 | /// 4 | /// It is intended to be portable at least between Unix-family platforms and 5 | /// Windows. 6 | interface insecure-seed { 7 | /// Return a 128-bit value that may contain a pseudo-random value. 8 | /// 9 | /// The returned value is not required to be computed from a CSPRNG, and may 10 | /// even be entirely deterministic. Host implementations are encouraged to 11 | /// provide pseudo-random values to any program exposed to 12 | /// attacker-controlled content, to enable DoS protection built into many 13 | /// languages' hash-map implementations. 14 | /// 15 | /// This function is intended to only be called once, by a source language 16 | /// to initialize Denial Of Service (DoS) protection in its hash-map 17 | /// implementation. 18 | /// 19 | /// # Expected future evolution 20 | /// 21 | /// This will likely be changed to a value import, to prevent it from being 22 | /// called multiple times and potentially used for purposes other than DoS 23 | /// protection. 24 | insecure-seed: func() -> tuple; 25 | } 26 | -------------------------------------------------------------------------------- /crates/macro/wit/deps/random/insecure-seed.wit: -------------------------------------------------------------------------------- 1 | package wasi:random@0.2.0; 2 | /// The insecure-seed interface for seeding hash-map DoS resistance. 3 | /// 4 | /// It is intended to be portable at least between Unix-family platforms and 5 | /// Windows. 6 | interface insecure-seed { 7 | /// Return a 128-bit value that may contain a pseudo-random value. 8 | /// 9 | /// The returned value is not required to be computed from a CSPRNG, and may 10 | /// even be entirely deterministic. Host implementations are encouraged to 11 | /// provide pseudo-random values to any program exposed to 12 | /// attacker-controlled content, to enable DoS protection built into many 13 | /// languages' hash-map implementations. 14 | /// 15 | /// This function is intended to only be called once, by a source language 16 | /// to initialize Denial Of Service (DoS) protection in its hash-map 17 | /// implementation. 18 | /// 19 | /// # Expected future evolution 20 | /// 21 | /// This will likely be changed to a value import, to prevent it from being 22 | /// called multiple times and potentially used for purposes other than DoS 23 | /// protection. 24 | insecure-seed: func() -> tuple; 25 | } 26 | -------------------------------------------------------------------------------- /wit/deps/http-2023-10-18/incoming-handler.wit: -------------------------------------------------------------------------------- 1 | // The `wasi:http/incoming-handler` interface is meant to be exported by 2 | // components and called by the host in response to a new incoming HTTP 3 | // response. 4 | // 5 | // NOTE: in Preview3, this interface will be merged with 6 | // `wasi:http/outgoing-handler` into a single `wasi:http/handler` interface 7 | // that takes a `request` parameter and returns a `response` result. 8 | // 9 | interface incoming-handler { 10 | use types.{incoming-request, response-outparam}; 11 | 12 | // The `handle` function takes an outparam instead of returning its response 13 | // so that the component may stream its response while streaming any other 14 | // request or response bodies. The callee MUST write a response to the 15 | // `response-outparam` and then finish the response before returning. The `handle` 16 | // function is allowed to continue execution after finishing the response's 17 | // output stream. While this post-response execution is taken off the 18 | // critical path, since there is no return value, there is no way to report 19 | // its success or failure. 20 | handle: func( 21 | request: incoming-request, 22 | response-out: response-outparam 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /wit/deps/random-2023-10-18/random.wit: -------------------------------------------------------------------------------- 1 | /// WASI Random is a random data API. 2 | /// 3 | /// It is intended to be portable at least between Unix-family platforms and 4 | /// Windows. 5 | interface random { 6 | /// Return `len` cryptographically-secure random or pseudo-random bytes. 7 | /// 8 | /// This function must produce data at least as cryptographically secure and 9 | /// fast as an adequately seeded cryptographically-secure pseudo-random 10 | /// number generator (CSPRNG). It must not block, from the perspective of 11 | /// the calling program, under any circumstances, including on the first 12 | /// request and on requests for numbers of bytes. The returned data must 13 | /// always be unpredictable. 14 | /// 15 | /// This function must always return fresh data. Deterministic environments 16 | /// must omit this function, rather than implementing it with deterministic 17 | /// data. 18 | get-random-bytes: func(len: u64) -> list; 19 | 20 | /// Return a cryptographically-secure random or pseudo-random `u64` value. 21 | /// 22 | /// This function returns the same type of data as `get-random-bytes`, 23 | /// represented as a `u64`. 24 | get-random-u64: func() -> u64; 25 | } 26 | -------------------------------------------------------------------------------- /wit/deps/random-2023-11-10/insecure-seed.wit: -------------------------------------------------------------------------------- 1 | package wasi:random@0.2.0-rc-2023-11-10; 2 | /// The insecure-seed interface for seeding hash-map DoS resistance. 3 | /// 4 | /// It is intended to be portable at least between Unix-family platforms and 5 | /// Windows. 6 | interface insecure-seed { 7 | /// Return a 128-bit value that may contain a pseudo-random value. 8 | /// 9 | /// The returned value is not required to be computed from a CSPRNG, and may 10 | /// even be entirely deterministic. Host implementations are encouraged to 11 | /// provide pseudo-random values to any program exposed to 12 | /// attacker-controlled content, to enable DoS protection built into many 13 | /// languages' hash-map implementations. 14 | /// 15 | /// This function is intended to only be called once, by a source language 16 | /// to initialize Denial Of Service (DoS) protection in its hash-map 17 | /// implementation. 18 | /// 19 | /// # Expected future evolution 20 | /// 21 | /// This will likely be changed to a value import, to prevent it from being 22 | /// called multiple times and potentially used for purposes other than DoS 23 | /// protection. 24 | insecure-seed: func() -> tuple; 25 | } 26 | -------------------------------------------------------------------------------- /wit/deps/random/random.wit: -------------------------------------------------------------------------------- 1 | package wasi:random@0.2.0; 2 | /// WASI Random is a random data API. 3 | /// 4 | /// It is intended to be portable at least between Unix-family platforms and 5 | /// Windows. 6 | interface random { 7 | /// Return `len` cryptographically-secure random or pseudo-random bytes. 8 | /// 9 | /// This function must produce data at least as cryptographically secure and 10 | /// fast as an adequately seeded cryptographically-secure pseudo-random 11 | /// number generator (CSPRNG). It must not block, from the perspective of 12 | /// the calling program, under any circumstances, including on the first 13 | /// request and on requests for numbers of bytes. The returned data must 14 | /// always be unpredictable. 15 | /// 16 | /// This function must always return fresh data. Deterministic environments 17 | /// must omit this function, rather than implementing it with deterministic 18 | /// data. 19 | get-random-bytes: func(len: u64) -> list; 20 | 21 | /// Return a cryptographically-secure random or pseudo-random `u64` value. 22 | /// 23 | /// This function returns the same type of data as `get-random-bytes`, 24 | /// represented as a `u64`. 25 | get-random-u64: func() -> u64; 26 | } 27 | -------------------------------------------------------------------------------- /wit/deps/cli-2023-11-10/reactor.wit: -------------------------------------------------------------------------------- 1 | package wasi:cli@0.2.0-rc-2023-11-10; 2 | 3 | world reactor { 4 | import wasi:clocks/wall-clock@0.2.0-rc-2023-11-10; 5 | import wasi:clocks/monotonic-clock@0.2.0-rc-2023-11-10; 6 | import wasi:filesystem/types@0.2.0-rc-2023-11-10; 7 | import wasi:filesystem/preopens@0.2.0-rc-2023-11-10; 8 | import wasi:sockets/instance-network@0.2.0-rc-2023-11-10; 9 | import wasi:sockets/ip-name-lookup@0.2.0-rc-2023-11-10; 10 | import wasi:sockets/network@0.2.0-rc-2023-11-10; 11 | import wasi:sockets/tcp-create-socket@0.2.0-rc-2023-11-10; 12 | import wasi:sockets/tcp@0.2.0-rc-2023-11-10; 13 | import wasi:sockets/udp-create-socket@0.2.0-rc-2023-11-10; 14 | import wasi:sockets/udp@0.2.0-rc-2023-11-10; 15 | import wasi:random/random@0.2.0-rc-2023-11-10; 16 | import wasi:random/insecure@0.2.0-rc-2023-11-10; 17 | import wasi:random/insecure-seed@0.2.0-rc-2023-11-10; 18 | import wasi:io/poll@0.2.0-rc-2023-11-10; 19 | import wasi:io/streams@0.2.0-rc-2023-11-10; 20 | 21 | import environment; 22 | import exit; 23 | import stdin; 24 | import stdout; 25 | import stderr; 26 | import terminal-input; 27 | import terminal-output; 28 | import terminal-stdin; 29 | import terminal-stdout; 30 | import terminal-stderr; 31 | } 32 | -------------------------------------------------------------------------------- /crates/macro/wit/deps/random/random.wit: -------------------------------------------------------------------------------- 1 | package wasi:random@0.2.0; 2 | /// WASI Random is a random data API. 3 | /// 4 | /// It is intended to be portable at least between Unix-family platforms and 5 | /// Windows. 6 | interface random { 7 | /// Return `len` cryptographically-secure random or pseudo-random bytes. 8 | /// 9 | /// This function must produce data at least as cryptographically secure and 10 | /// fast as an adequately seeded cryptographically-secure pseudo-random 11 | /// number generator (CSPRNG). It must not block, from the perspective of 12 | /// the calling program, under any circumstances, including on the first 13 | /// request and on requests for numbers of bytes. The returned data must 14 | /// always be unpredictable. 15 | /// 16 | /// This function must always return fresh data. Deterministic environments 17 | /// must omit this function, rather than implementing it with deterministic 18 | /// data. 19 | get-random-bytes: func(len: u64) -> list; 20 | 21 | /// Return a cryptographically-secure random or pseudo-random `u64` value. 22 | /// 23 | /// This function returns the same type of data as `get-random-bytes`, 24 | /// represented as a `u64`. 25 | get-random-u64: func() -> u64; 26 | } 27 | -------------------------------------------------------------------------------- /wit/deps/random-2023-11-10/random.wit: -------------------------------------------------------------------------------- 1 | package wasi:random@0.2.0-rc-2023-11-10; 2 | /// WASI Random is a random data API. 3 | /// 4 | /// It is intended to be portable at least between Unix-family platforms and 5 | /// Windows. 6 | interface random { 7 | /// Return `len` cryptographically-secure random or pseudo-random bytes. 8 | /// 9 | /// This function must produce data at least as cryptographically secure and 10 | /// fast as an adequately seeded cryptographically-secure pseudo-random 11 | /// number generator (CSPRNG). It must not block, from the perspective of 12 | /// the calling program, under any circumstances, including on the first 13 | /// request and on requests for numbers of bytes. The returned data must 14 | /// always be unpredictable. 15 | /// 16 | /// This function must always return fresh data. Deterministic environments 17 | /// must omit this function, rather than implementing it with deterministic 18 | /// data. 19 | get-random-bytes: func(len: u64) -> list; 20 | 21 | /// Return a cryptographically-secure random or pseudo-random `u64` value. 22 | /// 23 | /// This function returns the same type of data as `get-random-bytes`, 24 | /// represented as a `u64`. 25 | get-random-u64: func() -> u64; 26 | } 27 | -------------------------------------------------------------------------------- /examples/wasi-http-streaming-file/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::{fs::File, io::Read}; 2 | 3 | use anyhow::Result; 4 | use futures::SinkExt; 5 | use spin_sdk::{ 6 | http::{Headers, IncomingRequest, OutgoingResponse, ResponseOutparam}, 7 | http_component, 8 | }; 9 | 10 | const CHUNK_SIZE: usize = 1024 * 1024; // 1 MB 11 | 12 | #[http_component] 13 | async fn handler(req: IncomingRequest, res: ResponseOutparam) { 14 | stream_file(req, res).await.unwrap(); 15 | } 16 | 17 | async fn stream_file(_req: IncomingRequest, res: ResponseOutparam) -> Result<()> { 18 | let response = OutgoingResponse::new(Headers::from_list(&[( 19 | "content-type".to_string(), 20 | b"application/octet-stream".to_vec(), 21 | )])?); 22 | 23 | let mut body = response.take_body(); 24 | res.set(response); 25 | 26 | let mut file = File::open("target/wasm32-wasip1/release/spin_wasi_http_streaming_file.wasm")?; 27 | 28 | let mut buffer = vec![0; CHUNK_SIZE]; 29 | 30 | loop { 31 | let bytes_read = file.read(&mut buffer[..])?; 32 | if bytes_read == 0 { 33 | break; 34 | } 35 | 36 | let data = &buffer[..bytes_read]; 37 | body.send(data.to_vec()).await?; 38 | println!("sent {} bytes", data.len()); 39 | } 40 | Ok(()) 41 | } 42 | -------------------------------------------------------------------------------- /wit/deps/cli-2023-10-18/reactor.wit: -------------------------------------------------------------------------------- 1 | package wasi:cli@0.2.0-rc-2023-10-18; 2 | 3 | world reactor { 4 | import wasi:clocks/wall-clock@0.2.0-rc-2023-10-18; 5 | import wasi:clocks/monotonic-clock@0.2.0-rc-2023-10-18; 6 | import wasi:clocks/timezone@0.2.0-rc-2023-10-18; 7 | import wasi:filesystem/types@0.2.0-rc-2023-10-18; 8 | import wasi:filesystem/preopens@0.2.0-rc-2023-10-18; 9 | import wasi:sockets/instance-network@0.2.0-rc-2023-10-18; 10 | import wasi:sockets/ip-name-lookup@0.2.0-rc-2023-10-18; 11 | import wasi:sockets/network@0.2.0-rc-2023-10-18; 12 | import wasi:sockets/tcp-create-socket@0.2.0-rc-2023-10-18; 13 | import wasi:sockets/tcp@0.2.0-rc-2023-10-18; 14 | import wasi:sockets/udp-create-socket@0.2.0-rc-2023-10-18; 15 | import wasi:sockets/udp@0.2.0-rc-2023-10-18; 16 | import wasi:random/random@0.2.0-rc-2023-10-18; 17 | import wasi:random/insecure@0.2.0-rc-2023-10-18; 18 | import wasi:random/insecure-seed@0.2.0-rc-2023-10-18; 19 | import wasi:io/poll@0.2.0-rc-2023-10-18; 20 | import wasi:io/streams@0.2.0-rc-2023-10-18; 21 | 22 | import environment; 23 | import exit; 24 | import stdin; 25 | import stdout; 26 | import stderr; 27 | import terminal-input; 28 | import terminal-output; 29 | import terminal-stdin; 30 | import terminal-stdout; 31 | import terminal-stderr; 32 | } 33 | -------------------------------------------------------------------------------- /examples/http-router/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use spin_sdk::{ 3 | http::{IntoResponse, Params, Request, Response, Router}, 4 | http_component, 5 | }; 6 | 7 | /// A Spin HTTP component that internally routes requests. 8 | #[http_component] 9 | fn handle_route(req: Request) -> Response { 10 | let mut router = Router::new(); 11 | router.get("/goodbye/:planet", api::goodbye_planet); 12 | router.get_async("/hello/:planet", api::hello_planet); 13 | router.any_async("/*", api::echo_wildcard); 14 | router.handle(req) 15 | } 16 | 17 | mod api { 18 | use super::*; 19 | 20 | // /goodbye/:planet 21 | pub fn goodbye_planet(_req: Request, params: Params) -> Result { 22 | let planet = params.get("planet").expect("PLANET"); 23 | Ok(Response::new(200, planet.to_string())) 24 | } 25 | 26 | // /hello/:planet 27 | pub async fn hello_planet(_req: Request, params: Params) -> Result { 28 | let planet = params.get("planet").expect("PLANET"); 29 | Ok(Response::new(200, planet.to_string())) 30 | } 31 | 32 | // /* 33 | pub async fn echo_wildcard(_req: Request, params: Params) -> Result { 34 | let capture = params.wildcard().unwrap_or_default(); 35 | Ok(Response::new(200, capture.to_string())) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /wit/deps/spin@unversioned/rdbms-types.wit: -------------------------------------------------------------------------------- 1 | interface rdbms-types { 2 | enum db-data-type { 3 | boolean, 4 | int8, 5 | int16, 6 | int32, 7 | int64, 8 | uint8, 9 | uint16, 10 | uint32, 11 | uint64, 12 | floating32, 13 | floating64, 14 | str, 15 | binary, 16 | other, 17 | } 18 | 19 | variant db-value { 20 | boolean(bool), 21 | int8(s8), 22 | int16(s16), 23 | int32(s32), 24 | int64(s64), 25 | uint8(u8), 26 | uint16(u16), 27 | uint32(u32), 28 | uint64(u64), 29 | floating32(f32), 30 | floating64(f64), 31 | str(string), 32 | binary(list), 33 | db-null, 34 | unsupported, 35 | } 36 | 37 | variant parameter-value { 38 | boolean(bool), 39 | int8(s8), 40 | int16(s16), 41 | int32(s32), 42 | int64(s64), 43 | uint8(u8), 44 | uint16(u16), 45 | uint32(u32), 46 | uint64(u64), 47 | floating32(f32), 48 | floating64(f64), 49 | str(string), 50 | binary(list), 51 | db-null, 52 | } 53 | 54 | record column { 55 | name: string, 56 | data-type: db-data-type, 57 | } 58 | 59 | type row = list; 60 | 61 | record row-set { 62 | columns: list, 63 | rows: list, 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /examples/wasip3-http-axum-router/src/lib.rs: -------------------------------------------------------------------------------- 1 | use axum::{ 2 | http::StatusCode, 3 | routing::{get, post}, 4 | Json, Router, 5 | }; 6 | use serde::{Deserialize, Serialize}; 7 | use spin_sdk::http_wasip3::{http_service, IntoResponse, Request}; 8 | use tower_service::Service; 9 | 10 | /// Demonstrates integration with the Axum web framework 11 | #[http_service] 12 | async fn handler(req: Request) -> impl IntoResponse { 13 | Router::new() 14 | .route("/", get(root)) 15 | .route("/users", post(create_user)) 16 | .call(req) 17 | .await 18 | } 19 | 20 | async fn root() -> &'static str { 21 | "hello, world!" 22 | } 23 | 24 | async fn create_user( 25 | // this argument tells axum to parse the request body 26 | // as JSON into a `CreateUser` type 27 | Json(payload): Json, 28 | ) -> (StatusCode, Json) { 29 | // insert your application logic here 30 | let user = User { 31 | id: 1337, 32 | username: payload.username, 33 | }; 34 | 35 | // this will be converted into a JSON response 36 | // with a status code of `201 Created` 37 | (StatusCode::CREATED, Json(user)) 38 | } 39 | 40 | // the input to our `create_user` handler 41 | #[derive(Deserialize)] 42 | struct CreateUser { 43 | username: String, 44 | } 45 | 46 | // the output to our `create_user` handler 47 | #[derive(Serialize)] 48 | struct User { 49 | id: u64, 50 | username: String, 51 | } 52 | -------------------------------------------------------------------------------- /examples/postgres-v4/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | use anyhow::Result; 3 | use http::{Request, Response}; 4 | use spin_sdk::{http_component, pg4}; 5 | 6 | // The environment variable set in `spin.toml` that points to the 7 | // address of the Pg server that the component will write to 8 | const DB_URL_ENV: &str = "DB_URL"; 9 | 10 | #[http_component] 11 | fn process(req: Request<()>) -> Result> { 12 | let address = std::env::var(DB_URL_ENV)?; 13 | let conn = pg4::Connection::open(&address)?; 14 | 15 | let year_header = req 16 | .headers() 17 | .get("spin-path-match-year") 18 | .map(|hv| hv.to_str()) 19 | .transpose()? 20 | .unwrap_or("2025"); 21 | let year: i32 = year_header.parse()?; 22 | 23 | // Due to an ambiguity in the PostgreSQL `<@` operator syntax, we MUST qualify 24 | // the year as an int4 rather than an int4range in the query. 25 | let rulers = conn.query( 26 | "SELECT name FROM cats WHERE $1::int4 <@ reign", 27 | &[year.into()], 28 | )?; 29 | 30 | let response = if rulers.rows.is_empty() { 31 | "it was anarchy".to_owned() 32 | } else { 33 | let ruler_names = rulers 34 | .rows() 35 | .map(|r| r.get::("name").unwrap()) 36 | .collect::>(); 37 | ruler_names.join(" and ") 38 | }; 39 | 40 | Ok(http::Response::builder().body(format!("{response}\n"))?) 41 | } 42 | -------------------------------------------------------------------------------- /wit/deps/sockets-2023-10-18/udp-create-socket.wit: -------------------------------------------------------------------------------- 1 | 2 | interface udp-create-socket { 3 | use network.{network, error-code, ip-address-family}; 4 | use udp.{udp-socket}; 5 | 6 | /// Create a new UDP socket. 7 | /// 8 | /// Similar to `socket(AF_INET or AF_INET6, SOCK_DGRAM, IPPROTO_UDP)` in POSIX. 9 | /// 10 | /// This function does not require a network capability handle. This is considered to be safe because 11 | /// at time of creation, the socket is not bound to any `network` yet. Up to the moment `bind`/`connect` is called, 12 | /// the socket is effectively an in-memory configuration object, unable to communicate with the outside world. 13 | /// 14 | /// All sockets are non-blocking. Use the wasi-poll interface to block on asynchronous operations. 15 | /// 16 | /// # Typical errors 17 | /// - `not-supported`: The specified `address-family` is not supported. (EAFNOSUPPORT) 18 | /// - `new-socket-limit`: The new socket resource could not be created because of a system limit. (EMFILE, ENFILE) 19 | /// 20 | /// # References: 21 | /// - 22 | /// - 23 | /// - 24 | /// - 25 | create-udp-socket: func(address-family: ip-address-family) -> result; 26 | } 27 | -------------------------------------------------------------------------------- /wit/deps/io-2023-10-18/poll.wit: -------------------------------------------------------------------------------- 1 | /// A poll API intended to let users wait for I/O events on multiple handles 2 | /// at once. 3 | interface poll { 4 | /// A "pollable" handle. 5 | resource pollable; 6 | 7 | /// Poll for completion on a set of pollables. 8 | /// 9 | /// This function takes a list of pollables, which identify I/O sources of 10 | /// interest, and waits until one or more of the events is ready for I/O. 11 | /// 12 | /// The result `list` contains one or more indices of handles in the 13 | /// argument list that is ready for I/O. 14 | /// 15 | /// If the list contains more elements than can be indexed with a `u32` 16 | /// value, this function traps. 17 | /// 18 | /// A timeout can be implemented by adding a pollable from the 19 | /// wasi-clocks API to the list. 20 | /// 21 | /// This function does not return a `result`; polling in itself does not 22 | /// do any I/O so it doesn't fail. If any of the I/O sources identified by 23 | /// the pollables has an error, it is indicated by marking the source as 24 | /// being reaedy for I/O. 25 | poll-list: func(in: list>) -> list; 26 | 27 | /// Poll for completion on a single pollable. 28 | /// 29 | /// This function is similar to `poll-list`, but operates on only a single 30 | /// pollable. When it returns, the handle is ready for I/O. 31 | poll-one: func(in: borrow); 32 | } 33 | -------------------------------------------------------------------------------- /wit/deps/sockets-2023-10-18/tcp-create-socket.wit: -------------------------------------------------------------------------------- 1 | 2 | interface tcp-create-socket { 3 | use network.{network, error-code, ip-address-family}; 4 | use tcp.{tcp-socket}; 5 | 6 | /// Create a new TCP socket. 7 | /// 8 | /// Similar to `socket(AF_INET or AF_INET6, SOCK_STREAM, IPPROTO_TCP)` in POSIX. 9 | /// 10 | /// This function does not require a network capability handle. This is considered to be safe because 11 | /// at time of creation, the socket is not bound to any `network` yet. Up to the moment `bind`/`listen`/`connect` 12 | /// is called, the socket is effectively an in-memory configuration object, unable to communicate with the outside world. 13 | /// 14 | /// All sockets are non-blocking. Use the wasi-poll interface to block on asynchronous operations. 15 | /// 16 | /// # Typical errors 17 | /// - `not-supported`: The specified `address-family` is not supported. (EAFNOSUPPORT) 18 | /// - `new-socket-limit`: The new socket resource could not be created because of a system limit. (EMFILE, ENFILE) 19 | /// 20 | /// # References 21 | /// - 22 | /// - 23 | /// - 24 | /// - 25 | create-tcp-socket: func(address-family: ip-address-family) -> result; 26 | } 27 | -------------------------------------------------------------------------------- /crates/spin-wasip3-http-macro/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use quote::quote; 3 | 4 | #[proc_macro_attribute] 5 | pub fn http_service(_attr: TokenStream, item: TokenStream) -> TokenStream { 6 | let func = syn::parse_macro_input!(item as syn::ItemFn); 7 | 8 | if func.sig.asyncness.is_none() { 9 | return syn::Error::new_spanned( 10 | func.sig.fn_token, 11 | "the `#[http_service]` function must be `async`", 12 | ) 13 | .to_compile_error() 14 | .into(); 15 | } 16 | 17 | let func_name = &func.sig.ident; 18 | 19 | quote!( 20 | #func 21 | mod __spin_wasip3_http { 22 | use ::spin_sdk::http_wasip3::IntoResponse; 23 | 24 | struct Spin; 25 | ::spin_sdk::http_wasip3::wasip3::http::proxy::export!(Spin); 26 | 27 | impl ::spin_sdk::http_wasip3::wasip3::exports::http::handler::Guest for self::Spin { 28 | async fn handle(request: ::spin_sdk::http_wasip3::wasip3::http::types::Request) -> Result<::spin_sdk::http_wasip3::wasip3::http::types::Response, ::spin_sdk::http_wasip3::wasip3::http::types::ErrorCode> { 29 | let request = <::spin_sdk::http_wasip3::Request as ::spin_sdk::http_wasip3::FromRequest>::from_request(request)?; 30 | ::spin_sdk::http_wasip3::IntoResponse::into_response(super::#func_name(request).await) 31 | } 32 | } 33 | } 34 | ) 35 | .into() 36 | } 37 | -------------------------------------------------------------------------------- /wit/deps/sockets-2023-11-10/udp-create-socket.wit: -------------------------------------------------------------------------------- 1 | 2 | interface udp-create-socket { 3 | use network.{network, error-code, ip-address-family}; 4 | use udp.{udp-socket}; 5 | 6 | /// Create a new UDP socket. 7 | /// 8 | /// Similar to `socket(AF_INET or AF_INET6, SOCK_DGRAM, IPPROTO_UDP)` in POSIX. 9 | /// 10 | /// This function does not require a network capability handle. This is considered to be safe because 11 | /// at time of creation, the socket is not bound to any `network` yet. Up to the moment `bind` is called, 12 | /// the socket is effectively an in-memory configuration object, unable to communicate with the outside world. 13 | /// 14 | /// All sockets are non-blocking. Use the wasi-poll interface to block on asynchronous operations. 15 | /// 16 | /// # Typical errors 17 | /// - `not-supported`: The specified `address-family` is not supported. (EAFNOSUPPORT) 18 | /// - `new-socket-limit`: The new socket resource could not be created because of a system limit. (EMFILE, ENFILE) 19 | /// 20 | /// # References: 21 | /// - 22 | /// - 23 | /// - 24 | /// - 25 | create-udp-socket: func(address-family: ip-address-family) -> result; 26 | } 27 | -------------------------------------------------------------------------------- /wit/deps/sockets-2023-11-10/tcp-create-socket.wit: -------------------------------------------------------------------------------- 1 | 2 | interface tcp-create-socket { 3 | use network.{network, error-code, ip-address-family}; 4 | use tcp.{tcp-socket}; 5 | 6 | /// Create a new TCP socket. 7 | /// 8 | /// Similar to `socket(AF_INET or AF_INET6, SOCK_STREAM, IPPROTO_TCP)` in POSIX. 9 | /// 10 | /// This function does not require a network capability handle. This is considered to be safe because 11 | /// at time of creation, the socket is not bound to any `network` yet. Up to the moment `bind`/`listen`/`connect` 12 | /// is called, the socket is effectively an in-memory configuration object, unable to communicate with the outside world. 13 | /// 14 | /// All sockets are non-blocking. Use the wasi-poll interface to block on asynchronous operations. 15 | /// 16 | /// # Typical errors 17 | /// - `not-supported`: The specified `address-family` is not supported. (EAFNOSUPPORT) 18 | /// - `new-socket-limit`: The new socket resource could not be created because of a system limit. (EMFILE, ENFILE) 19 | /// 20 | /// # References 21 | /// - 22 | /// - 23 | /// - 24 | /// - 25 | create-tcp-socket: func(address-family: ip-address-family) -> result; 26 | } 27 | -------------------------------------------------------------------------------- /wit/deps/http/proxy.wit: -------------------------------------------------------------------------------- 1 | package wasi:http@0.2.0; 2 | 3 | /// The `wasi:http/proxy` world captures a widely-implementable intersection of 4 | /// hosts that includes HTTP forward and reverse proxies. Components targeting 5 | /// this world may concurrently stream in and out any number of incoming and 6 | /// outgoing HTTP requests. 7 | world proxy { 8 | /// HTTP proxies have access to time and randomness. 9 | include wasi:clocks/imports@0.2.0; 10 | import wasi:random/random@0.2.0; 11 | 12 | /// Proxies have standard output and error streams which are expected to 13 | /// terminate in a developer-facing console provided by the host. 14 | import wasi:cli/stdout@0.2.0; 15 | import wasi:cli/stderr@0.2.0; 16 | 17 | /// TODO: this is a temporary workaround until component tooling is able to 18 | /// gracefully handle the absence of stdin. Hosts must return an eof stream 19 | /// for this import, which is what wasi-libc + tooling will do automatically 20 | /// when this import is properly removed. 21 | import wasi:cli/stdin@0.2.0; 22 | 23 | /// This is the default handler to use when user code simply wants to make an 24 | /// HTTP request (e.g., via `fetch()`). 25 | import outgoing-handler; 26 | 27 | /// The host delivers incoming HTTP requests to a component by calling the 28 | /// `handle` function of this exported interface. A host may arbitrarily reuse 29 | /// or not reuse component instance when delivering incoming HTTP requests and 30 | /// thus a component must be able to handle 0..N calls to `handle`. 31 | export incoming-handler; 32 | } 33 | -------------------------------------------------------------------------------- /crates/macro/wit/deps/http/proxy.wit: -------------------------------------------------------------------------------- 1 | package wasi:http@0.2.0; 2 | 3 | /// The `wasi:http/proxy` world captures a widely-implementable intersection of 4 | /// hosts that includes HTTP forward and reverse proxies. Components targeting 5 | /// this world may concurrently stream in and out any number of incoming and 6 | /// outgoing HTTP requests. 7 | world proxy { 8 | /// HTTP proxies have access to time and randomness. 9 | include wasi:clocks/imports@0.2.0; 10 | import wasi:random/random@0.2.0; 11 | 12 | /// Proxies have standard output and error streams which are expected to 13 | /// terminate in a developer-facing console provided by the host. 14 | import wasi:cli/stdout@0.2.0; 15 | import wasi:cli/stderr@0.2.0; 16 | 17 | /// TODO: this is a temporary workaround until component tooling is able to 18 | /// gracefully handle the absence of stdin. Hosts must return an eof stream 19 | /// for this import, which is what wasi-libc + tooling will do automatically 20 | /// when this import is properly removed. 21 | import wasi:cli/stdin@0.2.0; 22 | 23 | /// This is the default handler to use when user code simply wants to make an 24 | /// HTTP request (e.g., via `fetch()`). 25 | import outgoing-handler; 26 | 27 | /// The host delivers incoming HTTP requests to a component by calling the 28 | /// `handle` function of this exported interface. A host may arbitrarily reuse 29 | /// or not reuse component instance when delivering incoming HTTP requests and 30 | /// thus a component must be able to handle 0..N calls to `handle`. 31 | export incoming-handler; 32 | } 33 | -------------------------------------------------------------------------------- /wit/deps/io/error.wit: -------------------------------------------------------------------------------- 1 | package wasi:io@0.2.0; 2 | 3 | 4 | interface error { 5 | /// A resource which represents some error information. 6 | /// 7 | /// The only method provided by this resource is `to-debug-string`, 8 | /// which provides some human-readable information about the error. 9 | /// 10 | /// In the `wasi:io` package, this resource is returned through the 11 | /// `wasi:io/streams/stream-error` type. 12 | /// 13 | /// To provide more specific error information, other interfaces may 14 | /// provide functions to further "downcast" this error into more specific 15 | /// error information. For example, `error`s returned in streams derived 16 | /// from filesystem types to be described using the filesystem's own 17 | /// error-code type, using the function 18 | /// `wasi:filesystem/types/filesystem-error-code`, which takes a parameter 19 | /// `borrow` and returns 20 | /// `option`. 21 | /// 22 | /// The set of functions which can "downcast" an `error` into a more 23 | /// concrete type is open. 24 | resource error { 25 | /// Returns a string that is suitable to assist humans in debugging 26 | /// this error. 27 | /// 28 | /// WARNING: The returned string should not be consumed mechanically! 29 | /// It may change across platforms, hosts, or other implementation 30 | /// details. Parsing this string is a major platform-compatibility 31 | /// hazard. 32 | to-debug-string: func() -> string; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /wit/deps/wasi-runtime-config-2024-09-27/store.wit: -------------------------------------------------------------------------------- 1 | interface store { 2 | /// An error type that encapsulates the different errors that can occur fetching configuration values. 3 | variant error { 4 | /// This indicates an error from an "upstream" config source. 5 | /// As this could be almost _anything_ (such as Vault, Kubernetes ConfigMaps, KeyValue buckets, etc), 6 | /// the error message is a string. 7 | upstream(string), 8 | /// This indicates an error from an I/O operation. 9 | /// As this could be almost _anything_ (such as a file read, network connection, etc), 10 | /// the error message is a string. 11 | /// Depending on how this ends up being consumed, 12 | /// we may consider moving this to use the `wasi:io/error` type instead. 13 | /// For simplicity right now in supporting multiple implementations, it is being left as a string. 14 | io(string), 15 | } 16 | 17 | /// Gets a configuration value of type `string` associated with the `key`. 18 | /// 19 | /// The value is returned as an `option`. If the key is not found, 20 | /// `Ok(none)` is returned. If an error occurs, an `Err(error)` is returned. 21 | get: func( 22 | /// A string key to fetch 23 | key: string 24 | ) -> result, error>; 25 | 26 | /// Gets a list of configuration key-value pairs of type `string`. 27 | /// 28 | /// If an error occurs, an `Err(error)` is returned. 29 | get-all: func() -> result>, error>; 30 | } 31 | -------------------------------------------------------------------------------- /crates/macro/wit/deps/io/error.wit: -------------------------------------------------------------------------------- 1 | package wasi:io@0.2.0; 2 | 3 | 4 | interface error { 5 | /// A resource which represents some error information. 6 | /// 7 | /// The only method provided by this resource is `to-debug-string`, 8 | /// which provides some human-readable information about the error. 9 | /// 10 | /// In the `wasi:io` package, this resource is returned through the 11 | /// `wasi:io/streams/stream-error` type. 12 | /// 13 | /// To provide more specific error information, other interfaces may 14 | /// provide functions to further "downcast" this error into more specific 15 | /// error information. For example, `error`s returned in streams derived 16 | /// from filesystem types to be described using the filesystem's own 17 | /// error-code type, using the function 18 | /// `wasi:filesystem/types/filesystem-error-code`, which takes a parameter 19 | /// `borrow` and returns 20 | /// `option`. 21 | /// 22 | /// The set of functions which can "downcast" an `error` into a more 23 | /// concrete type is open. 24 | resource error { 25 | /// Returns a string that is suitable to assist humans in debugging 26 | /// this error. 27 | /// 28 | /// WARNING: The returned string should not be consumed mechanically! 29 | /// It may change across platforms, hosts, or other implementation 30 | /// details. Parsing this string is a major platform-compatibility 31 | /// hazard. 32 | to-debug-string: func() -> string; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /wit/deps/sockets/udp-create-socket.wit: -------------------------------------------------------------------------------- 1 | 2 | interface udp-create-socket { 3 | use network.{network, error-code, ip-address-family}; 4 | use udp.{udp-socket}; 5 | 6 | /// Create a new UDP socket. 7 | /// 8 | /// Similar to `socket(AF_INET or AF_INET6, SOCK_DGRAM, IPPROTO_UDP)` in POSIX. 9 | /// On IPv6 sockets, IPV6_V6ONLY is enabled by default and can't be configured otherwise. 10 | /// 11 | /// This function does not require a network capability handle. This is considered to be safe because 12 | /// at time of creation, the socket is not bound to any `network` yet. Up to the moment `bind` is called, 13 | /// the socket is effectively an in-memory configuration object, unable to communicate with the outside world. 14 | /// 15 | /// All sockets are non-blocking. Use the wasi-poll interface to block on asynchronous operations. 16 | /// 17 | /// # Typical errors 18 | /// - `not-supported`: The specified `address-family` is not supported. (EAFNOSUPPORT) 19 | /// - `new-socket-limit`: The new socket resource could not be created because of a system limit. (EMFILE, ENFILE) 20 | /// 21 | /// # References: 22 | /// - 23 | /// - 24 | /// - 25 | /// - 26 | create-udp-socket: func(address-family: ip-address-family) -> result; 27 | } 28 | -------------------------------------------------------------------------------- /wit/deps/io-2023-11-10/error.wit: -------------------------------------------------------------------------------- 1 | package wasi:io@0.2.0-rc-2023-11-10; 2 | 3 | 4 | interface error { 5 | /// A resource which represents some error information. 6 | /// 7 | /// The only method provided by this resource is `to-debug-string`, 8 | /// which provides some human-readable information about the error. 9 | /// 10 | /// In the `wasi:io` package, this resource is returned through the 11 | /// `wasi:io/streams/stream-error` type. 12 | /// 13 | /// To provide more specific error information, other interfaces may 14 | /// provide functions to further "downcast" this error into more specific 15 | /// error information. For example, `error`s returned in streams derived 16 | /// from filesystem types to be described using the filesystem's own 17 | /// error-code type, using the function 18 | /// `wasi:filesystem/types/filesystem-error-code`, which takes a parameter 19 | /// `borrow` and returns 20 | /// `option`. 21 | /// 22 | /// The set of functions which can "downcast" an `error` into a more 23 | /// concrete type is open. 24 | resource error { 25 | /// Returns a string that is suitable to assist humans in debugging 26 | /// this error. 27 | /// 28 | /// WARNING: The returned string should not be consumed mechanically! 29 | /// It may change across platforms, hosts, or other implementation 30 | /// details. Parsing this string is a major platform-compatibility 31 | /// hazard. 32 | to-debug-string: func() -> string; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /wit/deps/sockets/tcp-create-socket.wit: -------------------------------------------------------------------------------- 1 | 2 | interface tcp-create-socket { 3 | use network.{network, error-code, ip-address-family}; 4 | use tcp.{tcp-socket}; 5 | 6 | /// Create a new TCP socket. 7 | /// 8 | /// Similar to `socket(AF_INET or AF_INET6, SOCK_STREAM, IPPROTO_TCP)` in POSIX. 9 | /// On IPv6 sockets, IPV6_V6ONLY is enabled by default and can't be configured otherwise. 10 | /// 11 | /// This function does not require a network capability handle. This is considered to be safe because 12 | /// at time of creation, the socket is not bound to any `network` yet. Up to the moment `bind`/`connect` 13 | /// is called, the socket is effectively an in-memory configuration object, unable to communicate with the outside world. 14 | /// 15 | /// All sockets are non-blocking. Use the wasi-poll interface to block on asynchronous operations. 16 | /// 17 | /// # Typical errors 18 | /// - `not-supported`: The specified `address-family` is not supported. (EAFNOSUPPORT) 19 | /// - `new-socket-limit`: The new socket resource could not be created because of a system limit. (EMFILE, ENFILE) 20 | /// 21 | /// # References 22 | /// - 23 | /// - 24 | /// - 25 | /// - 26 | create-tcp-socket: func(address-family: ip-address-family) -> result; 27 | } 28 | -------------------------------------------------------------------------------- /crates/macro/wit/deps/sockets/udp-create-socket.wit: -------------------------------------------------------------------------------- 1 | 2 | interface udp-create-socket { 3 | use network.{network, error-code, ip-address-family}; 4 | use udp.{udp-socket}; 5 | 6 | /// Create a new UDP socket. 7 | /// 8 | /// Similar to `socket(AF_INET or AF_INET6, SOCK_DGRAM, IPPROTO_UDP)` in POSIX. 9 | /// On IPv6 sockets, IPV6_V6ONLY is enabled by default and can't be configured otherwise. 10 | /// 11 | /// This function does not require a network capability handle. This is considered to be safe because 12 | /// at time of creation, the socket is not bound to any `network` yet. Up to the moment `bind` is called, 13 | /// the socket is effectively an in-memory configuration object, unable to communicate with the outside world. 14 | /// 15 | /// All sockets are non-blocking. Use the wasi-poll interface to block on asynchronous operations. 16 | /// 17 | /// # Typical errors 18 | /// - `not-supported`: The specified `address-family` is not supported. (EAFNOSUPPORT) 19 | /// - `new-socket-limit`: The new socket resource could not be created because of a system limit. (EMFILE, ENFILE) 20 | /// 21 | /// # References: 22 | /// - 23 | /// - 24 | /// - 25 | /// - 26 | create-udp-socket: func(address-family: ip-address-family) -> result; 27 | } 28 | -------------------------------------------------------------------------------- /crates/macro/wit/deps/sockets/tcp-create-socket.wit: -------------------------------------------------------------------------------- 1 | 2 | interface tcp-create-socket { 3 | use network.{network, error-code, ip-address-family}; 4 | use tcp.{tcp-socket}; 5 | 6 | /// Create a new TCP socket. 7 | /// 8 | /// Similar to `socket(AF_INET or AF_INET6, SOCK_STREAM, IPPROTO_TCP)` in POSIX. 9 | /// On IPv6 sockets, IPV6_V6ONLY is enabled by default and can't be configured otherwise. 10 | /// 11 | /// This function does not require a network capability handle. This is considered to be safe because 12 | /// at time of creation, the socket is not bound to any `network` yet. Up to the moment `bind`/`connect` 13 | /// is called, the socket is effectively an in-memory configuration object, unable to communicate with the outside world. 14 | /// 15 | /// All sockets are non-blocking. Use the wasi-poll interface to block on asynchronous operations. 16 | /// 17 | /// # Typical errors 18 | /// - `not-supported`: The specified `address-family` is not supported. (EAFNOSUPPORT) 19 | /// - `new-socket-limit`: The new socket resource could not be created because of a system limit. (EMFILE, ENFILE) 20 | /// 21 | /// # References 22 | /// - 23 | /// - 24 | /// - 25 | /// - 26 | create-tcp-socket: func(address-family: ip-address-family) -> result; 27 | } 28 | -------------------------------------------------------------------------------- /release-process.md: -------------------------------------------------------------------------------- 1 | # Cutting a new release of the Spin Rust SDK 2 | 3 | To cut a new release, you will need to do the following: 4 | 5 | 1. Confirm that [CI is green](https://github.com/spinframework/spin-rust-sdk/actions) for the commit selected to be tagged and released. 6 | 7 | 2. Change the workspace version number in [Cargo.toml](./Cargo.toml) and the versions for any dependencies that are part of this workspace (e.g. `spin-macro`). 8 | 9 | 3. Create a pull request with these changes and merge once approved. 10 | 11 | 4. Checkout the commit with the version bump from above. 12 | 13 | 5. Create and push a new tag with a `v` and then the version number. 14 | 15 | As an example, via the `git` CLI: 16 | 17 | ``` 18 | # Create a GPG-signed and annotated tag 19 | git tag -s -m "Spin Rust SDK v3.1.0" v3.1.0 20 | 21 | # Push the tag to the remote corresponding to spinframework/spin-rust-sdk (here 'origin') 22 | git push origin v3.1.0 23 | ``` 24 | 25 | 6. Pushing the tag upstream will trigger the [release action](https://github.com/spinframework/spin-rust-sdk/actions/workflows/release.yml) which publishes the crates in this workspace to `crates.io` and dispatches an `rust-sdk-release` event to the `spinframework/spin` repository. This event will trigger an action that updates the rust template's sdk dependency. 26 | 27 | 7. If applicable, create PR(s) or coordinate [documentation](https://github.com/spinframework/spin-docs) needs, e.g. for new features or updated functionality. 28 | 29 | 8. Create a PR to update the SDK version of [examples in the Spin repo](https://github.com/spinframework/spin/examples/) that use the Rust SDK as appropriate. 30 | -------------------------------------------------------------------------------- /examples/key-value/src/lib.rs: -------------------------------------------------------------------------------- 1 | use http::{Method, StatusCode}; 2 | use spin_sdk::{ 3 | http::{IntoResponse, Response}, 4 | http_component, 5 | key_value::Store, 6 | }; 7 | 8 | #[http_component] 9 | fn handle_request(req: http::Request>) -> anyhow::Result { 10 | // Open the default key-value store 11 | let store = Store::open_default()?; 12 | 13 | let (status, body) = match *req.method() { 14 | Method::POST => { 15 | // Add the request (URI, body) tuple to the store 16 | store.set(req.uri().path(), req.body().as_slice())?; 17 | (StatusCode::OK, None) 18 | } 19 | Method::GET => { 20 | // Get the value associated with the request URI, or return a 404 if it's not present 21 | match store.get(req.uri().path())? { 22 | Some(value) => (StatusCode::OK, Some(value)), 23 | None => (StatusCode::NOT_FOUND, None), 24 | } 25 | } 26 | Method::DELETE => { 27 | // Delete the value associated with the request URI, if present 28 | store.delete(req.uri().path())?; 29 | (StatusCode::OK, None) 30 | } 31 | Method::HEAD => { 32 | // Like GET, except do not return the value 33 | let code = if store.exists(req.uri().path())? { 34 | StatusCode::OK 35 | } else { 36 | StatusCode::NOT_FOUND 37 | }; 38 | (code, None) 39 | } 40 | // No other methods are currently supported 41 | _ => (StatusCode::METHOD_NOT_ALLOWED, None), 42 | }; 43 | Ok(Response::new(status, body)) 44 | } 45 | -------------------------------------------------------------------------------- /examples/postgres/README.md: -------------------------------------------------------------------------------- 1 | # Spin Outbound PostgreSQL example 2 | 3 | This example shows how to access a PostgreSQL database from Spin component. 4 | 5 | ## Prerequisite: Postgres 6 | 7 | This example assumes postgres is running and accessible locally via its standard 5432 port. 8 | 9 | We suggest running the `postgres` Docker container which has the necessary postgres user permissions 10 | already configured. For example: 11 | 12 | ``` 13 | docker run --rm -h 127.0.0.1 -p 5432:5432 -e POSTGRES_HOST_AUTH_METHOD=trust postgres 14 | ``` 15 | 16 | ## Spin up 17 | 18 | Then, run the following from the root of this example: 19 | 20 | ``` 21 | createdb -h localhost -U postgres spin_dev 22 | psql -h localhost -U postgres -d spin_dev -f db/testdata.sql 23 | spin build --up 24 | ``` 25 | 26 | Curl the read route: 27 | 28 | ``` 29 | $ curl -i localhost:3000/read 30 | HTTP/1.1 200 OK 31 | content-length: 501 32 | date: Sun, 25 Sep 2022 15:45:02 GMT 33 | 34 | Found 2 article(s) as follows: 35 | article: Article { 36 | id: 1, 37 | title: "My Life as a Goat", 38 | content: "I went to Nepal to live as a goat, and it was much better than being a butler.", 39 | authorname: "E. Blackadder", 40 | } 41 | article: Article { 42 | id: 2, 43 | title: "Magnificent Octopus", 44 | content: "Once upon a time there was a lovely little sausage.", 45 | authorname: "S. Baldrick", 46 | } 47 | 48 | (Column info: id:DbDataType::Int32, title:DbDataType::Str, content:DbDataType::Str, authorname:DbDataType::Str) 49 | ``` 50 | 51 | Curl the write route: 52 | 53 | ``` 54 | $ curl -i localhost:3000/write 55 | HTTP/1.1 200 OK 56 | content-length: 9 57 | date: Sun, 25 Sep 2022 15:46:22 GMT 58 | 59 | Count: 3 60 | ``` 61 | -------------------------------------------------------------------------------- /wit/deps/http-2023-11-10/proxy.wit: -------------------------------------------------------------------------------- 1 | package wasi:http@0.2.0-rc-2023-11-10; 2 | 3 | /// The `wasi:http/proxy` world captures a widely-implementable intersection of 4 | /// hosts that includes HTTP forward and reverse proxies. Components targeting 5 | /// this world may concurrently stream in and out any number of incoming and 6 | /// outgoing HTTP requests. 7 | world proxy { 8 | /// HTTP proxies have access to time and randomness. 9 | import wasi:clocks/wall-clock@0.2.0-rc-2023-11-10; 10 | import wasi:clocks/monotonic-clock@0.2.0-rc-2023-11-10; 11 | import wasi:random/random@0.2.0-rc-2023-11-10; 12 | 13 | /// Proxies have standard output and error streams which are expected to 14 | /// terminate in a developer-facing console provided by the host. 15 | import wasi:cli/stdout@0.2.0-rc-2023-11-10; 16 | import wasi:cli/stderr@0.2.0-rc-2023-11-10; 17 | 18 | /// TODO: this is a temporary workaround until component tooling is able to 19 | /// gracefully handle the absence of stdin. Hosts must return an eof stream 20 | /// for this import, which is what wasi-libc + tooling will do automatically 21 | /// when this import is properly removed. 22 | import wasi:cli/stdin@0.2.0-rc-2023-11-10; 23 | 24 | /// This is the default handler to use when user code simply wants to make an 25 | /// HTTP request (e.g., via `fetch()`). 26 | import outgoing-handler; 27 | 28 | /// The host delivers incoming HTTP requests to a component by calling the 29 | /// `handle` function of this exported interface. A host may arbitrarily reuse 30 | /// or not reuse component instance when delivering incoming HTTP requests and 31 | /// thus a component must be able to handle 0..N calls to `handle`. 32 | export incoming-handler; 33 | } 34 | -------------------------------------------------------------------------------- /wit/deps/clocks/monotonic-clock.wit: -------------------------------------------------------------------------------- 1 | package wasi:clocks@0.2.0; 2 | /// WASI Monotonic Clock is a clock API intended to let users measure elapsed 3 | /// time. 4 | /// 5 | /// It is intended to be portable at least between Unix-family platforms and 6 | /// Windows. 7 | /// 8 | /// A monotonic clock is a clock which has an unspecified initial value, and 9 | /// successive reads of the clock will produce non-decreasing values. 10 | /// 11 | /// It is intended for measuring elapsed time. 12 | interface monotonic-clock { 13 | use wasi:io/poll@0.2.0.{pollable}; 14 | 15 | /// An instant in time, in nanoseconds. An instant is relative to an 16 | /// unspecified initial value, and can only be compared to instances from 17 | /// the same monotonic-clock. 18 | type instant = u64; 19 | 20 | /// A duration of time, in nanoseconds. 21 | type duration = u64; 22 | 23 | /// Read the current value of the clock. 24 | /// 25 | /// The clock is monotonic, therefore calling this function repeatedly will 26 | /// produce a sequence of non-decreasing values. 27 | now: func() -> instant; 28 | 29 | /// Query the resolution of the clock. Returns the duration of time 30 | /// corresponding to a clock tick. 31 | resolution: func() -> duration; 32 | 33 | /// Create a `pollable` which will resolve once the specified instant 34 | /// occured. 35 | subscribe-instant: func( 36 | when: instant, 37 | ) -> pollable; 38 | 39 | /// Create a `pollable` which will resolve once the given duration has 40 | /// elapsed, starting at the time at which this function was called. 41 | /// occured. 42 | subscribe-duration: func( 43 | when: duration, 44 | ) -> pollable; 45 | } 46 | -------------------------------------------------------------------------------- /crates/macro/wit/deps/clocks/monotonic-clock.wit: -------------------------------------------------------------------------------- 1 | package wasi:clocks@0.2.0; 2 | /// WASI Monotonic Clock is a clock API intended to let users measure elapsed 3 | /// time. 4 | /// 5 | /// It is intended to be portable at least between Unix-family platforms and 6 | /// Windows. 7 | /// 8 | /// A monotonic clock is a clock which has an unspecified initial value, and 9 | /// successive reads of the clock will produce non-decreasing values. 10 | /// 11 | /// It is intended for measuring elapsed time. 12 | interface monotonic-clock { 13 | use wasi:io/poll@0.2.0.{pollable}; 14 | 15 | /// An instant in time, in nanoseconds. An instant is relative to an 16 | /// unspecified initial value, and can only be compared to instances from 17 | /// the same monotonic-clock. 18 | type instant = u64; 19 | 20 | /// A duration of time, in nanoseconds. 21 | type duration = u64; 22 | 23 | /// Read the current value of the clock. 24 | /// 25 | /// The clock is monotonic, therefore calling this function repeatedly will 26 | /// produce a sequence of non-decreasing values. 27 | now: func() -> instant; 28 | 29 | /// Query the resolution of the clock. Returns the duration of time 30 | /// corresponding to a clock tick. 31 | resolution: func() -> duration; 32 | 33 | /// Create a `pollable` which will resolve once the specified instant 34 | /// occured. 35 | subscribe-instant: func( 36 | when: instant, 37 | ) -> pollable; 38 | 39 | /// Create a `pollable` which will resolve once the given duration has 40 | /// elapsed, starting at the time at which this function was called. 41 | /// occured. 42 | subscribe-duration: func( 43 | when: duration, 44 | ) -> pollable; 45 | } 46 | -------------------------------------------------------------------------------- /wit/deps/io/poll.wit: -------------------------------------------------------------------------------- 1 | package wasi:io@0.2.0; 2 | 3 | /// A poll API intended to let users wait for I/O events on multiple handles 4 | /// at once. 5 | interface poll { 6 | /// `pollable` represents a single I/O event which may be ready, or not. 7 | resource pollable { 8 | 9 | /// Return the readiness of a pollable. This function never blocks. 10 | /// 11 | /// Returns `true` when the pollable is ready, and `false` otherwise. 12 | ready: func() -> bool; 13 | 14 | /// `block` returns immediately if the pollable is ready, and otherwise 15 | /// blocks until ready. 16 | /// 17 | /// This function is equivalent to calling `poll.poll` on a list 18 | /// containing only this pollable. 19 | block: func(); 20 | } 21 | 22 | /// Poll for completion on a set of pollables. 23 | /// 24 | /// This function takes a list of pollables, which identify I/O sources of 25 | /// interest, and waits until one or more of the events is ready for I/O. 26 | /// 27 | /// The result `list` contains one or more indices of handles in the 28 | /// argument list that is ready for I/O. 29 | /// 30 | /// If the list contains more elements than can be indexed with a `u32` 31 | /// value, this function traps. 32 | /// 33 | /// A timeout can be implemented by adding a pollable from the 34 | /// wasi-clocks API to the list. 35 | /// 36 | /// This function does not return a `result`; polling in itself does not 37 | /// do any I/O so it doesn't fail. If any of the I/O sources identified by 38 | /// the pollables has an error, it is indicated by marking the source as 39 | /// being reaedy for I/O. 40 | poll: func(in: list>) -> list; 41 | } 42 | -------------------------------------------------------------------------------- /wit/deps/http-2023-10-18/proxy.wit: -------------------------------------------------------------------------------- 1 | package wasi:http@0.2.0-rc-2023-10-18; 2 | 3 | // The `wasi:http/proxy` world captures a widely-implementable intersection of 4 | // hosts that includes HTTP forward and reverse proxies. Components targeting 5 | // this world may concurrently stream in and out any number of incoming and 6 | // outgoing HTTP requests. 7 | world proxy { 8 | // HTTP proxies have access to time and randomness. 9 | import wasi:clocks/wall-clock@0.2.0-rc-2023-10-18; 10 | import wasi:clocks/monotonic-clock@0.2.0-rc-2023-10-18; 11 | import wasi:clocks/timezone@0.2.0-rc-2023-10-18; 12 | import wasi:random/random@0.2.0-rc-2023-10-18; 13 | 14 | // Proxies have standard output and error streams which are expected to 15 | // terminate in a developer-facing console provided by the host. 16 | import wasi:cli/stdout@0.2.0-rc-2023-10-18; 17 | import wasi:cli/stderr@0.2.0-rc-2023-10-18; 18 | 19 | // TODO: this is a temporary workaround until component tooling is able to 20 | // gracefully handle the absence of stdin. Hosts must return an eof stream 21 | // for this import, which is what wasi-libc + tooling will do automatically 22 | // when this import is properly removed. 23 | import wasi:cli/stdin@0.2.0-rc-2023-10-18; 24 | 25 | // This is the default handler to use when user code simply wants to make an 26 | // HTTP request (e.g., via `fetch()`). 27 | import outgoing-handler; 28 | 29 | // The host delivers incoming HTTP requests to a component by calling the 30 | // `handle` function of this exported interface. A host may arbitrarily reuse 31 | // or not reuse component instance when delivering incoming HTTP requests and 32 | // thus a component must be able to handle 0..N calls to `handle`. 33 | export incoming-handler; 34 | } 35 | -------------------------------------------------------------------------------- /wit/deps/clocks-2023-11-10/monotonic-clock.wit: -------------------------------------------------------------------------------- 1 | package wasi:clocks@0.2.0-rc-2023-11-10; 2 | /// WASI Monotonic Clock is a clock API intended to let users measure elapsed 3 | /// time. 4 | /// 5 | /// It is intended to be portable at least between Unix-family platforms and 6 | /// Windows. 7 | /// 8 | /// A monotonic clock is a clock which has an unspecified initial value, and 9 | /// successive reads of the clock will produce non-decreasing values. 10 | /// 11 | /// It is intended for measuring elapsed time. 12 | interface monotonic-clock { 13 | use wasi:io/poll@0.2.0-rc-2023-11-10.{pollable}; 14 | 15 | /// An instant in time, in nanoseconds. An instant is relative to an 16 | /// unspecified initial value, and can only be compared to instances from 17 | /// the same monotonic-clock. 18 | type instant = u64; 19 | 20 | /// A duration of time, in nanoseconds. 21 | type duration = u64; 22 | 23 | /// Read the current value of the clock. 24 | /// 25 | /// The clock is monotonic, therefore calling this function repeatedly will 26 | /// produce a sequence of non-decreasing values. 27 | now: func() -> instant; 28 | 29 | /// Query the resolution of the clock. Returns the duration of time 30 | /// corresponding to a clock tick. 31 | resolution: func() -> duration; 32 | 33 | /// Create a `pollable` which will resolve once the specified instant 34 | /// occured. 35 | subscribe-instant: func( 36 | when: instant, 37 | ) -> pollable; 38 | 39 | /// Create a `pollable` which will resolve once the given duration has 40 | /// elapsed, starting at the time at which this function was called. 41 | /// occured. 42 | subscribe-duration: func( 43 | when: duration, 44 | ) -> pollable; 45 | } 46 | -------------------------------------------------------------------------------- /crates/macro/wit/deps/io/poll.wit: -------------------------------------------------------------------------------- 1 | package wasi:io@0.2.0; 2 | 3 | /// A poll API intended to let users wait for I/O events on multiple handles 4 | /// at once. 5 | interface poll { 6 | /// `pollable` represents a single I/O event which may be ready, or not. 7 | resource pollable { 8 | 9 | /// Return the readiness of a pollable. This function never blocks. 10 | /// 11 | /// Returns `true` when the pollable is ready, and `false` otherwise. 12 | ready: func() -> bool; 13 | 14 | /// `block` returns immediately if the pollable is ready, and otherwise 15 | /// blocks until ready. 16 | /// 17 | /// This function is equivalent to calling `poll.poll` on a list 18 | /// containing only this pollable. 19 | block: func(); 20 | } 21 | 22 | /// Poll for completion on a set of pollables. 23 | /// 24 | /// This function takes a list of pollables, which identify I/O sources of 25 | /// interest, and waits until one or more of the events is ready for I/O. 26 | /// 27 | /// The result `list` contains one or more indices of handles in the 28 | /// argument list that is ready for I/O. 29 | /// 30 | /// If the list contains more elements than can be indexed with a `u32` 31 | /// value, this function traps. 32 | /// 33 | /// A timeout can be implemented by adding a pollable from the 34 | /// wasi-clocks API to the list. 35 | /// 36 | /// This function does not return a `result`; polling in itself does not 37 | /// do any I/O so it doesn't fail. If any of the I/O sources identified by 38 | /// the pollables has an error, it is indicated by marking the source as 39 | /// being reaedy for I/O. 40 | poll: func(in: list>) -> list; 41 | } 42 | -------------------------------------------------------------------------------- /wit/deps/io-2023-11-10/poll.wit: -------------------------------------------------------------------------------- 1 | package wasi:io@0.2.0-rc-2023-11-10; 2 | 3 | /// A poll API intended to let users wait for I/O events on multiple handles 4 | /// at once. 5 | interface poll { 6 | /// `pollable` epresents a single I/O event which may be ready, or not. 7 | resource pollable { 8 | 9 | /// Return the readiness of a pollable. This function never blocks. 10 | /// 11 | /// Returns `true` when the pollable is ready, and `false` otherwise. 12 | ready: func() -> bool; 13 | 14 | /// `block` returns immediately if the pollable is ready, and otherwise 15 | /// blocks until ready. 16 | /// 17 | /// This function is equivalent to calling `poll.poll` on a list 18 | /// containing only this pollable. 19 | block: func(); 20 | } 21 | 22 | /// Poll for completion on a set of pollables. 23 | /// 24 | /// This function takes a list of pollables, which identify I/O sources of 25 | /// interest, and waits until one or more of the events is ready for I/O. 26 | /// 27 | /// The result `list` contains one or more indices of handles in the 28 | /// argument list that is ready for I/O. 29 | /// 30 | /// If the list contains more elements than can be indexed with a `u32` 31 | /// value, this function traps. 32 | /// 33 | /// A timeout can be implemented by adding a pollable from the 34 | /// wasi-clocks API to the list. 35 | /// 36 | /// This function does not return a `result`; polling in itself does not 37 | /// do any I/O so it doesn't fail. If any of the I/O sources identified by 38 | /// the pollables has an error, it is indicated by marking the source as 39 | /// being reaedy for I/O. 40 | poll: func(in: list>) -> list; 41 | } 42 | -------------------------------------------------------------------------------- /examples/mqtt-outbound/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use spin_sdk::{ 3 | http::responses::internal_server_error, 4 | http::{IntoResponse, Request, Response}, 5 | http_component, mqtt, 6 | }; 7 | use std::env; 8 | 9 | // The environment variable set in `spin.toml` that points to the 10 | // address of the Mqtt server (with other attributes) that the component will publish 11 | // a message to. 12 | const MQTT_ADDRESS_ENV: &str = "MQTT_ADDRESS"; 13 | const MQTT_USERNAME_ENV: &str = "MQTT_USERNAME"; 14 | const MQTT_PASSWORD_ENV: &str = "MQTT_PASSWORD"; 15 | const MQTT_KEEP_ALIVE_INTERVAL_ENV: &str = "MQTT_KEEP_ALIVE_INTERVAL"; 16 | 17 | // The environment variable set in `spin.toml` that specifies 18 | // the Mqtt topic that the component will publish to. 19 | const MQTT_TOPIC_ENV: &str = "MQTT_TOPIC"; 20 | 21 | /// This HTTP component demonstrates publishing a value to Mqtt 22 | /// topic. The component is triggered by an HTTP 23 | /// request served on the route configured in the `spin.toml`. 24 | #[http_component] 25 | fn publish(_req: Request) -> Result { 26 | let address = env::var(MQTT_ADDRESS_ENV)?; 27 | let username = env::var(MQTT_USERNAME_ENV)?; 28 | let password = env::var(MQTT_PASSWORD_ENV)?; 29 | let keep_alive_interval = env::var(MQTT_KEEP_ALIVE_INTERVAL_ENV)?.parse::()?; 30 | let topic = env::var(MQTT_TOPIC_ENV)?; 31 | 32 | let message = Vec::from("Eureka!"); 33 | 34 | // Open connection to Mqtt server 35 | let conn = mqtt::Connection::open(&address, &username, &password, keep_alive_interval)?; 36 | 37 | // Publish to Mqtt server 38 | match conn.publish(&topic, &message, mqtt::Qos::AtLeastOnce) { 39 | Ok(()) => Ok(Response::new(200, ())), 40 | Err(_e) => Ok(internal_server_error()), 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /wit/deps/spin@2.0.0/sqlite.wit: -------------------------------------------------------------------------------- 1 | interface sqlite { 2 | /// A handle to an open sqlite instance 3 | resource connection { 4 | /// Open a connection to a named database instance. 5 | /// 6 | /// If `database` is "default", the default instance is opened. 7 | /// 8 | /// `error::no-such-database` will be raised if the `name` is not recognized. 9 | open: static func(database: string) -> result; 10 | 11 | /// Execute a statement returning back data if there is any 12 | execute: func(statement: string, parameters: list) -> result; 13 | } 14 | 15 | /// The set of errors which may be raised by functions in this interface 16 | variant error { 17 | /// The host does not recognize the database name requested. 18 | no-such-database, 19 | /// The requesting component does not have access to the specified database (which may or may not exist). 20 | access-denied, 21 | /// The provided connection is not valid 22 | invalid-connection, 23 | /// The database has reached its capacity 24 | database-full, 25 | /// Some implementation-specific error has occurred (e.g. I/O) 26 | io(string) 27 | } 28 | 29 | /// A result of a query 30 | record query-result { 31 | /// The names of the columns retrieved in the query 32 | columns: list, 33 | /// the row results each containing the values for all the columns for a given row 34 | rows: list, 35 | } 36 | 37 | /// A set of values for each of the columns in a query-result 38 | record row-result { 39 | values: list 40 | } 41 | 42 | /// A single column's result from a database query 43 | variant value { 44 | integer(s64), 45 | real(f64), 46 | text(string), 47 | blob(list), 48 | null 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /wit/deps/spin@unversioned/redis.wit: -------------------------------------------------------------------------------- 1 | interface redis { 2 | use redis-types.{payload, redis-parameter, redis-result, error}; 3 | 4 | // Publish a Redis message to the specificed channel and return an error, if any. 5 | publish: func(address: string, channel: string, payload: payload) -> result<_, error>; 6 | 7 | // Get the value of a key. 8 | get: func(address: string, key: string) -> result; 9 | 10 | // Set key to value. If key alreads holds a value, it is overwritten. 11 | set: func(address: string, key: string, value: payload) -> result<_, error>; 12 | 13 | // Increments the number stored at key by one. If the key does not exist, it is set to 0 before performing the operation. 14 | // An error is returned if the key contains a value of the wrong type or contains a string that can not be represented as integer. 15 | incr: func(address: string, key: string) -> result; 16 | 17 | // Removes the specified keys. A key is ignored if it does not exist. 18 | del: func(address: string, keys: list) -> result; 19 | 20 | // Add the specified `values` to the set named `key`, returning the number of newly-added values. 21 | sadd: func(address: string, key: string, values: list) -> result; 22 | 23 | // Retrieve the contents of the set named `key`. 24 | smembers: func(address: string, key: string) -> result, error>; 25 | 26 | // Remove the specified `values` from the set named `key`, returning the number of newly-removed values. 27 | srem: func(address: string, key: string, values: list) -> result; 28 | 29 | // Execute an arbitrary Redis command and receive the result. 30 | execute: func(address: string, command: string, arguments: list) -> result, error>; 31 | } 32 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | on: 3 | push: 4 | branches: ["main", "v*"] 5 | tags: ["v*"] 6 | pull_request: 7 | branches: ["main", "v*"] 8 | paths-ignore: 9 | - "README.md" 10 | 11 | env: 12 | RUST_VERSION: 1.86 13 | 14 | jobs: 15 | lint-and-test: 16 | name: Lint and Test 17 | runs-on: "ubuntu-latest" 18 | steps: 19 | - uses: actions/checkout@v4 20 | 21 | - uses: Swatinem/rust-cache@v2 22 | with: 23 | shared-key: "${{ runner.os }}-full-${{ hashFiles('./Cargo.lock') }}" 24 | 25 | - name: Install Rust toolchain 26 | shell: bash 27 | run: | 28 | rustup toolchain install ${{ env.RUST_VERSION }} --component clippy --component rustfmt 29 | rustup default ${{ env.RUST_VERSION }} 30 | rustup target add wasm32-wasip1 31 | 32 | - name: Lint 33 | shell: bash 34 | run: | 35 | cargo fmt --all -- --check 36 | cargo clippy --workspace --all-targets -- -D warnings 37 | 38 | - name: Check with no-default-features 39 | shell: bash 40 | run: cargo check --no-default-features 41 | 42 | - name: Check with `json` feature only 43 | shell: bash 44 | run: cargo check --no-default-features --features json 45 | 46 | - name: Check with `postgres4-types` feature only 47 | shell: bash 48 | run: cargo check --no-default-features --features postgres4-types 49 | 50 | - name: Test 51 | shell: bash 52 | run: cargo test --workspace 53 | 54 | - name: Validate docs examples 55 | shell: bash 56 | run: cargo test --doc 57 | 58 | - name: audit dependencies 59 | run: | 60 | cargo install cargo-audit --locked 61 | cargo audit 62 | 63 | -------------------------------------------------------------------------------- /wit/deps/spin@2.0.0/rdbms-types.wit: -------------------------------------------------------------------------------- 1 | interface rdbms-types { 2 | /// Errors related to interacting with a database. 3 | variant error { 4 | connection-failed(string), 5 | bad-parameter(string), 6 | query-failed(string), 7 | value-conversion-failed(string), 8 | other(string) 9 | } 10 | 11 | /// Data types for a database column 12 | enum db-data-type { 13 | boolean, 14 | int8, 15 | int16, 16 | int32, 17 | int64, 18 | uint8, 19 | uint16, 20 | uint32, 21 | uint64, 22 | floating32, 23 | floating64, 24 | str, 25 | binary, 26 | other, 27 | } 28 | 29 | /// Database values 30 | variant db-value { 31 | boolean(bool), 32 | int8(s8), 33 | int16(s16), 34 | int32(s32), 35 | int64(s64), 36 | uint8(u8), 37 | uint16(u16), 38 | uint32(u32), 39 | uint64(u64), 40 | floating32(f32), 41 | floating64(f64), 42 | str(string), 43 | binary(list), 44 | db-null, 45 | unsupported, 46 | } 47 | 48 | /// Values used in parameterized queries 49 | variant parameter-value { 50 | boolean(bool), 51 | int8(s8), 52 | int16(s16), 53 | int32(s32), 54 | int64(s64), 55 | uint8(u8), 56 | uint16(u16), 57 | uint32(u32), 58 | uint64(u64), 59 | floating32(f32), 60 | floating64(f64), 61 | str(string), 62 | binary(list), 63 | db-null, 64 | } 65 | 66 | /// A database column 67 | record column { 68 | name: string, 69 | data-type: db-data-type, 70 | } 71 | 72 | /// A database row 73 | type row = list; 74 | 75 | /// A set of database rows 76 | record row-set { 77 | columns: list, 78 | rows: list, 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /wit/deps/spin@unversioned/sqlite.wit: -------------------------------------------------------------------------------- 1 | interface sqlite { 2 | // A handle to an open sqlite instance 3 | type connection = u32; 4 | 5 | // The set of errors which may be raised by functions in this interface 6 | variant error { 7 | // The host does not recognize the database name requested. 8 | no-such-database, 9 | // The requesting component does not have access to the specified database (which may or may not exist). 10 | access-denied, 11 | // The provided connection is not valid 12 | invalid-connection, 13 | // The database has reached its capacity 14 | database-full, 15 | // Some implementation-specific error has occurred (e.g. I/O) 16 | io(string) 17 | } 18 | 19 | // Open a connection to a named database instance. 20 | // 21 | // If `database` is "default", the default instance is opened. 22 | // 23 | // `error::no-such-database` will be raised if the `name` is not recognized. 24 | open: func(database: string) -> result; 25 | 26 | // Execute a statement returning back data if there is any 27 | execute: func(conn: connection, statement: string, parameters: list) -> result; 28 | 29 | // Close the specified `connection`. 30 | close: func(conn: connection); 31 | 32 | // A result of a query 33 | record query-result { 34 | // The names of the columns retrieved in the query 35 | columns: list, 36 | // the row results each containing the values for all the columns for a given row 37 | rows: list, 38 | } 39 | 40 | // A set of values for each of the columns in a query-result 41 | record row-result { 42 | values: list 43 | } 44 | 45 | variant value { 46 | integer(s64), 47 | real(f64), 48 | text(string), 49 | blob(list), 50 | null 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /wit/deps/clocks-2023-10-18/wall-clock.wit: -------------------------------------------------------------------------------- 1 | /// WASI Wall Clock is a clock API intended to let users query the current 2 | /// time. The name "wall" makes an analogy to a "clock on the wall", which 3 | /// is not necessarily monotonic as it may be reset. 4 | /// 5 | /// It is intended to be portable at least between Unix-family platforms and 6 | /// Windows. 7 | /// 8 | /// A wall clock is a clock which measures the date and time according to 9 | /// some external reference. 10 | /// 11 | /// External references may be reset, so this clock is not necessarily 12 | /// monotonic, making it unsuitable for measuring elapsed time. 13 | /// 14 | /// It is intended for reporting the current date and time for humans. 15 | interface wall-clock { 16 | /// A time and date in seconds plus nanoseconds. 17 | record datetime { 18 | seconds: u64, 19 | nanoseconds: u32, 20 | } 21 | 22 | /// Read the current value of the clock. 23 | /// 24 | /// This clock is not monotonic, therefore calling this function repeatedly 25 | /// will not necessarily produce a sequence of non-decreasing values. 26 | /// 27 | /// The returned timestamps represent the number of seconds since 28 | /// 1970-01-01T00:00:00Z, also known as [POSIX's Seconds Since the Epoch], 29 | /// also known as [Unix Time]. 30 | /// 31 | /// The nanoseconds field of the output is always less than 1000000000. 32 | /// 33 | /// [POSIX's Seconds Since the Epoch]: https://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_xbd_chap04.html#tag_21_04_16 34 | /// [Unix Time]: https://en.wikipedia.org/wiki/Unix_time 35 | now: func() -> datetime; 36 | 37 | /// Query the resolution of the clock. 38 | /// 39 | /// The nanoseconds field of the output is always less than 1000000000. 40 | resolution: func() -> datetime; 41 | } 42 | -------------------------------------------------------------------------------- /wit/deps/spin@2.0.0/key-value.wit: -------------------------------------------------------------------------------- 1 | interface key-value { 2 | /// An open key-value store 3 | resource store { 4 | /// Open the store with the specified label. 5 | /// 6 | /// `label` must refer to a store allowed in the spin.toml manifest. 7 | /// 8 | /// `error::no-such-store` will be raised if the `label` is not recognized. 9 | open: static func(label: string) -> result; 10 | 11 | /// Get the value associated with the specified `key` 12 | /// 13 | /// Returns `ok(none)` if the key does not exist. 14 | get: func(key: string) -> result>, error>; 15 | 16 | /// Set the `value` associated with the specified `key` overwriting any existing value. 17 | set: func(key: string, value: list) -> result<_, error>; 18 | 19 | /// Delete the tuple with the specified `key` 20 | /// 21 | /// No error is raised if a tuple did not previously exist for `key`. 22 | delete: func(key: string) -> result<_, error>; 23 | 24 | /// Return whether a tuple exists for the specified `key` 25 | exists: func(key: string) -> result; 26 | 27 | /// Return a list of all the keys 28 | get-keys: func() -> result, error>; 29 | } 30 | 31 | /// The set of errors which may be raised by functions in this interface 32 | variant error { 33 | /// Too many stores have been opened simultaneously. Closing one or more 34 | /// stores prior to retrying may address this. 35 | store-table-full, 36 | 37 | /// The host does not recognize the store label requested. 38 | no-such-store, 39 | 40 | /// The requesting component does not have access to the specified store 41 | /// (which may or may not exist). 42 | access-denied, 43 | 44 | /// Some implementation-specific error has occurred (e.g. I/O) 45 | other(string) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /wit/deps/clocks/wall-clock.wit: -------------------------------------------------------------------------------- 1 | package wasi:clocks@0.2.0; 2 | /// WASI Wall Clock is a clock API intended to let users query the current 3 | /// time. The name "wall" makes an analogy to a "clock on the wall", which 4 | /// is not necessarily monotonic as it may be reset. 5 | /// 6 | /// It is intended to be portable at least between Unix-family platforms and 7 | /// Windows. 8 | /// 9 | /// A wall clock is a clock which measures the date and time according to 10 | /// some external reference. 11 | /// 12 | /// External references may be reset, so this clock is not necessarily 13 | /// monotonic, making it unsuitable for measuring elapsed time. 14 | /// 15 | /// It is intended for reporting the current date and time for humans. 16 | interface wall-clock { 17 | /// A time and date in seconds plus nanoseconds. 18 | record datetime { 19 | seconds: u64, 20 | nanoseconds: u32, 21 | } 22 | 23 | /// Read the current value of the clock. 24 | /// 25 | /// This clock is not monotonic, therefore calling this function repeatedly 26 | /// will not necessarily produce a sequence of non-decreasing values. 27 | /// 28 | /// The returned timestamps represent the number of seconds since 29 | /// 1970-01-01T00:00:00Z, also known as [POSIX's Seconds Since the Epoch], 30 | /// also known as [Unix Time]. 31 | /// 32 | /// The nanoseconds field of the output is always less than 1000000000. 33 | /// 34 | /// [POSIX's Seconds Since the Epoch]: https://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_xbd_chap04.html#tag_21_04_16 35 | /// [Unix Time]: https://en.wikipedia.org/wiki/Unix_time 36 | now: func() -> datetime; 37 | 38 | /// Query the resolution of the clock. 39 | /// 40 | /// The nanoseconds field of the output is always less than 1000000000. 41 | resolution: func() -> datetime; 42 | } 43 | -------------------------------------------------------------------------------- /wit/deps/cli-2023-10-18/terminal.wit: -------------------------------------------------------------------------------- 1 | interface terminal-input { 2 | /// The input side of a terminal. 3 | resource terminal-input; 4 | 5 | // In the future, this may include functions for disabling echoing, 6 | // disabling input buffering so that keyboard events are sent through 7 | // immediately, querying supported features, and so on. 8 | } 9 | 10 | interface terminal-output { 11 | /// The output side of a terminal. 12 | resource terminal-output; 13 | 14 | // In the future, this may include functions for querying the terminal 15 | // size, being notified of terminal size changes, querying supported 16 | // features, and so on. 17 | } 18 | 19 | /// An interface providing an optional `terminal-input` for stdin as a 20 | /// link-time authority. 21 | interface terminal-stdin { 22 | use terminal-input.{terminal-input}; 23 | 24 | /// If stdin is connected to a terminal, return a `terminal-input` handle 25 | /// allowing further interaction with it. 26 | get-terminal-stdin: func() -> option; 27 | } 28 | 29 | /// An interface providing an optional `terminal-output` for stdout as a 30 | /// link-time authority. 31 | interface terminal-stdout { 32 | use terminal-output.{terminal-output}; 33 | 34 | /// If stdout is connected to a terminal, return a `terminal-output` handle 35 | /// allowing further interaction with it. 36 | get-terminal-stdout: func() -> option; 37 | } 38 | 39 | /// An interface providing an optional `terminal-output` for stderr as a 40 | /// link-time authority. 41 | interface terminal-stderr { 42 | use terminal-output.{terminal-output}; 43 | 44 | /// If stderr is connected to a terminal, return a `terminal-output` handle 45 | /// allowing further interaction with it. 46 | get-terminal-stderr: func() -> option; 47 | } 48 | -------------------------------------------------------------------------------- /wit/deps/cli-2023-11-10/terminal.wit: -------------------------------------------------------------------------------- 1 | interface terminal-input { 2 | /// The input side of a terminal. 3 | resource terminal-input; 4 | 5 | // In the future, this may include functions for disabling echoing, 6 | // disabling input buffering so that keyboard events are sent through 7 | // immediately, querying supported features, and so on. 8 | } 9 | 10 | interface terminal-output { 11 | /// The output side of a terminal. 12 | resource terminal-output; 13 | 14 | // In the future, this may include functions for querying the terminal 15 | // size, being notified of terminal size changes, querying supported 16 | // features, and so on. 17 | } 18 | 19 | /// An interface providing an optional `terminal-input` for stdin as a 20 | /// link-time authority. 21 | interface terminal-stdin { 22 | use terminal-input.{terminal-input}; 23 | 24 | /// If stdin is connected to a terminal, return a `terminal-input` handle 25 | /// allowing further interaction with it. 26 | get-terminal-stdin: func() -> option; 27 | } 28 | 29 | /// An interface providing an optional `terminal-output` for stdout as a 30 | /// link-time authority. 31 | interface terminal-stdout { 32 | use terminal-output.{terminal-output}; 33 | 34 | /// If stdout is connected to a terminal, return a `terminal-output` handle 35 | /// allowing further interaction with it. 36 | get-terminal-stdout: func() -> option; 37 | } 38 | 39 | /// An interface providing an optional `terminal-output` for stderr as a 40 | /// link-time authority. 41 | interface terminal-stderr { 42 | use terminal-output.{terminal-output}; 43 | 44 | /// If stderr is connected to a terminal, return a `terminal-output` handle 45 | /// allowing further interaction with it. 46 | get-terminal-stderr: func() -> option; 47 | } 48 | -------------------------------------------------------------------------------- /crates/macro/wit/deps/clocks/wall-clock.wit: -------------------------------------------------------------------------------- 1 | package wasi:clocks@0.2.0; 2 | /// WASI Wall Clock is a clock API intended to let users query the current 3 | /// time. The name "wall" makes an analogy to a "clock on the wall", which 4 | /// is not necessarily monotonic as it may be reset. 5 | /// 6 | /// It is intended to be portable at least between Unix-family platforms and 7 | /// Windows. 8 | /// 9 | /// A wall clock is a clock which measures the date and time according to 10 | /// some external reference. 11 | /// 12 | /// External references may be reset, so this clock is not necessarily 13 | /// monotonic, making it unsuitable for measuring elapsed time. 14 | /// 15 | /// It is intended for reporting the current date and time for humans. 16 | interface wall-clock { 17 | /// A time and date in seconds plus nanoseconds. 18 | record datetime { 19 | seconds: u64, 20 | nanoseconds: u32, 21 | } 22 | 23 | /// Read the current value of the clock. 24 | /// 25 | /// This clock is not monotonic, therefore calling this function repeatedly 26 | /// will not necessarily produce a sequence of non-decreasing values. 27 | /// 28 | /// The returned timestamps represent the number of seconds since 29 | /// 1970-01-01T00:00:00Z, also known as [POSIX's Seconds Since the Epoch], 30 | /// also known as [Unix Time]. 31 | /// 32 | /// The nanoseconds field of the output is always less than 1000000000. 33 | /// 34 | /// [POSIX's Seconds Since the Epoch]: https://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_xbd_chap04.html#tag_21_04_16 35 | /// [Unix Time]: https://en.wikipedia.org/wiki/Unix_time 36 | now: func() -> datetime; 37 | 38 | /// Query the resolution of the clock. 39 | /// 40 | /// The nanoseconds field of the output is always less than 1000000000. 41 | resolution: func() -> datetime; 42 | } 43 | -------------------------------------------------------------------------------- /wit/deps/clocks-2023-11-10/wall-clock.wit: -------------------------------------------------------------------------------- 1 | package wasi:clocks@0.2.0-rc-2023-11-10; 2 | /// WASI Wall Clock is a clock API intended to let users query the current 3 | /// time. The name "wall" makes an analogy to a "clock on the wall", which 4 | /// is not necessarily monotonic as it may be reset. 5 | /// 6 | /// It is intended to be portable at least between Unix-family platforms and 7 | /// Windows. 8 | /// 9 | /// A wall clock is a clock which measures the date and time according to 10 | /// some external reference. 11 | /// 12 | /// External references may be reset, so this clock is not necessarily 13 | /// monotonic, making it unsuitable for measuring elapsed time. 14 | /// 15 | /// It is intended for reporting the current date and time for humans. 16 | interface wall-clock { 17 | /// A time and date in seconds plus nanoseconds. 18 | record datetime { 19 | seconds: u64, 20 | nanoseconds: u32, 21 | } 22 | 23 | /// Read the current value of the clock. 24 | /// 25 | /// This clock is not monotonic, therefore calling this function repeatedly 26 | /// will not necessarily produce a sequence of non-decreasing values. 27 | /// 28 | /// The returned timestamps represent the number of seconds since 29 | /// 1970-01-01T00:00:00Z, also known as [POSIX's Seconds Since the Epoch], 30 | /// also known as [Unix Time]. 31 | /// 32 | /// The nanoseconds field of the output is always less than 1000000000. 33 | /// 34 | /// [POSIX's Seconds Since the Epoch]: https://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_xbd_chap04.html#tag_21_04_16 35 | /// [Unix Time]: https://en.wikipedia.org/wiki/Unix_time 36 | now: func() -> datetime; 37 | 38 | /// Query the resolution of the clock. 39 | /// 40 | /// The nanoseconds field of the output is always less than 1000000000. 41 | resolution: func() -> datetime; 42 | } 43 | -------------------------------------------------------------------------------- /wit/deps/cli/terminal.wit: -------------------------------------------------------------------------------- 1 | /// Terminal input. 2 | /// 3 | /// In the future, this may include functions for disabling echoing, 4 | /// disabling input buffering so that keyboard events are sent through 5 | /// immediately, querying supported features, and so on. 6 | interface terminal-input { 7 | /// The input side of a terminal. 8 | resource terminal-input; 9 | } 10 | 11 | /// Terminal output. 12 | /// 13 | /// In the future, this may include functions for querying the terminal 14 | /// size, being notified of terminal size changes, querying supported 15 | /// features, and so on. 16 | interface terminal-output { 17 | /// The output side of a terminal. 18 | resource terminal-output; 19 | } 20 | 21 | /// An interface providing an optional `terminal-input` for stdin as a 22 | /// link-time authority. 23 | interface terminal-stdin { 24 | use terminal-input.{terminal-input}; 25 | 26 | /// If stdin is connected to a terminal, return a `terminal-input` handle 27 | /// allowing further interaction with it. 28 | get-terminal-stdin: func() -> option; 29 | } 30 | 31 | /// An interface providing an optional `terminal-output` for stdout as a 32 | /// link-time authority. 33 | interface terminal-stdout { 34 | use terminal-output.{terminal-output}; 35 | 36 | /// If stdout is connected to a terminal, return a `terminal-output` handle 37 | /// allowing further interaction with it. 38 | get-terminal-stdout: func() -> option; 39 | } 40 | 41 | /// An interface providing an optional `terminal-output` for stderr as a 42 | /// link-time authority. 43 | interface terminal-stderr { 44 | use terminal-output.{terminal-output}; 45 | 46 | /// If stderr is connected to a terminal, return a `terminal-output` handle 47 | /// allowing further interaction with it. 48 | get-terminal-stderr: func() -> option; 49 | } 50 | -------------------------------------------------------------------------------- /crates/macro/wit/deps/cli/terminal.wit: -------------------------------------------------------------------------------- 1 | /// Terminal input. 2 | /// 3 | /// In the future, this may include functions for disabling echoing, 4 | /// disabling input buffering so that keyboard events are sent through 5 | /// immediately, querying supported features, and so on. 6 | interface terminal-input { 7 | /// The input side of a terminal. 8 | resource terminal-input; 9 | } 10 | 11 | /// Terminal output. 12 | /// 13 | /// In the future, this may include functions for querying the terminal 14 | /// size, being notified of terminal size changes, querying supported 15 | /// features, and so on. 16 | interface terminal-output { 17 | /// The output side of a terminal. 18 | resource terminal-output; 19 | } 20 | 21 | /// An interface providing an optional `terminal-input` for stdin as a 22 | /// link-time authority. 23 | interface terminal-stdin { 24 | use terminal-input.{terminal-input}; 25 | 26 | /// If stdin is connected to a terminal, return a `terminal-input` handle 27 | /// allowing further interaction with it. 28 | get-terminal-stdin: func() -> option; 29 | } 30 | 31 | /// An interface providing an optional `terminal-output` for stdout as a 32 | /// link-time authority. 33 | interface terminal-stdout { 34 | use terminal-output.{terminal-output}; 35 | 36 | /// If stdout is connected to a terminal, return a `terminal-output` handle 37 | /// allowing further interaction with it. 38 | get-terminal-stdout: func() -> option; 39 | } 40 | 41 | /// An interface providing an optional `terminal-output` for stderr as a 42 | /// link-time authority. 43 | interface terminal-stderr { 44 | use terminal-output.{terminal-output}; 45 | 46 | /// If stderr is connected to a terminal, return a `terminal-output` handle 47 | /// allowing further interaction with it. 48 | get-terminal-stderr: func() -> option; 49 | } 50 | -------------------------------------------------------------------------------- /examples/http-outbound/spin.toml: -------------------------------------------------------------------------------- 1 | spin_manifest_version = 2 2 | 3 | [application] 4 | authors = ["The Spin authors"] 5 | description = "Demonstrates outbound HTTP calls" 6 | name = "outbound-http" 7 | version = "1.0.0" 8 | 9 | [[trigger.http]] 10 | route = "/outbound" 11 | component = "outbound-http" 12 | 13 | [[trigger.http]] 14 | route = "/wildcard" 15 | component = "outbound-http-wildcard" 16 | 17 | [[trigger.http]] 18 | route = "/outbound-to-hello-component" 19 | component = "outbound-http-to-same-app" 20 | 21 | [[trigger.http]] 22 | route = "/hello" 23 | component = "hello-component" 24 | 25 | [component.outbound-http] 26 | source = "../../target/wasm32-wasip1/release/http_rust_outbound_http.wasm" 27 | allowed_outbound_hosts = [ 28 | "https://random-data-api.fermyon.app", 29 | "http://foo.com", 30 | "https://foo.com", 31 | ] 32 | [component.outbound-http.build] 33 | workdir = "outbound-http" 34 | command = "cargo build --target wasm32-wasip1 --release" 35 | 36 | [component.outbound-http-wildcard] 37 | source = "../../target/wasm32-wasip1/release/http_rust_outbound_http.wasm" 38 | allowed_outbound_hosts = ["https://*:*"] 39 | [component.outbound-http-wildcard.build] 40 | workdir = "outbound-http" 41 | command = "cargo build --target wasm32-wasip1 --release" 42 | 43 | [component.outbound-http-to-same-app] 44 | source = "../../target/wasm32-wasip1/release/outbound_http_to_same_app.wasm" 45 | # To make outbound calls to components in the same Spin app, use the special value self. 46 | allowed_outbound_hosts = ["http://self"] 47 | [component.outbound-http-to-same-app.build] 48 | workdir = "outbound-http-to-same-app" 49 | command = "cargo build --target wasm32-wasip1 --release" 50 | 51 | [component.hello-component] 52 | source = "../../target/wasm32-wasip1/release/http_hello.wasm" 53 | description = "A simple component that returns hello." 54 | [component.hello-component.build] 55 | workdir = "http-hello" 56 | command = "cargo build --target wasm32-wasip1 --release" 57 | -------------------------------------------------------------------------------- /wit/deps/http/handler.wit: -------------------------------------------------------------------------------- 1 | /// This interface defines a handler of incoming HTTP Requests. It should 2 | /// be exported by components which can respond to HTTP Requests. 3 | interface incoming-handler { 4 | use types.{incoming-request, response-outparam}; 5 | 6 | /// This function is invoked with an incoming HTTP Request, and a resource 7 | /// `response-outparam` which provides the capability to reply with an HTTP 8 | /// Response. The response is sent by calling the `response-outparam.set` 9 | /// method, which allows execution to continue after the response has been 10 | /// sent. This enables both streaming to the response body, and performing other 11 | /// work. 12 | /// 13 | /// The implementor of this function must write a response to the 14 | /// `response-outparam` before returning, or else the caller will respond 15 | /// with an error on its behalf. 16 | handle: func( 17 | request: incoming-request, 18 | response-out: response-outparam 19 | ); 20 | } 21 | 22 | /// This interface defines a handler of outgoing HTTP Requests. It should be 23 | /// imported by components which wish to make HTTP Requests. 24 | interface outgoing-handler { 25 | use types.{ 26 | outgoing-request, request-options, future-incoming-response, error-code 27 | }; 28 | 29 | /// This function is invoked with an outgoing HTTP Request, and it returns 30 | /// a resource `future-incoming-response` which represents an HTTP Response 31 | /// which may arrive in the future. 32 | /// 33 | /// The `options` argument accepts optional parameters for the HTTP 34 | /// protocol's transport layer. 35 | /// 36 | /// This function may return an error if the `outgoing-request` is invalid 37 | /// or not allowed to be made. Otherwise, protocol errors are reported 38 | /// through the `future-incoming-response`. 39 | handle: func( 40 | request: outgoing-request, 41 | options: option 42 | ) -> result; 43 | } 44 | -------------------------------------------------------------------------------- /crates/macro/wit/deps/http/handler.wit: -------------------------------------------------------------------------------- 1 | /// This interface defines a handler of incoming HTTP Requests. It should 2 | /// be exported by components which can respond to HTTP Requests. 3 | interface incoming-handler { 4 | use types.{incoming-request, response-outparam}; 5 | 6 | /// This function is invoked with an incoming HTTP Request, and a resource 7 | /// `response-outparam` which provides the capability to reply with an HTTP 8 | /// Response. The response is sent by calling the `response-outparam.set` 9 | /// method, which allows execution to continue after the response has been 10 | /// sent. This enables both streaming to the response body, and performing other 11 | /// work. 12 | /// 13 | /// The implementor of this function must write a response to the 14 | /// `response-outparam` before returning, or else the caller will respond 15 | /// with an error on its behalf. 16 | handle: func( 17 | request: incoming-request, 18 | response-out: response-outparam 19 | ); 20 | } 21 | 22 | /// This interface defines a handler of outgoing HTTP Requests. It should be 23 | /// imported by components which wish to make HTTP Requests. 24 | interface outgoing-handler { 25 | use types.{ 26 | outgoing-request, request-options, future-incoming-response, error-code 27 | }; 28 | 29 | /// This function is invoked with an outgoing HTTP Request, and it returns 30 | /// a resource `future-incoming-response` which represents an HTTP Response 31 | /// which may arrive in the future. 32 | /// 33 | /// The `options` argument accepts optional parameters for the HTTP 34 | /// protocol's transport layer. 35 | /// 36 | /// This function may return an error if the `outgoing-request` is invalid 37 | /// or not allowed to be made. Otherwise, protocol errors are reported 38 | /// through the `future-incoming-response`. 39 | handle: func( 40 | request: outgoing-request, 41 | options: option 42 | ) -> result; 43 | } 44 | -------------------------------------------------------------------------------- /wit/deps/http-2023-11-10/handler.wit: -------------------------------------------------------------------------------- 1 | /// This interface defines a handler of incoming HTTP Requests. It should 2 | /// be exported by components which can respond to HTTP Requests. 3 | interface incoming-handler { 4 | use types.{incoming-request, response-outparam}; 5 | 6 | /// This function is invoked with an incoming HTTP Request, and a resource 7 | /// `response-outparam` which provides the capability to reply with an HTTP 8 | /// Response. The response is sent by calling the `response-outparam.set` 9 | /// method, which allows execution to continue after the response has been 10 | /// sent. This enables both streaming to the response body, and performing other 11 | /// work. 12 | /// 13 | /// The implementor of this function must write a response to the 14 | /// `response-outparam` before returning, or else the caller will respond 15 | /// with an error on its behalf. 16 | handle: func( 17 | request: incoming-request, 18 | response-out: response-outparam 19 | ); 20 | } 21 | 22 | /// This interface defines a handler of outgoing HTTP Requests. It should be 23 | /// imported by components which wish to make HTTP Requests. 24 | interface outgoing-handler { 25 | use types.{ 26 | outgoing-request, request-options, future-incoming-response, error-code 27 | }; 28 | 29 | /// This function is invoked with an outgoing HTTP Request, and it returns 30 | /// a resource `future-incoming-response` which represents an HTTP Response 31 | /// which may arrive in the future. 32 | /// 33 | /// The `options` argument accepts optional parameters for the HTTP 34 | /// protocol's transport layer. 35 | /// 36 | /// This function may return an error if the `outgoing-request` is invalid 37 | /// or not allowed to be made. Otherwise, protocol errors are reported 38 | /// through the `future-incoming-response`. 39 | handle: func( 40 | request: outgoing-request, 41 | options: option 42 | ) -> result; 43 | } 44 | -------------------------------------------------------------------------------- /src/mqtt.rs: -------------------------------------------------------------------------------- 1 | //! MQTT message publishing. 2 | //! 3 | //! To receive MQTT messages, use the MQTT trigger. 4 | //! 5 | //! # Examples 6 | //! 7 | //! Send an MQTT message. 8 | //! 9 | //! ```no_run 10 | //! use spin_sdk::mqtt::{Connection, Qos}; 11 | //! 12 | //! # fn ensure_pet_picture(_: &[u8]) -> anyhow::Result<()> { Ok(()) } 13 | //! # fn use_mqtt(request: spin_sdk::http::Request) -> anyhow::Result<()> { 14 | //! let user = spin_sdk::variables::get("mqtt_username")?; 15 | //! let password = spin_sdk::variables::get("mqtt_password")?; 16 | //! 17 | //! let conn = Connection::open( 18 | //! "mqtt://localhost:1883?client_id=123", 19 | //! &user, 20 | //! &password, 21 | //! 30 /* seconds */ 22 | //! )?; 23 | //! 24 | //! let payload = request.body().to_vec(); 25 | //! ensure_pet_picture(&payload)?; 26 | //! 27 | //! conn.publish("pet-pictures", &payload, Qos::AtLeastOnce)?; 28 | //! # Ok(()) 29 | //! # } 30 | //! ``` 31 | 32 | /// An open connection to an MQTT queue. 33 | /// 34 | /// The address must be in URL form, and must include a `client_id`: 35 | /// `mqtt://hostname?client_id=...` 36 | /// 37 | /// # Examples 38 | /// 39 | /// Send an MQTT message. 40 | /// 41 | /// ```no_run 42 | /// use spin_sdk::mqtt::{Connection, Qos}; 43 | /// 44 | /// # fn ensure_pet_picture(_: &[u8]) -> anyhow::Result<()> { Ok(()) } 45 | /// # fn use_mqtt(request: spin_sdk::http::Request) -> anyhow::Result<()> { 46 | /// let user = spin_sdk::variables::get("mqtt_username")?; 47 | /// let password = spin_sdk::variables::get("mqtt_password")?; 48 | /// 49 | /// let conn = Connection::open( 50 | /// "mqtt://localhost:1883?client_id=123", 51 | /// &user, 52 | /// &password, 53 | /// 30 /* seconds */ 54 | /// )?; 55 | /// 56 | /// let payload = request.body().to_vec(); 57 | /// ensure_pet_picture(&payload)?; 58 | /// 59 | /// conn.publish("pet-pictures", &payload, Qos::AtLeastOnce)?; 60 | /// # Ok(()) 61 | /// # } 62 | /// ``` 63 | pub use super::wit::v2::mqtt::Connection; 64 | 65 | pub use super::wit::v2::mqtt::{Error, Payload, Qos}; 66 | -------------------------------------------------------------------------------- /examples/redis-outbound/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, Context, Result}; 2 | use spin_sdk::{ 3 | http::responses::internal_server_error, 4 | http::{IntoResponse, Request, Response}, 5 | http_component, redis, 6 | }; 7 | 8 | // The environment variable set in `spin.toml` that points to the 9 | // address of the Redis server that the component will publish 10 | // a message to. 11 | const REDIS_ADDRESS_ENV: &str = "REDIS_ADDRESS"; 12 | 13 | // The environment variable set in `spin.toml` that specifies 14 | // the Redis channel that the component will publish to. 15 | const REDIS_CHANNEL_ENV: &str = "REDIS_CHANNEL"; 16 | 17 | /// This HTTP component demonstrates fetching a value from Redis 18 | /// by key, setting a key with a value, and publishing a message 19 | /// to a Redis channel. The component is triggered by an HTTP 20 | /// request served on the route configured in the `spin.toml`. 21 | #[http_component] 22 | fn publish(_req: Request) -> Result { 23 | let address = std::env::var(REDIS_ADDRESS_ENV)?; 24 | let channel = std::env::var(REDIS_CHANNEL_ENV)?; 25 | 26 | let conn = redis::Connection::open(&address)?; 27 | 28 | // Get the message to publish from the Redis key "mykey" 29 | let payload = conn 30 | .get("mykey") 31 | .map_err(|_| anyhow!("Error querying Redis"))? 32 | .context("no value for key 'mykey'")?; 33 | 34 | // Set the Redis key "spin-example" to value "Eureka!" 35 | conn.set("spin-example", &"Eureka!".to_owned().into_bytes()) 36 | .map_err(|_| anyhow!("Error executing Redis set command"))?; 37 | 38 | // Set the Redis key "int-key" to value 0 39 | conn.set("int-key", &format!("{:x}", 0).into_bytes()) 40 | .map_err(|_| anyhow!("Error executing Redis set command"))?; 41 | let int_value = conn 42 | .incr("int-key") 43 | .map_err(|_| anyhow!("Error executing Redis incr command",))?; 44 | assert_eq!(int_value, 1); 45 | 46 | // Publish to Redis 47 | match conn.publish(&channel, &payload) { 48 | Ok(()) => Ok(Response::new(200, ())), 49 | Err(_e) => Ok(internal_server_error()), 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/variables.rs: -------------------------------------------------------------------------------- 1 | //! Component configuration variables. 2 | //! 3 | //! Component variables must be defined in the application 4 | //! manifest, in the `[component..variables]` section. 5 | //! Component variables typically use template syntax to 6 | //! derive values from application variables, which are 7 | //! the only variables that may be overridden directly (for 8 | //! example, on the Spin command line). 9 | //! 10 | //! # Examples 11 | //! 12 | //! Get the value of a component variable. 13 | //! 14 | //! ```no_run 15 | //! # fn main() -> anyhow::Result<()> { 16 | //! let region = spin_sdk::variables::get("region_id")?; 17 | //! let regional_url = format!("https://{region}.db.example.com"); 18 | //! # Ok(()) 19 | //! # } 20 | //! ``` 21 | //! 22 | //! Fail gracefully if a variable is not set. 23 | //! 24 | //! ```no_run 25 | //! use spin_sdk::variables::Error; 26 | //! 27 | //! # fn main() -> anyhow::Result<()> { 28 | //! let favourite = match spin_sdk::variables::get("favourite") { 29 | //! Ok(value) => value, 30 | //! Err(Error::Undefined(_)) => "not playing favourites".to_owned(), 31 | //! Err(e) => anyhow::bail!(e), 32 | //! }; 33 | //! # Ok(()) 34 | //! # } 35 | //! ``` 36 | 37 | /// Get the value of a component variable. 38 | /// 39 | /// # Examples 40 | /// 41 | /// Get the value of a component variable. 42 | /// 43 | /// ```no_run 44 | /// # fn main() -> anyhow::Result<()> { 45 | /// let region = spin_sdk::variables::get("region_id")?; 46 | /// let regional_url = format!("https://{region}.db.example.com"); 47 | /// # Ok(()) 48 | /// # } 49 | /// ``` 50 | /// 51 | /// Fail gracefully if a variable is not set. 52 | /// 53 | /// ```no_run 54 | /// use spin_sdk::variables::Error; 55 | /// 56 | /// # fn main() -> anyhow::Result<()> { 57 | /// let favourite = match spin_sdk::variables::get("favourite") { 58 | /// Ok(value) => value, 59 | /// Err(Error::Undefined(_)) => "not playing favourites".to_owned(), 60 | /// Err(e) => anyhow::bail!(e), 61 | /// }; 62 | /// # Ok(()) 63 | /// # } 64 | /// ``` 65 | #[doc(inline)] 66 | pub use super::wit::v2::variables::get; 67 | 68 | #[doc(inline)] 69 | pub use super::wit::v2::variables::Error; 70 | -------------------------------------------------------------------------------- /wit/deps/clocks-2023-10-18/timezone.wit: -------------------------------------------------------------------------------- 1 | interface timezone { 2 | use wall-clock.{datetime}; 3 | 4 | /// Return information needed to display the given `datetime`. This includes 5 | /// the UTC offset, the time zone name, and a flag indicating whether 6 | /// daylight saving time is active. 7 | /// 8 | /// If the timezone cannot be determined for the given `datetime`, return a 9 | /// `timezone-display` for `UTC` with a `utc-offset` of 0 and no daylight 10 | /// saving time. 11 | display: func(when: datetime) -> timezone-display; 12 | 13 | /// The same as `display`, but only return the UTC offset. 14 | utc-offset: func(when: datetime) -> s32; 15 | 16 | /// Information useful for displaying the timezone of a specific `datetime`. 17 | /// 18 | /// This information may vary within a single `timezone` to reflect daylight 19 | /// saving time adjustments. 20 | record timezone-display { 21 | /// The number of seconds difference between UTC time and the local 22 | /// time of the timezone. 23 | /// 24 | /// The returned value will always be less than 86400 which is the 25 | /// number of seconds in a day (24*60*60). 26 | /// 27 | /// In implementations that do not expose an actual time zone, this 28 | /// should return 0. 29 | utc-offset: s32, 30 | 31 | /// The abbreviated name of the timezone to display to a user. The name 32 | /// `UTC` indicates Coordinated Universal Time. Otherwise, this should 33 | /// reference local standards for the name of the time zone. 34 | /// 35 | /// In implementations that do not expose an actual time zone, this 36 | /// should be the string `UTC`. 37 | /// 38 | /// In time zones that do not have an applicable name, a formatted 39 | /// representation of the UTC offset may be returned, such as `-04:00`. 40 | name: string, 41 | 42 | /// Whether daylight saving time is active. 43 | /// 44 | /// In implementations that do not expose an actual time zone, this 45 | /// should return false. 46 | in-daylight-saving-time: bool, 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /wit/deps/spin-sqlite@3.0.0/sqlite.wit: -------------------------------------------------------------------------------- 1 | package spin:sqlite@3.0.0; 2 | 3 | interface sqlite { 4 | /// A handle to an open sqlite instance 5 | resource connection { 6 | /// Open a connection to a named database instance. 7 | /// 8 | /// If `database` is "default", the default instance is opened. 9 | /// 10 | /// `error::no-such-database` will be raised if the `name` is not recognized. 11 | open: static func(database: string) -> result; 12 | 13 | /// Execute a statement returning back data if there is any 14 | execute: func(statement: string, parameters: list) -> result; 15 | 16 | /// The SQLite rowid of the most recent successful INSERT on the connection, or 0 if 17 | /// there has not yet been an INSERT on the connection. 18 | last-insert-rowid: func() -> s64; 19 | 20 | /// The number of rows modified, inserted or deleted by the most recently completed 21 | /// INSERT, UPDATE or DELETE statement on the connection. 22 | changes: func() -> u64; 23 | } 24 | 25 | /// The set of errors which may be raised by functions in this interface 26 | variant error { 27 | /// The host does not recognize the database name requested. 28 | no-such-database, 29 | /// The requesting component does not have access to the specified database (which may or may not exist). 30 | access-denied, 31 | /// The provided connection is not valid 32 | invalid-connection, 33 | /// The database has reached its capacity 34 | database-full, 35 | /// Some implementation-specific error has occurred (e.g. I/O) 36 | io(string) 37 | } 38 | 39 | /// A result of a query 40 | record query-result { 41 | /// The names of the columns retrieved in the query 42 | columns: list, 43 | /// the row results each containing the values for all the columns for a given row 44 | rows: list, 45 | } 46 | 47 | /// A set of values for each of the columns in a query-result 48 | record row-result { 49 | values: list 50 | } 51 | 52 | /// A single column's result from a database query 53 | variant value { 54 | integer(s64), 55 | real(f64), 56 | text(string), 57 | blob(list), 58 | null 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /examples/postgres-v3/README.md: -------------------------------------------------------------------------------- 1 | # Spin Outbound PostgreSQL example 2 | 3 | This example shows how to access a PostgreSQL database from Spin component. 4 | 5 | ## Prerequisite: Postgres 6 | 7 | This example assumes postgres is running and accessible locally via its standard 5432 port. 8 | 9 | We suggest running the `postgres` Docker container which has the necessary postgres user permissions 10 | already configured. For example: 11 | 12 | ``` 13 | docker run --rm -h 127.0.0.1 -p 5432:5432 -e POSTGRES_HOST_AUTH_METHOD=trust postgres 14 | ``` 15 | 16 | ## Spin up 17 | 18 | Then, run the following from the root of this example: 19 | 20 | ``` 21 | createdb -h localhost -U postgres spin_dev 22 | psql -h localhost -U postgres -d spin_dev -f db/testdata.sql 23 | spin build --up 24 | ``` 25 | 26 | Curl the read route: 27 | 28 | ``` 29 | $ curl -i localhost:3000/read 30 | HTTP/1.1 200 OK 31 | transfer-encoding: chunked 32 | date: Wed, 06 Nov 2024 20:17:03 GMT 33 | 34 | Found 2 article(s) as follows: 35 | article: Article { 36 | id: 1, 37 | title: "My Life as a Goat", 38 | content: "I went to Nepal to live as a goat, and it was much better than being a butler.", 39 | authorname: "E. Blackadder", 40 | published: Date( 41 | 2024-11-05, 42 | ), 43 | coauthor: None, 44 | } 45 | article: Article { 46 | id: 2, 47 | title: "Magnificent Octopus", 48 | content: "Once upon a time there was a lovely little sausage.", 49 | authorname: "S. Baldrick", 50 | published: Date( 51 | 2024-11-06, 52 | ), 53 | coauthor: None, 54 | } 55 | 56 | (Column info: id:DbDataType::Int32, title:DbDataType::Str, content:DbDataType::Str, authorname:DbDataType::Str, published:DbDataType::Date, coauthor:DbDataType::Str) 57 | ``` 58 | 59 | Curl the write route: 60 | 61 | ``` 62 | $ curl -i localhost:3000/write 63 | HTTP/1.1 200 OK 64 | content-length: 9 65 | date: Sun, 25 Sep 2022 15:46:22 GMT 66 | 67 | Count: 3 68 | ``` 69 | 70 | Curl the write_datetime_info route to experiment with date time types: 71 | ``` 72 | $ curl -i localhost:3000/write_datetime_info 73 | HTTP/1.1 200 OK 74 | content-length: 9 75 | date: Sun, 25 Sep 2022 15:46:22 GMT 76 | 77 | Count: 4 78 | ``` 79 | 80 | Read endpoint should now also show a row with publisheddate, publishedtime, publisheddatetime and readtime values. 81 | -------------------------------------------------------------------------------- /wit/deps/keyvalue-2024-10-17/atomic.wit: -------------------------------------------------------------------------------- 1 | /// A keyvalue interface that provides atomic operations. 2 | /// 3 | /// Atomic operations are single, indivisible operations. When a fault causes an atomic operation to 4 | /// fail, it will appear to the invoker of the atomic operation that the action either completed 5 | /// successfully or did nothing at all. 6 | /// 7 | /// Please note that this interface is bare functions that take a reference to a bucket. This is to 8 | /// get around the current lack of a way to "extend" a resource with additional methods inside of 9 | /// wit. Future version of the interface will instead extend these methods on the base `bucket` 10 | /// resource. 11 | interface atomics { 12 | use store.{bucket, error}; 13 | 14 | /// The error returned by a CAS operation 15 | variant cas-error { 16 | /// A store error occurred when performing the operation 17 | store-error(error), 18 | /// The CAS operation failed because the value was too old. This returns a new CAS handle 19 | /// for easy retries. Implementors MUST return a CAS handle that has been updated to the 20 | /// latest version or transaction. 21 | cas-failed(cas), 22 | } 23 | 24 | /// A handle to a CAS (compare-and-swap) operation. 25 | resource cas { 26 | /// Construct a new CAS operation. Implementors can map the underlying functionality 27 | /// (transactions, versions, etc) as desired. 28 | new: static func(bucket: borrow, key: string) -> result; 29 | /// Get the current value of the key (if it exists). This allows for avoiding reads if all 30 | /// that is needed to ensure the atomicity of the operation 31 | current: func() -> result>, error>; 32 | } 33 | 34 | /// Atomically increment the value associated with the key in the store by the given delta. It 35 | /// returns the new value. 36 | /// 37 | /// If the key does not exist in the store, it creates a new key-value pair with the value set 38 | /// to the given delta. 39 | /// 40 | /// If any other error occurs, it returns an `Err(error)`. 41 | increment: func(bucket: borrow, key: string, delta: s64) -> result; 42 | 43 | /// Perform the swap on a CAS operation. This consumes the CAS handle and returns an error if 44 | /// the CAS operation failed. 45 | swap: func(cas: cas, value: list) -> result<_, cas-error>; 46 | } 47 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | tags: 5 | - "v*" 6 | 7 | env: 8 | RUST_VERSION: 1.86 9 | 10 | jobs: 11 | crates: 12 | name: Publish to crates.io 13 | runs-on: ubuntu-latest 14 | permissions: 15 | id-token: write # required for OIDC token exchange (crates-io-auth-action) 16 | if: github.repository_owner == 'spinframework' 17 | steps: 18 | - uses: actions/checkout@v4 19 | 20 | - name: Install Rust toolchain 21 | shell: bash 22 | run: | 23 | rustup toolchain install ${{ env.RUST_VERSION }} 24 | rustup default ${{ env.RUST_VERSION }} 25 | 26 | - uses: rust-lang/crates-io-auth-action@v1.0.1 27 | id: auth 28 | 29 | - name: Publish spin-wasip3-http-macro to crates.io 30 | working-directory: ./crates/spin-wasip3-http-macro 31 | run: cargo publish 32 | env: 33 | CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }} 34 | 35 | - name: Publish spin-wasip3-http to crates.io 36 | working-directory: ./crates/spin-wasip3-http 37 | run: cargo publish 38 | env: 39 | CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }} 40 | 41 | - name: Publish spin-executor to crates.io 42 | working-directory: ./crates/executor 43 | run: cargo publish 44 | env: 45 | CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }} 46 | 47 | - name: Publish spin-macro to crates.io 48 | working-directory: ./crates/macro 49 | run: cargo publish 50 | env: 51 | CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }} 52 | 53 | - name: Publish spin-sdk to crates.io 54 | working-directory: ./ 55 | run: cargo publish 56 | env: 57 | CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }} 58 | 59 | dispatch-rust-sdk-release: 60 | name: Dispatch rust-sdk-release event to spinframework/spin 61 | needs: crates 62 | runs-on: ubuntu-latest 63 | if: github.repository_owner == 'spinframework' && startsWith(github.ref, 'refs/tags/v') 64 | steps: 65 | - name: Repository Dispatch 66 | uses: peter-evans/repository-dispatch@v3 67 | with: 68 | token: ${{ secrets.SPIN_REPO_PAT }} 69 | repository: spinframework/spin 70 | event-type: rust-sdk-release 71 | client-payload: '{"version": "${{ github.ref_name }}"}' -------------------------------------------------------------------------------- /wit/deps/spin@2.0.0/llm.wit: -------------------------------------------------------------------------------- 1 | // A WASI interface dedicated to performing inferencing for Large Language Models. 2 | interface llm { 3 | /// A Large Language Model. 4 | type inferencing-model = string; 5 | 6 | /// Inference request parameters 7 | record inferencing-params { 8 | /// The maximum tokens that should be inferred. 9 | /// 10 | /// Note: the backing implementation may return less tokens. 11 | max-tokens: u32, 12 | /// The amount the model should avoid repeating tokens. 13 | repeat-penalty: f32, 14 | /// The number of tokens the model should apply the repeat penalty to. 15 | repeat-penalty-last-n-token-count: u32, 16 | /// The randomness with which the next token is selected. 17 | temperature: f32, 18 | /// The number of possible next tokens the model will choose from. 19 | top-k: u32, 20 | /// The probability total of next tokens the model will choose from. 21 | top-p: f32 22 | } 23 | 24 | /// The set of errors which may be raised by functions in this interface 25 | variant error { 26 | model-not-supported, 27 | runtime-error(string), 28 | invalid-input(string) 29 | } 30 | 31 | /// An inferencing result 32 | record inferencing-result { 33 | /// The text generated by the model 34 | // TODO: this should be a stream 35 | text: string, 36 | /// Usage information about the inferencing request 37 | usage: inferencing-usage 38 | } 39 | 40 | /// Usage information related to the inferencing result 41 | record inferencing-usage { 42 | /// Number of tokens in the prompt 43 | prompt-token-count: u32, 44 | /// Number of tokens generated by the inferencing operation 45 | generated-token-count: u32 46 | } 47 | 48 | /// Perform inferencing using the provided model and prompt with the given optional params 49 | infer: func(model: inferencing-model, prompt: string, params: option) -> result; 50 | 51 | /// The model used for generating embeddings 52 | type embedding-model = string; 53 | 54 | /// Generate embeddings for the supplied list of text 55 | generate-embeddings: func(model: embedding-model, text: list) -> result; 56 | 57 | /// Result of generating embeddings 58 | record embeddings-result { 59 | /// The embeddings generated by the request 60 | embeddings: list>, 61 | /// Usage related to the embeddings generation request 62 | usage: embeddings-usage 63 | } 64 | 65 | /// Usage related to an embeddings generation request 66 | record embeddings-usage { 67 | /// Number of tokens in the prompt 68 | prompt-token-count: u32, 69 | } 70 | } 71 | --------------------------------------------------------------------------------