├── .gitignore ├── 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 │ │ ├── llm.wit │ │ └── key-value.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 │ ├── cli-0.3.0-rc-2025-09-16 │ │ ├── run.wit │ │ ├── command.wit │ │ ├── exit.wit │ │ ├── environment.wit │ │ ├── imports.wit │ │ ├── terminal.wit │ │ └── stdio.wit │ ├── sockets │ │ ├── world.wit │ │ ├── instance-network.wit │ │ ├── udp-create-socket.wit │ │ ├── tcp-create-socket.wit │ │ ├── ip-name-lookup.wit │ │ └── network.wit │ ├── sockets-2023-10-18 │ │ ├── instance-network.wit │ │ ├── world.wit │ │ ├── udp-create-socket.wit │ │ ├── tcp-create-socket.wit │ │ ├── ip-name-lookup.wit │ │ └── network.wit │ ├── sockets-2023-11-10 │ │ ├── instance-network.wit │ │ ├── world.wit │ │ ├── udp-create-socket.wit │ │ ├── tcp-create-socket.wit │ │ ├── ip-name-lookup.wit │ │ └── network.wit │ ├── filesystem-0.3.0-rc-2025-09-16 │ │ ├── world.wit │ │ └── preopens.wit │ ├── sockets-0.3.0-rc-2025-09-16 │ │ ├── world.wit │ │ └── ip-name-lookup.wit │ ├── clocks-0.3.0-rc-2025-09-16 │ │ ├── types.wit │ │ ├── world.wit │ │ ├── monotonic-clock.wit │ │ ├── wall-clock.wit │ │ └── timezone.wit │ ├── random-0.3.0-rc-2025-09-16 │ │ ├── world.wit │ │ ├── insecure.wit │ │ ├── insecure-seed.wit │ │ └── random.wit │ ├── spin@3.0.0 │ │ └── world.wit │ ├── spin@3.2.0 │ │ └── world.wit │ ├── spin@3.4.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 │ │ └── redis.wit │ ├── keyvalue-2024-10-17 │ │ ├── watch.wit │ │ ├── world.wit │ │ ├── atomic.wit │ │ ├── batch.wit │ │ └── store.wit │ ├── http-0.3.0-rc-2025-09-16 │ │ ├── handler.wit │ │ └── proxy.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 │ ├── spin-postgres@3.0.0 │ │ └── postgres.wit │ └── spin-postgres@4.0.0 │ │ └── postgres.wit └── world.wit ├── example-app ├── src │ └── lib.rs └── Cargo.toml ├── compose.yaml ├── Cargo.toml ├── compose.wac ├── github-oauth ├── src │ ├── api │ │ ├── login.rs │ │ ├── authorize.rs │ │ ├── authenticate.rs │ │ └── callback.rs │ ├── response.rs │ ├── lib.rs │ └── api.rs ├── Cargo.toml └── login.html ├── .vscode └── settings.json ├── spin.toml ├── .devcontainer ├── Dockerfile └── devcontainer.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .spin/ 3 | p3/ 4 | -------------------------------------------------------------------------------- /wit/deps/io-2023-10-18/world.wit: -------------------------------------------------------------------------------- 1 | package wasi:io@0.2.0-rc-2023-10-18; 2 | -------------------------------------------------------------------------------- /wit/deps/cli/run.wit: -------------------------------------------------------------------------------- 1 | interface run { 2 | /// Run the program. 3 | run: func() -> result; 4 | } 5 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /wit/deps/cli-0.3.0-rc-2025-09-16/run.wit: -------------------------------------------------------------------------------- 1 | @since(version = 0.3.0-rc-2025-09-16) 2 | interface run { 3 | /// Run the program. 4 | @since(version = 0.3.0-rc-2025-09-16) 5 | run: async func() -> result; 6 | } 7 | -------------------------------------------------------------------------------- /example-app/src/lib.rs: -------------------------------------------------------------------------------- 1 | use spin_sdk::http_wasip3::{Request, IntoResponse}; 2 | 3 | #[spin_sdk::http_wasip3::http_service] 4 | async fn handle(_request: Request) -> impl IntoResponse { 5 | "Business logic executed!\n" 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 | -------------------------------------------------------------------------------- /example-app/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-app" 3 | description = "A simple HTTP handler" 4 | version = "0.1.0" 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = [ "cdylib" ] 9 | 10 | [dependencies] 11 | spin-sdk = { workspace = true } 12 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/cli-0.3.0-rc-2025-09-16/command.wit: -------------------------------------------------------------------------------- 1 | package wasi:cli@0.3.0-rc-2025-09-16; 2 | 3 | @since(version = 0.3.0-rc-2025-09-16) 4 | world command { 5 | @since(version = 0.3.0-rc-2025-09-16) 6 | include imports; 7 | 8 | @since(version = 0.3.0-rc-2025-09-16) 9 | export run; 10 | } 11 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /wit/deps/filesystem-0.3.0-rc-2025-09-16/world.wit: -------------------------------------------------------------------------------- 1 | package wasi:filesystem@0.3.0-rc-2025-09-16; 2 | 3 | @since(version = 0.3.0-rc-2025-09-16) 4 | world imports { 5 | @since(version = 0.3.0-rc-2025-09-16) 6 | import types; 7 | @since(version = 0.3.0-rc-2025-09-16) 8 | import preopens; 9 | } 10 | -------------------------------------------------------------------------------- /wit/deps/sockets-0.3.0-rc-2025-09-16/world.wit: -------------------------------------------------------------------------------- 1 | package wasi:sockets@0.3.0-rc-2025-09-16; 2 | 3 | @since(version = 0.3.0-rc-2025-09-16) 4 | world imports { 5 | @since(version = 0.3.0-rc-2025-09-16) 6 | import types; 7 | @since(version = 0.3.0-rc-2025-09-16) 8 | import ip-name-lookup; 9 | } 10 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /wit/world.wit: -------------------------------------------------------------------------------- 1 | package double:http; 2 | 3 | interface chain-http { 4 | use wasi:http/types@0.3.0-rc-2025-09-16.{request, response, error-code}; 5 | 6 | handle: async func( 7 | request: request, 8 | ) -> result; 9 | } 10 | 11 | world middleware { 12 | import chain-http; 13 | } 14 | -------------------------------------------------------------------------------- /wit/deps/clocks-0.3.0-rc-2025-09-16/types.wit: -------------------------------------------------------------------------------- 1 | package wasi:clocks@0.3.0-rc-2025-09-16; 2 | /// This interface common types used throughout wasi:clocks. 3 | @since(version = 0.3.0-rc-2025-09-16) 4 | interface types { 5 | /// A duration of time, in nanoseconds. 6 | @since(version = 0.3.0-rc-2025-09-16) 7 | type duration = u64; 8 | } 9 | -------------------------------------------------------------------------------- /compose.yaml: -------------------------------------------------------------------------------- 1 | skip-validation: true 2 | dependencies: 3 | "double:http/chain-http": ./target/wasm32-wasip2/release/example_app.wasm 4 | instantiations: 5 | root: 6 | dependency: "double:http/chain-http" 7 | arguments: 8 | "double:http/chain-http": 9 | instance: "double:http/chain-http" 10 | export: "wasi:http/handler@0.3.0-rc-2025-09-16" 11 | -------------------------------------------------------------------------------- /wit/deps/clocks-0.3.0-rc-2025-09-16/world.wit: -------------------------------------------------------------------------------- 1 | package wasi:clocks@0.3.0-rc-2025-09-16; 2 | 3 | @since(version = 0.3.0-rc-2025-09-16) 4 | world imports { 5 | @since(version = 0.3.0-rc-2025-09-16) 6 | import monotonic-clock; 7 | @since(version = 0.3.0-rc-2025-09-16) 8 | import wall-clock; 9 | @unstable(feature = clocks-timezone) 10 | import timezone; 11 | } 12 | -------------------------------------------------------------------------------- /wit/deps/random-0.3.0-rc-2025-09-16/world.wit: -------------------------------------------------------------------------------- 1 | package wasi:random@0.3.0-rc-2025-09-16; 2 | 3 | @since(version = 0.3.0-rc-2025-09-16) 4 | world imports { 5 | @since(version = 0.3.0-rc-2025-09-16) 6 | import random; 7 | 8 | @since(version = 0.3.0-rc-2025-09-16) 9 | import insecure; 10 | 11 | @since(version = 0.3.0-rc-2025-09-16) 12 | import insecure-seed; 13 | } 14 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "github-oauth", 4 | "example-app", 5 | ] 6 | resolver = "2" 7 | 8 | [workspace.dependencies] 9 | anyhow = "1.98" 10 | http = "1.0.0" 11 | spin-sdk = { version = "5.1.0", features = ["wasip3-unstable"] } 12 | spin-executor = "3.0.1" 13 | 14 | [profile.release] 15 | codegen-units = 1 16 | opt-level = "s" 17 | debug = false 18 | strip = true 19 | lto = true -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /compose.wac: -------------------------------------------------------------------------------- 1 | // TODO: this doesn't work yet because WAC support for WASI P3 2 | // is not complete. So unused for now but hopefully we can switch 3 | // over in the near future. 4 | 5 | package authenticated:example@0.1.0; 6 | 7 | let bizzo = new business:logic { ... }; 8 | 9 | let composed = new middle:ware { 10 | "double:http/chain-http": bizzo["wasi:http/handler@0.3.0-rc-2025-09-16"], ... 11 | }; 12 | 13 | export composed...; 14 | -------------------------------------------------------------------------------- /wit/deps/filesystem-0.3.0-rc-2025-09-16/preopens.wit: -------------------------------------------------------------------------------- 1 | package wasi:filesystem@0.3.0-rc-2025-09-16; 2 | 3 | @since(version = 0.3.0-rc-2025-09-16) 4 | interface preopens { 5 | @since(version = 0.3.0-rc-2025-09-16) 6 | use types.{descriptor}; 7 | 8 | /// Return the set of preopened directories, and their paths. 9 | @since(version = 0.3.0-rc-2025-09-16) 10 | get-directories: func() -> list>; 11 | } 12 | -------------------------------------------------------------------------------- /github-oauth/src/api/login.rs: -------------------------------------------------------------------------------- 1 | use spin_sdk::http_wasip3::{FullBody, IntoResponse}; 2 | 3 | /// `login` returns the login page. 4 | pub async fn login() -> impl IntoResponse { 5 | const LOGIN_HTML: &[u8] = include_bytes!("../../login.html"); // TODO: this shouldn't be included statically. 6 | 7 | http::Response::builder() 8 | .status(200) 9 | .header("content-type", "text/html") 10 | .body(FullBody::new(LOGIN_HTML)) 11 | } 12 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[rust]": { 3 | "editor.defaultFormatter": "rust-lang.rust-analyzer" 4 | }, 5 | "rust-analyzer.linkedProjects": [ 6 | "github-oauth/Cargo.toml", 7 | "example-app/Cargo.toml" 8 | ], 9 | "rust-analyzer.checkOnSave": true, 10 | "rust-analyzer.check.overrideCommand": ["cargo", "component", "check", "--message-format=json"], 11 | // VS Code don't watch files under ./target 12 | "files.watcherExclude": { 13 | "**/target/**": true 14 | } 15 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /wit/deps/spin@3.4.0/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /github-oauth/src/response.rs: -------------------------------------------------------------------------------- 1 | use spin_sdk::http_wasip3::{EmptyBody, IntoResponse}; 2 | 3 | /// 307 Temporary Redirect with no header other than Location 4 | pub fn temporary_redirect(location: &str) -> impl IntoResponse { 5 | http::Response::builder() 6 | .status(307) 7 | .header("Location", location) 8 | .body(EmptyBody::new()) 9 | } 10 | 11 | /// 403 Forbidden with no message body 12 | pub fn forbidden() -> impl IntoResponse { 13 | http::Response::builder().status(403).body(EmptyBody::new()) 14 | } 15 | 16 | /// 500 Internal Server Error with no message body 17 | pub fn internal_server_error() -> impl IntoResponse { 18 | http::Response::builder().status(500).body(EmptyBody::new()) 19 | } 20 | -------------------------------------------------------------------------------- /spin.toml: -------------------------------------------------------------------------------- 1 | spin_manifest_version = 2 2 | 3 | [application] 4 | name = "github-oauth2-example" 5 | version = "0.1.0" 6 | description = "A simple HTTP handler" 7 | 8 | [[trigger.http]] 9 | route = "/..." 10 | component = "frontend" 11 | executor = { type = "wasip3-unstable" } 12 | 13 | [component.frontend] 14 | source = "p3/authed_app.wasm" # for P3 we have to post-process compose in build.sh 15 | allowed_outbound_hosts = ["https://github.com", "https://api.github.com"] 16 | [component.frontend.build] 17 | command = "./build.sh" 18 | # Unused because for now we have to precompose in build.sh 19 | # [component.frontend.dependencies] 20 | # "wasi:http/incoming-handler@0.2.0" = { path = "target/wasm32-wasip1/release/example.wasm" } 21 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /github-oauth/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "github-oauth" 3 | description = "An HTTP authentication middleware component." 4 | version = "0.1.0" 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["cdylib"] 9 | 10 | [dependencies] 11 | anyhow = "1" 12 | bytes = "1" 13 | cookie = "0.18" 14 | futures = "0.3.28" 15 | http = "1.3.1" 16 | json = "0.12.4" 17 | oauth2 = { git = "https://github.com/ramosbugs/oauth2-rs", rev = "c74aec9", default-features = false } 18 | serde = { version = "1.0.190", features = ["derive"] } 19 | serde_json = "1.0.108" 20 | spin-sdk = { workspace = true } 21 | url = "2.4.0" 22 | wit-bindgen = "0.46" 23 | 24 | [features] 25 | # Inject oauth credentials environment variables at compile time rather than runtime 26 | compile-time-secrets = [] -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /wit/deps/cli-0.3.0-rc-2025-09-16/exit.wit: -------------------------------------------------------------------------------- 1 | @since(version = 0.3.0-rc-2025-09-16) 2 | interface exit { 3 | /// Exit the current instance and any linked instances. 4 | @since(version = 0.3.0-rc-2025-09-16) 5 | exit: func(status: result); 6 | 7 | /// Exit the current instance and any linked instances, reporting the 8 | /// specified status code to the host. 9 | /// 10 | /// The meaning of the code depends on the context, with 0 usually meaning 11 | /// "success", and other values indicating various types of failure. 12 | /// 13 | /// This function does not return; the effect is analogous to a trap, but 14 | /// without the connotation that something bad has happened. 15 | @unstable(feature = cli-exit-with-code) 16 | exit-with-code: func(status-code: u8); 17 | } 18 | -------------------------------------------------------------------------------- /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-oauth/src/lib.rs: -------------------------------------------------------------------------------- 1 | use spin_sdk::http_wasip3::{Request, IntoResponse}; 2 | 3 | mod api; 4 | mod response; 5 | 6 | wit_bindgen::generate!({ 7 | path: "../wit", 8 | world: "double:http/middleware", 9 | async: true, 10 | with: { 11 | "wasi:http/types@0.3.0-rc-2025-09-16": spin_sdk::http_wasip3::wasip3::http::types, 12 | }, 13 | generate_all, 14 | }); 15 | 16 | #[spin_sdk::http_wasip3::http_service] 17 | async fn handle(request: Request) -> impl IntoResponse { 18 | match request.uri().path() { 19 | "/login/authorize" => api::authorize().await.into_response(), 20 | "/login/callback" => api::callback(request.uri()).await.into_response(), 21 | "/login" => api::login().await.into_response(), 22 | _ => api::authenticate(request).await.into_response(), 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /github-oauth/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | OAuth Middleware Example 5 | 6 | 7 | 8 | 9 |
10 |
11 |
12 |
13 |

15 | 17 | Login with GitHub 18 | 19 |

20 |

21 |
22 |
23 |
24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /wit/deps/http-0.3.0-rc-2025-09-16/handler.wit: -------------------------------------------------------------------------------- 1 | /// This interface defines a handler of HTTP Requests. It may be imported by 2 | /// components which wish to send HTTP Requests and also exported by components 3 | /// which can respond to HTTP Requests. In addition, it may be used to pass 4 | /// a request from one component to another without any use of a network. 5 | interface handler { 6 | use types.{request, response, error-code}; 7 | 8 | /// When exported, this function may be called with either an incoming 9 | /// request read from the network or a request synthesized or forwarded by 10 | /// another component. 11 | /// 12 | /// When imported, this function may be used to either send an outgoing 13 | /// request over the network or pass it to another component. 14 | handle: async func( 15 | request: request, 16 | ) -> result; 17 | } 18 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /wit/deps/cli-0.3.0-rc-2025-09-16/environment.wit: -------------------------------------------------------------------------------- 1 | @since(version = 0.3.0-rc-2025-09-16) 2 | interface environment { 3 | /// Get the POSIX-style environment variables. 4 | /// 5 | /// Each environment variable is provided as a pair of string variable names 6 | /// and string value. 7 | /// 8 | /// Morally, these are a value import, but until value imports are available 9 | /// in the component model, this import function should return the same 10 | /// values each time it is called. 11 | @since(version = 0.3.0-rc-2025-09-16) 12 | get-environment: func() -> list>; 13 | 14 | /// Get the POSIX-style arguments to the program. 15 | @since(version = 0.3.0-rc-2025-09-16) 16 | get-arguments: func() -> list; 17 | 18 | /// Return a path that programs should use as their initial current working 19 | /// directory, interpreting `.` as shorthand for this. 20 | @since(version = 0.3.0-rc-2025-09-16) 21 | get-initial-cwd: func() -> option; 22 | } 23 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /wit/deps/random-0.3.0-rc-2025-09-16/insecure.wit: -------------------------------------------------------------------------------- 1 | package wasi:random@0.3.0-rc-2025-09-16; 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 | @since(version = 0.3.0-rc-2025-09-16) 7 | interface insecure { 8 | /// Return `len` insecure pseudo-random bytes. 9 | /// 10 | /// This function is not cryptographically secure. Do not use it for 11 | /// anything related to security. 12 | /// 13 | /// There are no requirements on the values of the returned bytes, however 14 | /// implementations are encouraged to return evenly distributed values with 15 | /// a long period. 16 | @since(version = 0.3.0-rc-2025-09-16) 17 | get-insecure-random-bytes: func(len: u64) -> list; 18 | 19 | /// Return an insecure pseudo-random `u64` value. 20 | /// 21 | /// This function returns the same type of pseudo-random data as 22 | /// `get-insecure-random-bytes`, represented as a `u64`. 23 | @since(version = 0.3.0-rc-2025-09-16) 24 | get-insecure-random-u64: func() -> u64; 25 | } 26 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/cli-0.3.0-rc-2025-09-16/imports.wit: -------------------------------------------------------------------------------- 1 | package wasi:cli@0.3.0-rc-2025-09-16; 2 | 3 | @since(version = 0.3.0-rc-2025-09-16) 4 | world imports { 5 | @since(version = 0.3.0-rc-2025-09-16) 6 | include wasi:clocks/imports@0.3.0-rc-2025-09-16; 7 | @since(version = 0.3.0-rc-2025-09-16) 8 | include wasi:filesystem/imports@0.3.0-rc-2025-09-16; 9 | @since(version = 0.3.0-rc-2025-09-16) 10 | include wasi:sockets/imports@0.3.0-rc-2025-09-16; 11 | @since(version = 0.3.0-rc-2025-09-16) 12 | include wasi:random/imports@0.3.0-rc-2025-09-16; 13 | 14 | @since(version = 0.3.0-rc-2025-09-16) 15 | import environment; 16 | @since(version = 0.3.0-rc-2025-09-16) 17 | import exit; 18 | @since(version = 0.3.0-rc-2025-09-16) 19 | import stdin; 20 | @since(version = 0.3.0-rc-2025-09-16) 21 | import stdout; 22 | @since(version = 0.3.0-rc-2025-09-16) 23 | import stderr; 24 | @since(version = 0.3.0-rc-2025-09-16) 25 | import terminal-input; 26 | @since(version = 0.3.0-rc-2025-09-16) 27 | import terminal-output; 28 | @since(version = 0.3.0-rc-2025-09-16) 29 | import terminal-stdin; 30 | @since(version = 0.3.0-rc-2025-09-16) 31 | import terminal-stdout; 32 | @since(version = 0.3.0-rc-2025-09-16) 33 | import terminal-stderr; 34 | } 35 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /wit/deps/random-0.3.0-rc-2025-09-16/insecure-seed.wit: -------------------------------------------------------------------------------- 1 | package wasi:random@0.3.0-rc-2025-09-16; 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 | @since(version = 0.3.0-rc-2025-09-16) 7 | interface insecure-seed { 8 | /// Return a 128-bit value that may contain a pseudo-random value. 9 | /// 10 | /// The returned value is not required to be computed from a CSPRNG, and may 11 | /// even be entirely deterministic. Host implementations are encouraged to 12 | /// provide pseudo-random values to any program exposed to 13 | /// attacker-controlled content, to enable DoS protection built into many 14 | /// languages' hash-map implementations. 15 | /// 16 | /// This function is intended to only be called once, by a source language 17 | /// to initialize Denial Of Service (DoS) protection in its hash-map 18 | /// implementation. 19 | /// 20 | /// # Expected future evolution 21 | /// 22 | /// This will likely be changed to a value import, to prevent it from being 23 | /// called multiple times and potentially used for purposes other than DoS 24 | /// protection. 25 | @since(version = 0.3.0-rc-2025-09-16) 26 | get-insecure-seed: func() -> tuple; 27 | } 28 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /wit/deps/random-0.3.0-rc-2025-09-16/random.wit: -------------------------------------------------------------------------------- 1 | package wasi:random@0.3.0-rc-2025-09-16; 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 | @since(version = 0.3.0-rc-2025-09-16) 7 | interface random { 8 | /// Return `len` cryptographically-secure random or pseudo-random bytes. 9 | /// 10 | /// This function must produce data at least as cryptographically secure and 11 | /// fast as an adequately seeded cryptographically-secure pseudo-random 12 | /// number generator (CSPRNG). It must not block, from the perspective of 13 | /// the calling program, under any circumstances, including on the first 14 | /// request and on requests for numbers of bytes. The returned data must 15 | /// always be unpredictable. 16 | /// 17 | /// This function must always return fresh data. Deterministic environments 18 | /// must omit this function, rather than implementing it with deterministic 19 | /// data. 20 | @since(version = 0.3.0-rc-2025-09-16) 21 | get-random-bytes: func(len: u64) -> list; 22 | 23 | /// Return a cryptographically-secure random or pseudo-random `u64` value. 24 | /// 25 | /// This function returns the same type of data as `get-random-bytes`, 26 | /// represented as a `u64`. 27 | @since(version = 0.3.0-rc-2025-09-16) 28 | get-random-u64: func() -> u64; 29 | } 30 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /github-oauth/src/api/authorize.rs: -------------------------------------------------------------------------------- 1 | use super::OAuth2; 2 | use crate::response; 3 | 4 | use oauth2::{basic, CsrfToken, Scope}; 5 | use spin_sdk::http_wasip3::IntoResponse; 6 | 7 | /// `authorize` kicks off the oauth flow constructing the authorization url and redirecting the client to github 8 | /// to authorize the application to the user's profile. 9 | pub async fn authorize() -> impl IntoResponse { 10 | let client = match OAuth2::try_init() { 11 | Ok(config) => basic::BasicClient::new(config.client_id) 12 | .set_client_secret(config.client_secret) 13 | .set_auth_uri(config.auth_url) 14 | .set_token_uri(config.token_url) 15 | .set_redirect_uri(config.redirect_url) 16 | .set_auth_type(oauth2::AuthType::RequestBody), 17 | Err(error) => { 18 | eprintln!("failed to initialize oauth client: {error}"); 19 | return response::internal_server_error().into_response(); 20 | } 21 | }; 22 | 23 | // Generate the authorization URL to which we'll redirect the user. 24 | let (authorize_url, _csrf_state) = client 25 | .authorize_url(CsrfToken::new_random) 26 | // This example is requesting access to the user's email. 27 | .add_scope(Scope::new("user:email".to_string())) 28 | .url(); 29 | 30 | // TODO: cache the csrf token for validation on callback 31 | eprintln!("redirecting to {authorize_url}"); 32 | 33 | response::temporary_redirect(authorize_url.as_str()).into_response() 34 | } 35 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | # The goal of this Dockerfile is to be architecture independent. To that end, 2 | # it avoids downloading any platform-specific binaries, and installs the required 3 | # tools either through Debian's package manager, or through installation scripts 4 | # that download the appropriate binaries. 5 | 6 | # If the examples in this repository require it, update this Dockerfile to install 7 | # more language toolchains (such as .NET or TinyGo). 8 | 9 | FROM --platform=linux/amd64 ubuntu:22.04 10 | 11 | RUN apt-get update && apt-get install -y \ 12 | bash \ 13 | git \ 14 | curl \ 15 | nodejs \ 16 | npm \ 17 | golang-go \ 18 | build-essential libssl-dev pkg-config\ 19 | glibc-source \ 20 | ca-certificates \ 21 | tree \ 22 | wget 23 | 24 | # Install Rust 25 | RUN curl https://sh.rustup.rs -sSf | bash -s -- -y 26 | ENV PATH="/root/.cargo/bin:${PATH}" 27 | RUN rustup target add wasm32-wasip2 28 | 29 | # Install WAC 30 | cargo install wac-cli --locked 31 | 32 | # Install the gopls Go Language Server, see https://github.com/golang/tools/tree/master/gopls 33 | RUN go install golang.org/x/tools/gopls@latest 34 | 35 | # Install Spin and required plugins 36 | RUN (curl -fsSL https://developer.fermyon.com/downloads/install.sh | bash -s -- -v v2.0.0-rc.1 && mv spin /usr/local/bin/) && \ 37 | spin templates install --git https://github.com/radu-matei/spin-kv-explorer --update && \ 38 | spin templates install --git https://github.com/radu-matei/spin-nextjs --update 39 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/clocks-0.3.0-rc-2025-09-16/monotonic-clock.wit: -------------------------------------------------------------------------------- 1 | package wasi:clocks@0.3.0-rc-2025-09-16; 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 | @since(version = 0.3.0-rc-2025-09-16) 11 | interface monotonic-clock { 12 | use types.{duration}; 13 | 14 | /// An instant in time, in nanoseconds. An instant is relative to an 15 | /// unspecified initial value, and can only be compared to instances from 16 | /// the same monotonic-clock. 17 | @since(version = 0.3.0-rc-2025-09-16) 18 | type instant = u64; 19 | 20 | /// Read the current value of the clock. 21 | /// 22 | /// The clock is monotonic, therefore calling this function repeatedly will 23 | /// produce a sequence of non-decreasing values. 24 | /// 25 | /// For completeness, this function traps if it's not possible to represent 26 | /// the value of the clock in an `instant`. Consequently, implementations 27 | /// should ensure that the starting time is low enough to avoid the 28 | /// possibility of overflow in practice. 29 | @since(version = 0.3.0-rc-2025-09-16) 30 | now: func() -> instant; 31 | 32 | /// Query the resolution of the clock. Returns the duration of time 33 | /// corresponding to a clock tick. 34 | @since(version = 0.3.0-rc-2025-09-16) 35 | get-resolution: func() -> duration; 36 | 37 | /// Wait until the specified instant has occurred. 38 | @since(version = 0.3.0-rc-2025-09-16) 39 | wait-until: async func( 40 | when: instant, 41 | ); 42 | 43 | /// Wait for the specified duration to elapse. 44 | @since(version = 0.3.0-rc-2025-09-16) 45 | wait-for: async func( 46 | how-long: duration, 47 | ); 48 | } 49 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /wit/deps/clocks-0.3.0-rc-2025-09-16/wall-clock.wit: -------------------------------------------------------------------------------- 1 | package wasi:clocks@0.3.0-rc-2025-09-16; 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 | @since(version = 0.3.0-rc-2025-09-16) 17 | interface wall-clock { 18 | /// A time and date in seconds plus nanoseconds. 19 | @since(version = 0.3.0-rc-2025-09-16) 20 | record datetime { 21 | seconds: u64, 22 | nanoseconds: u32, 23 | } 24 | 25 | /// Read the current value of the clock. 26 | /// 27 | /// This clock is not monotonic, therefore calling this function repeatedly 28 | /// will not necessarily produce a sequence of non-decreasing values. 29 | /// 30 | /// The returned timestamps represent the number of seconds since 31 | /// 1970-01-01T00:00:00Z, also known as [POSIX's Seconds Since the Epoch], 32 | /// also known as [Unix Time]. 33 | /// 34 | /// The nanoseconds field of the output is always less than 1000000000. 35 | /// 36 | /// [POSIX's Seconds Since the Epoch]: https://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_xbd_chap04.html#tag_21_04_16 37 | /// [Unix Time]: https://en.wikipedia.org/wiki/Unix_time 38 | @since(version = 0.3.0-rc-2025-09-16) 39 | now: func() -> datetime; 40 | 41 | /// Query the resolution of the clock. 42 | /// 43 | /// The nanoseconds field of the output is always less than 1000000000. 44 | @since(version = 0.3.0-rc-2025-09-16) 45 | get-resolution: func() -> datetime; 46 | } 47 | -------------------------------------------------------------------------------- /wit/deps/http-0.3.0-rc-2025-09-16/proxy.wit: -------------------------------------------------------------------------------- 1 | package wasi:http@0.3.0-rc-2025-09-16; 2 | 3 | /// The `wasi:http/imports` world imports all the APIs for HTTP proxies. 4 | /// It is intended to be `include`d in other worlds. 5 | world imports { 6 | /// HTTP proxies have access to time and randomness. 7 | include wasi:clocks/imports@0.3.0-rc-2025-09-16; 8 | import wasi:random/random@0.3.0-rc-2025-09-16; 9 | 10 | /// Proxies have standard output and error streams which are expected to 11 | /// terminate in a developer-facing console provided by the host. 12 | import wasi:cli/stdout@0.3.0-rc-2025-09-16; 13 | import wasi:cli/stderr@0.3.0-rc-2025-09-16; 14 | 15 | /// TODO: this is a temporary workaround until component tooling is able to 16 | /// gracefully handle the absence of stdin. Hosts must return an eof stream 17 | /// for this import, which is what wasi-libc + tooling will do automatically 18 | /// when this import is properly removed. 19 | import wasi:cli/stdin@0.3.0-rc-2025-09-16; 20 | 21 | /// This is the default handler to use when user code simply wants to make an 22 | /// HTTP request (e.g., via `fetch()`). 23 | /// 24 | /// This may also be used to pass synthesized or forwarded requests to another 25 | /// component. 26 | import handler; 27 | } 28 | 29 | /// The `wasi:http/proxy` world captures a widely-implementable intersection of 30 | /// hosts that includes HTTP forward and reverse proxies. Components targeting 31 | /// this world may concurrently stream in and out any number of incoming and 32 | /// outgoing HTTP requests. 33 | world proxy { 34 | include imports; 35 | 36 | /// The host delivers incoming HTTP requests to a component by calling the 37 | /// `handle` function of this exported interface. A host may arbitrarily reuse 38 | /// or not reuse component instance when delivering incoming HTTP requests and 39 | /// thus a component must be able to handle 0..N calls to `handle`. 40 | /// 41 | /// This may also be used to receive synthesized or forwarded requests from 42 | /// another component. 43 | export handler; 44 | } 45 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Fermyon Spin", 3 | // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile 4 | // "image": "ghcr.io/fermyon/workshops/dev-container:20230920-075415-ge7406ad", 5 | "build": { 6 | "dockerfile": "Dockerfile" 7 | }, 8 | "features": { 9 | "ghcr.io/devcontainers/features/docker-outside-of-docker:1": {}, 10 | "ghcr.io/devcontainers/features/common-utils:2": { 11 | "configureZshAsDefaultShell": true 12 | } 13 | }, 14 | 15 | "runArgs": [ 16 | "--cap-add=SYS_PTRACE", 17 | "--security-opt", 18 | "seccomp=unconfined", 19 | "--privileged", 20 | "--init" 21 | ], 22 | 23 | "otherPortsAttributes": { 24 | "onAutoForward": "ignore" 25 | }, 26 | "customizations": { 27 | "vscode": { 28 | "extensions": [ 29 | "mutantdino.resourcemonitor", 30 | "humao.rest-client", 31 | "rust-lang.rust-analyzer", 32 | "tamasfe.even-better-toml", 33 | "golang.Go", 34 | "alexcvzz.vscode-sqlite", 35 | "qwtel.sqlite-viewer", 36 | "bytecodealliance.wit-idl", 37 | "ms-vscode.makefile-tools" 38 | ] 39 | }, 40 | // Use 'mounts' to make the cargo cache persistent in a Docker Volume. 41 | // "mounts": [ 42 | // { 43 | // "source": "devcontainer-cargo-cache-${devcontainerId}", 44 | // "target": "/usr/local/cargo", 45 | // "type": "volume" 46 | // } 47 | // ] 48 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 49 | "forwardPorts": [ 50 | 3000 51 | ] 52 | // Use 'postCreateCommand' to run commands after the container is created. 53 | // "postCreateCommand": "rustc --version", 54 | // Configure tool-specific properties. 55 | // "customizations": {}, 56 | // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. 57 | // "remoteUser": "root" 58 | } 59 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /github-oauth/src/api.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Context; 2 | use oauth2::{AuthUrl, ClientId, ClientSecret, RedirectUrl, TokenUrl}; 3 | 4 | pub use authenticate::authenticate; 5 | pub use authorize::authorize; 6 | pub use callback::callback; 7 | pub use login::login; 8 | 9 | mod authenticate; 10 | mod authorize; 11 | mod callback; 12 | mod login; 13 | 14 | pub struct OAuth2 { 15 | pub client_secret: ClientSecret, 16 | pub client_id: ClientId, 17 | pub auth_url: AuthUrl, 18 | pub token_url: TokenUrl, 19 | pub redirect_url: RedirectUrl, 20 | } 21 | 22 | const AUTH_CALLBACK_URL: Option<&'static str> = option_env!("AUTH_CALLBACK_URL"); 23 | 24 | impl OAuth2 { 25 | pub fn try_init() -> anyhow::Result { 26 | let client_secret_env = option_env!("CLIENT_SECRET"); 27 | let client_id_env = option_env!("CLIENT_ID"); 28 | 29 | let (client_secret, client_id) = if !cfg!(feature = "compile-time-secrets") { 30 | (std::env::var("CLIENT_SECRET")?, std::env::var("CLIENT_ID")?) 31 | } else { 32 | ( 33 | client_secret_env 34 | .context("CLIENT_SECRET was not configured at build time")? 35 | .to_string(), 36 | client_id_env 37 | .context("CLIENT_ID was not configured at build time")? 38 | .to_string(), 39 | ) 40 | }; 41 | 42 | let client_secret = ClientSecret::new(client_secret); 43 | let client_id = ClientId::new(client_id); 44 | let auth_url = AuthUrl::new("https://github.com/login/oauth/authorize".to_string())?; 45 | let token_url = TokenUrl::new("https://github.com/login/oauth/access_token".to_string())?; 46 | 47 | let redirect_url = match std::env::var("AUTH_CALLBACK_URL") { 48 | Ok(runtime_env) => RedirectUrl::new(runtime_env)?, 49 | Err(_) => RedirectUrl::new( 50 | AUTH_CALLBACK_URL 51 | .unwrap_or("http://127.0.0.1:3000/login/callback") 52 | .to_string(), 53 | )?, 54 | }; 55 | 56 | Ok(OAuth2 { 57 | client_secret, 58 | client_id, 59 | token_url, 60 | auth_url, 61 | redirect_url, 62 | }) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /wit/deps/clocks-0.3.0-rc-2025-09-16/timezone.wit: -------------------------------------------------------------------------------- 1 | package wasi:clocks@0.3.0-rc-2025-09-16; 2 | 3 | @unstable(feature = clocks-timezone) 4 | interface timezone { 5 | @unstable(feature = clocks-timezone) 6 | use wall-clock.{datetime}; 7 | 8 | /// Return information needed to display the given `datetime`. This includes 9 | /// the UTC offset, the time zone name, and a flag indicating whether 10 | /// daylight saving time is active. 11 | /// 12 | /// If the timezone cannot be determined for the given `datetime`, return a 13 | /// `timezone-display` for `UTC` with a `utc-offset` of 0 and no daylight 14 | /// saving time. 15 | @unstable(feature = clocks-timezone) 16 | display: func(when: datetime) -> timezone-display; 17 | 18 | /// The same as `display`, but only return the UTC offset. 19 | @unstable(feature = clocks-timezone) 20 | utc-offset: func(when: datetime) -> s32; 21 | 22 | /// Information useful for displaying the timezone of a specific `datetime`. 23 | /// 24 | /// This information may vary within a single `timezone` to reflect daylight 25 | /// saving time adjustments. 26 | @unstable(feature = clocks-timezone) 27 | record timezone-display { 28 | /// The number of seconds difference between UTC time and the local 29 | /// time of the timezone. 30 | /// 31 | /// The returned value will always be less than 86400 which is the 32 | /// number of seconds in a day (24*60*60). 33 | /// 34 | /// In implementations that do not expose an actual time zone, this 35 | /// should return 0. 36 | utc-offset: s32, 37 | 38 | /// The abbreviated name of the timezone to display to a user. The name 39 | /// `UTC` indicates Coordinated Universal Time. Otherwise, this should 40 | /// reference local standards for the name of the time zone. 41 | /// 42 | /// In implementations that do not expose an actual time zone, this 43 | /// should be the string `UTC`. 44 | /// 45 | /// In time zones that do not have an applicable name, a formatted 46 | /// representation of the UTC offset may be returned, such as `-04:00`. 47 | name: string, 48 | 49 | /// Whether daylight saving time is active. 50 | /// 51 | /// In implementations that do not expose an actual time zone, this 52 | /// should return false. 53 | in-daylight-saving-time: bool, 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /wit/deps/cli-0.3.0-rc-2025-09-16/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 | @since(version = 0.3.0-rc-2025-09-16) 7 | interface terminal-input { 8 | /// The input side of a terminal. 9 | @since(version = 0.3.0-rc-2025-09-16) 10 | resource terminal-input; 11 | } 12 | 13 | /// Terminal output. 14 | /// 15 | /// In the future, this may include functions for querying the terminal 16 | /// size, being notified of terminal size changes, querying supported 17 | /// features, and so on. 18 | @since(version = 0.3.0-rc-2025-09-16) 19 | interface terminal-output { 20 | /// The output side of a terminal. 21 | @since(version = 0.3.0-rc-2025-09-16) 22 | resource terminal-output; 23 | } 24 | 25 | /// An interface providing an optional `terminal-input` for stdin as a 26 | /// link-time authority. 27 | @since(version = 0.3.0-rc-2025-09-16) 28 | interface terminal-stdin { 29 | @since(version = 0.3.0-rc-2025-09-16) 30 | use terminal-input.{terminal-input}; 31 | 32 | /// If stdin is connected to a terminal, return a `terminal-input` handle 33 | /// allowing further interaction with it. 34 | @since(version = 0.3.0-rc-2025-09-16) 35 | get-terminal-stdin: func() -> option; 36 | } 37 | 38 | /// An interface providing an optional `terminal-output` for stdout as a 39 | /// link-time authority. 40 | @since(version = 0.3.0-rc-2025-09-16) 41 | interface terminal-stdout { 42 | @since(version = 0.3.0-rc-2025-09-16) 43 | use terminal-output.{terminal-output}; 44 | 45 | /// If stdout is connected to a terminal, return a `terminal-output` handle 46 | /// allowing further interaction with it. 47 | @since(version = 0.3.0-rc-2025-09-16) 48 | get-terminal-stdout: func() -> option; 49 | } 50 | 51 | /// An interface providing an optional `terminal-output` for stderr as a 52 | /// link-time authority. 53 | @since(version = 0.3.0-rc-2025-09-16) 54 | interface terminal-stderr { 55 | @since(version = 0.3.0-rc-2025-09-16) 56 | use terminal-output.{terminal-output}; 57 | 58 | /// If stderr is connected to a terminal, return a `terminal-output` handle 59 | /// allowing further interaction with it. 60 | @since(version = 0.3.0-rc-2025-09-16) 61 | get-terminal-stderr: func() -> option; 62 | } 63 | -------------------------------------------------------------------------------- /wit/deps/cli-0.3.0-rc-2025-09-16/stdio.wit: -------------------------------------------------------------------------------- 1 | @since(version = 0.3.0-rc-2025-09-16) 2 | interface types { 3 | @since(version = 0.3.0-rc-2025-09-16) 4 | enum error-code { 5 | /// Input/output error 6 | io, 7 | /// Invalid or incomplete multibyte or wide character 8 | illegal-byte-sequence, 9 | /// Broken pipe 10 | pipe, 11 | } 12 | } 13 | 14 | @since(version = 0.3.0-rc-2025-09-16) 15 | interface stdin { 16 | use types.{error-code}; 17 | 18 | /// Return a stream for reading from stdin. 19 | /// 20 | /// This function returns a stream which provides data read from stdin, 21 | /// and a future to signal read results. 22 | /// 23 | /// If the stream's readable end is dropped the future will resolve to success. 24 | /// 25 | /// If the stream's writable end is dropped the future will either resolve to 26 | /// success if stdin was closed by the writer or to an error-code if reading 27 | /// failed for some other reason. 28 | /// 29 | /// Multiple streams may be active at the same time. The behavior of concurrent 30 | /// reads is implementation-specific. 31 | @since(version = 0.3.0-rc-2025-09-16) 32 | read-via-stream: func() -> tuple, future>>; 33 | } 34 | 35 | @since(version = 0.3.0-rc-2025-09-16) 36 | interface stdout { 37 | use types.{error-code}; 38 | 39 | /// Write the given stream to stdout. 40 | /// 41 | /// If the stream's writable end is dropped this function will either return 42 | /// success once the entire contents of the stream have been written or an 43 | /// error-code representing a failure. 44 | /// 45 | /// Otherwise if there is an error the readable end of the stream will be 46 | /// dropped and this function will return an error-code. 47 | @since(version = 0.3.0-rc-2025-09-16) 48 | write-via-stream: async func(data: stream) -> result<_, error-code>; 49 | } 50 | 51 | @since(version = 0.3.0-rc-2025-09-16) 52 | interface stderr { 53 | use types.{error-code}; 54 | 55 | /// Write the given stream to stderr. 56 | /// 57 | /// If the stream's writable end is dropped this function will either return 58 | /// success once the entire contents of the stream have been written or an 59 | /// error-code representing a failure. 60 | /// 61 | /// Otherwise if there is an error the readable end of the stream will be 62 | /// dropped and this function will return an error-code. 63 | @since(version = 0.3.0-rc-2025-09-16) 64 | write-via-stream: async func(data: stream) -> result<_, error-code>; 65 | } 66 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /wit/deps/spin@unversioned/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 | -------------------------------------------------------------------------------- /github-oauth/src/api/authenticate.rs: -------------------------------------------------------------------------------- 1 | use crate::response; 2 | 3 | use spin_sdk::http_wasip3::{send, EmptyBody, Request, IntoRequest, IntoResponse}; 4 | 5 | use cookie::Cookie; 6 | use http::header::COOKIE; 7 | 8 | /// `authenticate` validates the access token required in the incoming request by making an 9 | /// outgoing request to github. If the token is valid, the request is passed through to the 10 | /// imported endpoint. 11 | pub async fn authenticate(mut request: Request) -> impl IntoResponse { 12 | let token = match get_access_token(&request) { 13 | Some(token) => token, 14 | None => { 15 | eprintln!("no access token found in incoming request"); 16 | 17 | return unauthorized().into_response(); 18 | } 19 | }; 20 | 21 | let api_request = http::Request::builder() 22 | .uri("https://api.github.com/user") 23 | .header("Authorization", format!("Bearer {token}")) 24 | .header("User-Agent", "Spin Middleware") 25 | .body(EmptyBody::new()) 26 | .unwrap(); 27 | 28 | let api_response = send(api_request).await; 29 | 30 | match api_response { 31 | Ok(response) => { 32 | let status = response.status(); 33 | if status.is_success() { 34 | eprintln!("authenticated"); 35 | // mmm, forbidden header 36 | request.headers_mut().remove("connection"); 37 | request.headers_mut().remove("host"); 38 | crate::double::http::chain_http::handle(request.into_request()?).await.into_response() 39 | } else { 40 | eprintln!("unauthenticated"); 41 | unauthorized().into_response() 42 | } 43 | } 44 | Err(error) => { 45 | eprintln!("error authenticating with github: {error}"); 46 | response::internal_server_error().into_response() 47 | } 48 | } 49 | } 50 | 51 | fn unauthorized() -> impl IntoResponse { 52 | http::Response::builder() 53 | .status(403) 54 | .header("Content-Type", "text/html") 55 | .body("Unauthorized, login".to_owned()) 56 | } 57 | 58 | fn get_access_token(request: &Request) -> Option { 59 | const OAUTH_TOKEN: &str = "access-token"; 60 | 61 | let cookies = request.headers().get(&COOKIE)?.to_str().ok()?; 62 | 63 | let parsed = Cookie::split_parse(cookies); 64 | for cookie in parsed.flatten() { 65 | if cookie.name() == OAUTH_TOKEN { 66 | return Some(cookie.value().to_string()); 67 | } 68 | } 69 | 70 | None 71 | } 72 | -------------------------------------------------------------------------------- /wit/deps/sockets-0.3.0-rc-2025-09-16/ip-name-lookup.wit: -------------------------------------------------------------------------------- 1 | @since(version = 0.3.0-rc-2025-09-16) 2 | interface ip-name-lookup { 3 | @since(version = 0.3.0-rc-2025-09-16) 4 | use types.{ip-address}; 5 | 6 | /// Lookup error codes. 7 | @since(version = 0.3.0-rc-2025-09-16) 8 | enum error-code { 9 | /// Unknown error 10 | unknown, 11 | 12 | /// Access denied. 13 | /// 14 | /// POSIX equivalent: EACCES, EPERM 15 | access-denied, 16 | 17 | /// `name` is a syntactically invalid domain name or IP address. 18 | /// 19 | /// POSIX equivalent: EINVAL 20 | invalid-argument, 21 | 22 | /// Name does not exist or has no suitable associated IP addresses. 23 | /// 24 | /// POSIX equivalent: EAI_NONAME, EAI_NODATA, EAI_ADDRFAMILY 25 | name-unresolvable, 26 | 27 | /// A temporary failure in name resolution occurred. 28 | /// 29 | /// POSIX equivalent: EAI_AGAIN 30 | temporary-resolver-failure, 31 | 32 | /// A permanent failure in name resolution occurred. 33 | /// 34 | /// POSIX equivalent: EAI_FAIL 35 | permanent-resolver-failure, 36 | } 37 | 38 | /// Resolve an internet host name to a list of IP addresses. 39 | /// 40 | /// Unicode domain names are automatically converted to ASCII using IDNA encoding. 41 | /// If the input is an IP address string, the address is parsed and returned 42 | /// as-is without making any external requests. 43 | /// 44 | /// See the wasi-socket proposal README.md for a comparison with getaddrinfo. 45 | /// 46 | /// The results are returned in connection order preference. 47 | /// 48 | /// This function never succeeds with 0 results. It either fails or succeeds 49 | /// with at least one address. Additionally, this function never returns 50 | /// IPv4-mapped IPv6 addresses. 51 | /// 52 | /// The returned future will resolve to an error code in case of failure. 53 | /// It will resolve to success once the returned stream is exhausted. 54 | /// 55 | /// # References: 56 | /// - 57 | /// - 58 | /// - 59 | /// - 60 | @since(version = 0.3.0-rc-2025-09-16) 61 | resolve-addresses: async func(name: string) -> result, error-code>; 62 | } 63 | -------------------------------------------------------------------------------- /wit/deps/spin@2.0.0/redis.wit: -------------------------------------------------------------------------------- 1 | interface redis { 2 | /// Errors related to interacting with Redis 3 | variant error { 4 | /// An invalid address string 5 | invalid-address, 6 | /// There are too many open connections 7 | too-many-connections, 8 | /// A retrieved value was not of the correct type 9 | type-error, 10 | /// Some other error occurred 11 | other(string), 12 | } 13 | 14 | resource connection { 15 | /// Open a connection to the Redis instance at `address`. 16 | open: static func(address: string) -> result; 17 | 18 | /// Publish a Redis message to the specified channel. 19 | publish: func(channel: string, payload: payload) -> result<_, error>; 20 | 21 | /// Get the value of a key. 22 | get: func(key: string) -> result, error>; 23 | 24 | /// Set key to value. 25 | /// 26 | /// If key already holds a value, it is overwritten. 27 | set: func(key: string, value: payload) -> result<_, error>; 28 | 29 | /// Increments the number stored at key by one. 30 | /// 31 | /// If the key does not exist, it is set to 0 before performing the operation. 32 | /// An `error::type-error` is returned if the key contains a value of the wrong type 33 | /// or contains a string that can not be represented as integer. 34 | incr: func(key: string) -> result; 35 | 36 | /// Removes the specified keys. 37 | /// 38 | /// A key is ignored if it does not exist. Returns the number of keys deleted. 39 | del: func(keys: list) -> result; 40 | 41 | /// Add the specified `values` to the set named `key`, returning the number of newly-added values. 42 | sadd: func(key: string, values: list) -> result; 43 | 44 | /// Retrieve the contents of the set named `key`. 45 | smembers: func(key: string) -> result, error>; 46 | 47 | /// Remove the specified `values` from the set named `key`, returning the number of newly-removed values. 48 | srem: func(key: string, values: list) -> result; 49 | 50 | /// Execute an arbitrary Redis command and receive the result. 51 | execute: func(command: string, arguments: list) -> result, error>; 52 | } 53 | 54 | /// The message payload. 55 | type payload = list; 56 | 57 | /// A parameter type for the general-purpose `execute` function. 58 | variant redis-parameter { 59 | int64(s64), 60 | binary(payload) 61 | } 62 | 63 | /// A return type for the general-purpose `execute` function. 64 | variant redis-result { 65 | nil, 66 | status(string), 67 | int64(s64), 68 | binary(payload) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /wit/deps/sockets/ip-name-lookup.wit: -------------------------------------------------------------------------------- 1 | 2 | interface ip-name-lookup { 3 | use wasi:io/poll@0.2.0.{pollable}; 4 | use network.{network, error-code, ip-address}; 5 | 6 | 7 | /// Resolve an internet host name to a list of IP addresses. 8 | /// 9 | /// Unicode domain names are automatically converted to ASCII using IDNA encoding. 10 | /// If the input is an IP address string, the address is parsed and returned 11 | /// as-is without making any external requests. 12 | /// 13 | /// See the wasi-socket proposal README.md for a comparison with getaddrinfo. 14 | /// 15 | /// This function never blocks. It either immediately fails or immediately 16 | /// returns successfully with a `resolve-address-stream` that can be used 17 | /// to (asynchronously) fetch the results. 18 | /// 19 | /// # Typical errors 20 | /// - `invalid-argument`: `name` is a syntactically invalid domain name or IP address. 21 | /// 22 | /// # References: 23 | /// - 24 | /// - 25 | /// - 26 | /// - 27 | resolve-addresses: func(network: borrow, name: string) -> result; 28 | 29 | resource resolve-address-stream { 30 | /// Returns the next address from the resolver. 31 | /// 32 | /// This function should be called multiple times. On each call, it will 33 | /// return the next address in connection order preference. If all 34 | /// addresses have been exhausted, this function returns `none`. 35 | /// 36 | /// This function never returns IPv4-mapped IPv6 addresses. 37 | /// 38 | /// # Typical errors 39 | /// - `name-unresolvable`: Name does not exist or has no suitable associated IP addresses. (EAI_NONAME, EAI_NODATA, EAI_ADDRFAMILY) 40 | /// - `temporary-resolver-failure`: A temporary failure in name resolution occurred. (EAI_AGAIN) 41 | /// - `permanent-resolver-failure`: A permanent failure in name resolution occurred. (EAI_FAIL) 42 | /// - `would-block`: A result is not available yet. (EWOULDBLOCK, EAGAIN) 43 | resolve-next-address: func() -> result, error-code>; 44 | 45 | /// Create a `pollable` which will resolve once the stream is ready for I/O. 46 | /// 47 | /// Note: this function is here for WASI Preview2 only. 48 | /// It's planned to be removed when `future` is natively supported in Preview3. 49 | subscribe: func() -> pollable; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /wit/deps/sockets-2023-11-10/ip-name-lookup.wit: -------------------------------------------------------------------------------- 1 | 2 | interface ip-name-lookup { 3 | use wasi:io/poll@0.2.0-rc-2023-11-10.{pollable}; 4 | use network.{network, error-code, ip-address}; 5 | 6 | 7 | /// Resolve an internet host name to a list of IP addresses. 8 | /// 9 | /// Unicode domain names are automatically converted to ASCII using IDNA encoding. 10 | /// If the input is an IP address string, the address is parsed and returned 11 | /// as-is without making any external requests. 12 | /// 13 | /// See the wasi-socket proposal README.md for a comparison with getaddrinfo. 14 | /// 15 | /// This function never blocks. It either immediately fails or immediately 16 | /// returns successfully with a `resolve-address-stream` that can be used 17 | /// to (asynchronously) fetch the results. 18 | /// 19 | /// # Typical errors 20 | /// - `invalid-argument`: `name` is a syntactically invalid domain name or IP address. 21 | /// 22 | /// # References: 23 | /// - 24 | /// - 25 | /// - 26 | /// - 27 | resolve-addresses: func(network: borrow, name: string) -> result; 28 | 29 | resource resolve-address-stream { 30 | /// Returns the next address from the resolver. 31 | /// 32 | /// This function should be called multiple times. On each call, it will 33 | /// return the next address in connection order preference. If all 34 | /// addresses have been exhausted, this function returns `none`. 35 | /// 36 | /// This function never returns IPv4-mapped IPv6 addresses. 37 | /// 38 | /// # Typical errors 39 | /// - `name-unresolvable`: Name does not exist or has no suitable associated IP addresses. (EAI_NONAME, EAI_NODATA, EAI_ADDRFAMILY) 40 | /// - `temporary-resolver-failure`: A temporary failure in name resolution occurred. (EAI_AGAIN) 41 | /// - `permanent-resolver-failure`: A permanent failure in name resolution occurred. (EAI_FAIL) 42 | /// - `would-block`: A result is not available yet. (EWOULDBLOCK, EAGAIN) 43 | resolve-next-address: func() -> result, error-code>; 44 | 45 | /// Create a `pollable` which will resolve once the stream is ready for I/O. 46 | /// 47 | /// Note: this function is here for WASI Preview2 only. 48 | /// It's planned to be removed when `future` is natively supported in Preview3. 49 | subscribe: func() -> pollable; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /wit/deps/spin-postgres@3.0.0/postgres.wit: -------------------------------------------------------------------------------- 1 | package spin:postgres@3.0.0; 2 | 3 | interface postgres { 4 | /// Errors related to interacting with a database. 5 | variant error { 6 | connection-failed(string), 7 | bad-parameter(string), 8 | query-failed(string), 9 | value-conversion-failed(string), 10 | other(string) 11 | } 12 | 13 | /// Data types for a database column 14 | enum db-data-type { 15 | boolean, 16 | int8, 17 | int16, 18 | int32, 19 | int64, 20 | floating32, 21 | floating64, 22 | str, 23 | binary, 24 | date, 25 | time, 26 | datetime, 27 | timestamp, 28 | other, 29 | } 30 | 31 | /// Database values 32 | variant db-value { 33 | boolean(bool), 34 | int8(s8), 35 | int16(s16), 36 | int32(s32), 37 | int64(s64), 38 | floating32(f32), 39 | floating64(f64), 40 | str(string), 41 | binary(list), 42 | date(tuple), // (year, month, day) 43 | time(tuple), // (hour, minute, second, nanosecond) 44 | /// Date-time types are always treated as UTC (without timezone info). 45 | /// The instant is represented as a (year, month, day, hour, minute, second, nanosecond) tuple. 46 | datetime(tuple), 47 | /// Unix timestamp (seconds since epoch) 48 | timestamp(s64), 49 | db-null, 50 | unsupported, 51 | } 52 | 53 | /// Values used in parameterized queries 54 | variant parameter-value { 55 | boolean(bool), 56 | int8(s8), 57 | int16(s16), 58 | int32(s32), 59 | int64(s64), 60 | floating32(f32), 61 | floating64(f64), 62 | str(string), 63 | binary(list), 64 | date(tuple), // (year, month, day) 65 | time(tuple), // (hour, minute, second, nanosecond) 66 | /// Date-time types are always treated as UTC (without timezone info). 67 | /// The instant is represented as a (year, month, day, hour, minute, second, nanosecond) tuple. 68 | datetime(tuple), 69 | /// Unix timestamp (seconds since epoch) 70 | timestamp(s64), 71 | db-null, 72 | } 73 | 74 | /// A database column 75 | record column { 76 | name: string, 77 | data-type: db-data-type, 78 | } 79 | 80 | /// A database row 81 | type row = list; 82 | 83 | /// A set of database rows 84 | record row-set { 85 | columns: list, 86 | rows: list, 87 | } 88 | 89 | /// A connection to a postgres database. 90 | resource connection { 91 | /// Open a connection to the Postgres instance at `address`. 92 | open: static func(address: string) -> result; 93 | 94 | /// Query the database. 95 | query: func(statement: string, params: list) -> result; 96 | 97 | /// Execute command to the database. 98 | execute: func(statement: string, params: list) -> result; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /wit/deps/spin@unversioned/key-value.wit: -------------------------------------------------------------------------------- 1 | interface key-value { 2 | // A handle to an open key-value store 3 | type store = u32; 4 | 5 | // The set of errors which may be raised by functions in this interface 6 | variant error { 7 | // Too many stores have been opened simultaneously. Closing one or more 8 | // stores prior to retrying may address this. 9 | store-table-full, 10 | 11 | // The host does not recognize the store name requested. Defining and 12 | // configuring a store with that name in a runtime configuration file 13 | // may address this. 14 | no-such-store, 15 | 16 | // The requesting component does not have access to the specified store 17 | // (which may or may not exist). 18 | access-denied, 19 | 20 | // The store handle provided is not recognized, i.e. it was either never 21 | // opened or has been closed. 22 | invalid-store, 23 | 24 | // No key-value tuple exists for the specified key in the specified 25 | // store. 26 | no-such-key, 27 | 28 | // Some implementation-specific error has occurred (e.g. I/O) 29 | io(string) 30 | } 31 | 32 | // Open the store with the specified name. 33 | // 34 | // If `name` is "default", the default store is opened. Otherwise, 35 | // `name` must refer to a store defined and configured in a runtime 36 | // configuration file supplied with the application. 37 | // 38 | // `error::no-such-store` will be raised if the `name` is not recognized. 39 | open: func(name: string) -> result; 40 | 41 | // Get the value associated with the specified `key` from the specified 42 | // `store`. 43 | // 44 | // `error::invalid-store` will be raised if `store` is not a valid handle 45 | // to an open store, and `error::no-such-key` will be raised if there is no 46 | // tuple for `key` in `store`. 47 | get: func(store: store, key: string) -> result, error>; 48 | 49 | // Set the `value` associated with the specified `key` in the specified 50 | // `store`, overwriting any existing value. 51 | // 52 | // `error::invalid-store` will be raised if `store` is not a valid handle 53 | // to an open store. 54 | set: func(store: store, key: string, value: list) -> result<_, error>; 55 | 56 | // Delete the tuple with the specified `key` from the specified `store`. 57 | // 58 | // `error::invalid-store` will be raised if `store` is not a valid handle 59 | // to an open store. No error is raised if a tuple did not previously 60 | // exist for `key`. 61 | delete: func(store: store, key: string) -> result<_, error>; 62 | 63 | // Return whether a tuple exists for the specified `key` in the specified 64 | // `store`. 65 | // 66 | // `error::invalid-store` will be raised if `store` is not a valid handle 67 | // to an open store. 68 | exists: func(store: store, key: string) -> result; 69 | 70 | // Return a list of all the keys in the specified `store`. 71 | // 72 | // `error::invalid-store` will be raised if `store` is not a valid handle 73 | // to an open store. 74 | get-keys: func(store: store) -> result, error>; 75 | 76 | // Close the specified `store`. 77 | // 78 | // This has no effect if `store` is not a valid handle to an open store. 79 | close: func(store: store); 80 | } 81 | -------------------------------------------------------------------------------- /wit/deps/keyvalue-2024-10-17/batch.wit: -------------------------------------------------------------------------------- 1 | /// A keyvalue interface that provides batch operations. 2 | /// 3 | /// A batch operation is an operation that operates on multiple keys at once. 4 | /// 5 | /// Batch operations are useful for reducing network round-trip time. For example, if you want to 6 | /// get the values associated with 100 keys, you can either do 100 get operations or you can do 1 7 | /// batch get operation. The batch operation is faster because it only needs to make 1 network call 8 | /// instead of 100. 9 | /// 10 | /// A batch operation does not guarantee atomicity, meaning that if the batch operation fails, some 11 | /// of the keys may have been modified and some may not. 12 | /// 13 | /// This interface does has the same consistency guarantees as the `store` interface, meaning that 14 | /// you should be able to "read your writes." 15 | /// 16 | /// Please note that this interface is bare functions that take a reference to a bucket. This is to 17 | /// get around the current lack of a way to "extend" a resource with additional methods inside of 18 | /// wit. Future version of the interface will instead extend these methods on the base `bucket` 19 | /// resource. 20 | interface batch { 21 | use store.{bucket, error}; 22 | 23 | /// Get the key-value pairs associated with the keys in the store. It returns a list of 24 | /// key-value pairs. 25 | /// 26 | /// If any of the keys do not exist in the store, it returns a `none` value for that pair in the 27 | /// list. 28 | /// 29 | /// MAY show an out-of-date value if there are concurrent writes to the store. 30 | /// 31 | /// If any other error occurs, it returns an `Err(error)`. 32 | get-many: func(bucket: borrow, keys: list) -> result>>>, error>; 33 | 34 | /// Set the values associated with the keys in the store. If the key already exists in the 35 | /// store, it overwrites the value. 36 | /// 37 | /// Note that the key-value pairs are not guaranteed to be set in the order they are provided. 38 | /// 39 | /// If any of the keys do not exist in the store, it creates a new key-value pair. 40 | /// 41 | /// If any other error occurs, it returns an `Err(error)`. When an error occurs, it does not 42 | /// rollback the key-value pairs that were already set. Thus, this batch operation does not 43 | /// guarantee atomicity, implying that some key-value pairs could be set while others might 44 | /// fail. 45 | /// 46 | /// Other concurrent operations may also be able to see the partial results. 47 | set-many: func(bucket: borrow, key-values: list>>) -> result<_, error>; 48 | 49 | /// Delete the key-value pairs associated with the keys in the store. 50 | /// 51 | /// Note that the key-value pairs are not guaranteed to be deleted in the order they are 52 | /// provided. 53 | /// 54 | /// If any of the keys do not exist in the store, it skips the key. 55 | /// 56 | /// If any other error occurs, it returns an `Err(error)`. When an error occurs, it does not 57 | /// rollback the key-value pairs that were already deleted. Thus, this batch operation does not 58 | /// guarantee atomicity, implying that some key-value pairs could be deleted while others might 59 | /// fail. 60 | /// 61 | /// Other concurrent operations may also be able to see the partial results. 62 | delete-many: func(bucket: borrow, keys: list) -> result<_, error>; 63 | } 64 | -------------------------------------------------------------------------------- /wit/deps/sockets-2023-10-18/ip-name-lookup.wit: -------------------------------------------------------------------------------- 1 | 2 | interface ip-name-lookup { 3 | use wasi:io/poll@0.2.0-rc-2023-10-18.{pollable}; 4 | use network.{network, error-code, ip-address, ip-address-family}; 5 | 6 | 7 | /// Resolve an internet host name to a list of IP addresses. 8 | /// 9 | /// See the wasi-socket proposal README.md for a comparison with getaddrinfo. 10 | /// 11 | /// # Parameters 12 | /// - `name`: The name to look up. IP addresses are not allowed. Unicode domain names are automatically converted 13 | /// to ASCII using IDNA encoding. 14 | /// - `address-family`: If provided, limit the results to addresses of this specific address family. 15 | /// - `include-unavailable`: When set to true, this function will also return addresses of which the runtime 16 | /// thinks (or knows) can't be connected to at the moment. For example, this will return IPv6 addresses on 17 | /// systems without an active IPv6 interface. Notes: 18 | /// - Even when no public IPv6 interfaces are present or active, names like "localhost" can still resolve to an IPv6 address. 19 | /// - Whatever is "available" or "unavailable" is volatile and can change everytime a network cable is unplugged. 20 | /// 21 | /// This function never blocks. It either immediately fails or immediately returns successfully with a `resolve-address-stream` 22 | /// that can be used to (asynchronously) fetch the results. 23 | /// 24 | /// At the moment, the stream never completes successfully with 0 items. Ie. the first call 25 | /// to `resolve-next-address` never returns `ok(none)`. This may change in the future. 26 | /// 27 | /// # Typical errors 28 | /// - `invalid-argument`: `name` is a syntactically invalid domain name. 29 | /// - `invalid-argument`: `name` is an IP address. 30 | /// - `not-supported`: The specified `address-family` is not supported. (EAI_FAMILY) 31 | /// 32 | /// # References: 33 | /// - 34 | /// - 35 | /// - 36 | /// - 37 | resolve-addresses: func(network: borrow, name: string, address-family: option, include-unavailable: bool) -> result; 38 | 39 | resource resolve-address-stream { 40 | /// Returns the next address from the resolver. 41 | /// 42 | /// This function should be called multiple times. On each call, it will 43 | /// return the next address in connection order preference. If all 44 | /// addresses have been exhausted, this function returns `none`. 45 | /// 46 | /// This function never returns IPv4-mapped IPv6 addresses. 47 | /// 48 | /// # Typical errors 49 | /// - `name-unresolvable`: Name does not exist or has no suitable associated IP addresses. (EAI_NONAME, EAI_NODATA, EAI_ADDRFAMILY) 50 | /// - `temporary-resolver-failure`: A temporary failure in name resolution occurred. (EAI_AGAIN) 51 | /// - `permanent-resolver-failure`: A permanent failure in name resolution occurred. (EAI_FAIL) 52 | /// - `would-block`: A result is not available yet. (EWOULDBLOCK, EAGAIN) 53 | resolve-next-address: func() -> result, error-code>; 54 | 55 | /// Create a `pollable` which will resolve once the stream is ready for I/O. 56 | /// 57 | /// Note: this function is here for WASI Preview2 only. 58 | /// It's planned to be removed when `future` is natively supported in Preview3. 59 | subscribe: func() -> pollable; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /github-oauth/src/api/callback.rs: -------------------------------------------------------------------------------- 1 | use super::OAuth2; 2 | use crate::response; 3 | 4 | use cookie::{Cookie, SameSite}; 5 | use oauth2::{basic, AuthorizationCode, CsrfToken, TokenResponse}; 6 | use spin_sdk::http_wasip3::{send, EmptyBody, FullBody, IntoResponse}; 7 | use url::Url; 8 | 9 | pub async fn callback(url: &http::Uri) -> impl IntoResponse { 10 | let Ok(url) = url::Url::parse(&url.to_string()) else { 11 | return response::internal_server_error().into_response(); 12 | }; 13 | 14 | let client = match OAuth2::try_init() { 15 | Ok(config) => basic::BasicClient::new(config.client_id) 16 | .set_client_secret(config.client_secret) 17 | .set_auth_uri(config.auth_url) 18 | .set_token_uri(config.token_url) 19 | .set_redirect_uri(config.redirect_url) 20 | .set_auth_type(oauth2::AuthType::RequestBody), 21 | Err(error) => { 22 | eprintln!("failed to initialize oauth client: {error}"); 23 | return response::internal_server_error().into_response(); 24 | } 25 | }; 26 | 27 | let (code, _state) = match get_code_and_state_param(&url) { 28 | Ok((code, state)) => (code, state), 29 | Err(error) => { 30 | eprintln!("error retrieving required query parameters: {error}"); 31 | return response::internal_server_error().into_response(); 32 | } 33 | }; 34 | 35 | // TODO: check state with cached state and ensure equality 36 | 37 | let result = client 38 | .exchange_code(code) 39 | .request_async(&oauth_http_client) 40 | .await; 41 | 42 | let mut location = client.redirect_uri().unwrap().url().clone(); 43 | location.set_path(""); 44 | match result { 45 | Ok(result) => { 46 | let access_token = serde_json::to_string(result.access_token()) 47 | .unwrap() 48 | .replace("\"", ""); 49 | 50 | let mut oauth_cookie = Cookie::new("access-token", access_token); 51 | oauth_cookie.set_same_site(Some(SameSite::Lax)); 52 | oauth_cookie.set_http_only(true); 53 | oauth_cookie.set_path("/"); 54 | 55 | http::Response::builder() 56 | .status(301) 57 | .header("Content-Type", "text/plain") 58 | .header("Location", location.as_str()) 59 | .header("Set-Cookie", oauth_cookie.to_string()) 60 | .body(EmptyBody::new()) 61 | .into_response() 62 | } 63 | Err(error) => { 64 | eprintln!("error exchanging code for token with github: {error}"); 65 | response::forbidden().into_response() 66 | } 67 | } 68 | } 69 | 70 | fn get_code_and_state_param(url: &Url) -> anyhow::Result<(AuthorizationCode, CsrfToken)> { 71 | fn get_query_param(url: &Url, param: &str) -> Option { 72 | url.query_pairs() 73 | .find(|(key, _)| key == param) 74 | .map(|(_, value)| value.into_owned()) 75 | } 76 | 77 | const STATE_QUERY_PARAM_NAME: &str = "state"; 78 | const CODE_QUERY_PARAM_NAME: &str = "code"; 79 | 80 | let Some(param) = get_query_param(url, STATE_QUERY_PARAM_NAME) else { 81 | anyhow::bail!("missing '{STATE_QUERY_PARAM_NAME}' query parameter"); 82 | }; 83 | 84 | let state = CsrfToken::new(param); 85 | 86 | let Some(param) = get_query_param(url, CODE_QUERY_PARAM_NAME) else { 87 | anyhow::bail!("missing '{CODE_QUERY_PARAM_NAME}' query parameter"); 88 | }; 89 | 90 | let code = AuthorizationCode::new(param); 91 | 92 | Ok((code, state)) 93 | } 94 | 95 | async fn oauth_http_client( 96 | oauth_req: oauth2::HttpRequest, 97 | ) -> Result { 98 | use spin_sdk::http_wasip3::body::IncomingBodyExt; 99 | 100 | let wasi_req = oauth_req.map(|bod| FullBody::new(bytes::Bytes::from_owner(bod))); 101 | let wasi_response = send(wasi_req).await?; 102 | let (parts, body) = wasi_response.into_parts(); 103 | let sync_body = body.bytes().await?.to_vec(); 104 | let oauth_response = http::Response::from_parts(parts, sync_body); 105 | Ok(oauth_response) 106 | } 107 | -------------------------------------------------------------------------------- /wit/deps/sockets-2023-10-18/network.wit: -------------------------------------------------------------------------------- 1 | 2 | interface network { 3 | /// An opaque resource that represents access to (a subset of) the network. 4 | /// This enables context-based security for networking. 5 | /// There is no need for this to map 1:1 to a physical network interface. 6 | resource network; 7 | 8 | /// Error codes. 9 | /// 10 | /// In theory, every API can return any error code. 11 | /// In practice, API's typically only return the errors documented per API 12 | /// combined with a couple of errors that are always possible: 13 | /// - `unknown` 14 | /// - `access-denied` 15 | /// - `not-supported` 16 | /// - `out-of-memory` 17 | /// - `concurrency-conflict` 18 | /// 19 | /// See each individual API for what the POSIX equivalents are. They sometimes differ per API. 20 | enum error-code { 21 | // ### GENERAL ERRORS ### 22 | 23 | /// Unknown error 24 | unknown, 25 | 26 | /// Access denied. 27 | /// 28 | /// POSIX equivalent: EACCES, EPERM 29 | access-denied, 30 | 31 | /// The operation is not supported. 32 | /// 33 | /// POSIX equivalent: EOPNOTSUPP 34 | not-supported, 35 | 36 | /// One of the arguments is invalid. 37 | /// 38 | /// POSIX equivalent: EINVAL 39 | invalid-argument, 40 | 41 | /// Not enough memory to complete the operation. 42 | /// 43 | /// POSIX equivalent: ENOMEM, ENOBUFS, EAI_MEMORY 44 | out-of-memory, 45 | 46 | /// The operation timed out before it could finish completely. 47 | timeout, 48 | 49 | /// This operation is incompatible with another asynchronous operation that is already in progress. 50 | /// 51 | /// POSIX equivalent: EALREADY 52 | concurrency-conflict, 53 | 54 | /// Trying to finish an asynchronous operation that: 55 | /// - has not been started yet, or: 56 | /// - was already finished by a previous `finish-*` call. 57 | /// 58 | /// Note: this is scheduled to be removed when `future`s are natively supported. 59 | not-in-progress, 60 | 61 | /// The operation has been aborted because it could not be completed immediately. 62 | /// 63 | /// Note: this is scheduled to be removed when `future`s are natively supported. 64 | would-block, 65 | 66 | 67 | 68 | // ### TCP & UDP SOCKET ERRORS ### 69 | 70 | /// The operation is not valid in the socket's current state. 71 | invalid-state, 72 | 73 | /// A new socket resource could not be created because of a system limit. 74 | new-socket-limit, 75 | 76 | /// A bind operation failed because the provided address is not an address that the `network` can bind to. 77 | address-not-bindable, 78 | 79 | /// A bind operation failed because the provided address is already in use or because there are no ephemeral ports available. 80 | address-in-use, 81 | 82 | /// The remote address is not reachable 83 | remote-unreachable, 84 | 85 | 86 | // ### TCP SOCKET ERRORS ### 87 | 88 | /// The connection was forcefully rejected 89 | connection-refused, 90 | 91 | /// The connection was reset. 92 | connection-reset, 93 | 94 | /// A connection was aborted. 95 | connection-aborted, 96 | 97 | // ### UDP SOCKET ERRORS ### 98 | datagram-too-large, 99 | 100 | 101 | // ### NAME LOOKUP ERRORS ### 102 | 103 | /// Name does not exist or has no suitable associated IP addresses. 104 | name-unresolvable, 105 | 106 | /// A temporary failure in name resolution occurred. 107 | temporary-resolver-failure, 108 | 109 | /// A permanent failure in name resolution occurred. 110 | permanent-resolver-failure, 111 | } 112 | 113 | enum ip-address-family { 114 | /// Similar to `AF_INET` in POSIX. 115 | ipv4, 116 | 117 | /// Similar to `AF_INET6` in POSIX. 118 | ipv6, 119 | } 120 | 121 | type ipv4-address = tuple; 122 | type ipv6-address = tuple; 123 | 124 | variant ip-address { 125 | ipv4(ipv4-address), 126 | ipv6(ipv6-address), 127 | } 128 | 129 | record ipv4-socket-address { 130 | port: u16, // sin_port 131 | address: ipv4-address, // sin_addr 132 | } 133 | 134 | record ipv6-socket-address { 135 | port: u16, // sin6_port 136 | flow-info: u32, // sin6_flowinfo 137 | address: ipv6-address, // sin6_addr 138 | scope-id: u32, // sin6_scope_id 139 | } 140 | 141 | variant ip-socket-address { 142 | ipv4(ipv4-socket-address), 143 | ipv6(ipv6-socket-address), 144 | } 145 | 146 | } 147 | -------------------------------------------------------------------------------- /wit/deps/sockets/network.wit: -------------------------------------------------------------------------------- 1 | 2 | interface network { 3 | /// An opaque resource that represents access to (a subset of) the network. 4 | /// This enables context-based security for networking. 5 | /// There is no need for this to map 1:1 to a physical network interface. 6 | resource network; 7 | 8 | /// Error codes. 9 | /// 10 | /// In theory, every API can return any error code. 11 | /// In practice, API's typically only return the errors documented per API 12 | /// combined with a couple of errors that are always possible: 13 | /// - `unknown` 14 | /// - `access-denied` 15 | /// - `not-supported` 16 | /// - `out-of-memory` 17 | /// - `concurrency-conflict` 18 | /// 19 | /// See each individual API for what the POSIX equivalents are. They sometimes differ per API. 20 | enum error-code { 21 | /// Unknown error 22 | unknown, 23 | 24 | /// Access denied. 25 | /// 26 | /// POSIX equivalent: EACCES, EPERM 27 | access-denied, 28 | 29 | /// The operation is not supported. 30 | /// 31 | /// POSIX equivalent: EOPNOTSUPP 32 | not-supported, 33 | 34 | /// One of the arguments is invalid. 35 | /// 36 | /// POSIX equivalent: EINVAL 37 | invalid-argument, 38 | 39 | /// Not enough memory to complete the operation. 40 | /// 41 | /// POSIX equivalent: ENOMEM, ENOBUFS, EAI_MEMORY 42 | out-of-memory, 43 | 44 | /// The operation timed out before it could finish completely. 45 | timeout, 46 | 47 | /// This operation is incompatible with another asynchronous operation that is already in progress. 48 | /// 49 | /// POSIX equivalent: EALREADY 50 | concurrency-conflict, 51 | 52 | /// Trying to finish an asynchronous operation that: 53 | /// - has not been started yet, or: 54 | /// - was already finished by a previous `finish-*` call. 55 | /// 56 | /// Note: this is scheduled to be removed when `future`s are natively supported. 57 | not-in-progress, 58 | 59 | /// The operation has been aborted because it could not be completed immediately. 60 | /// 61 | /// Note: this is scheduled to be removed when `future`s are natively supported. 62 | would-block, 63 | 64 | 65 | /// The operation is not valid in the socket's current state. 66 | invalid-state, 67 | 68 | /// A new socket resource could not be created because of a system limit. 69 | new-socket-limit, 70 | 71 | /// A bind operation failed because the provided address is not an address that the `network` can bind to. 72 | address-not-bindable, 73 | 74 | /// A bind operation failed because the provided address is already in use or because there are no ephemeral ports available. 75 | address-in-use, 76 | 77 | /// The remote address is not reachable 78 | remote-unreachable, 79 | 80 | 81 | /// The TCP connection was forcefully rejected 82 | connection-refused, 83 | 84 | /// The TCP connection was reset. 85 | connection-reset, 86 | 87 | /// A TCP connection was aborted. 88 | connection-aborted, 89 | 90 | 91 | /// The size of a datagram sent to a UDP socket exceeded the maximum 92 | /// supported size. 93 | datagram-too-large, 94 | 95 | 96 | /// Name does not exist or has no suitable associated IP addresses. 97 | name-unresolvable, 98 | 99 | /// A temporary failure in name resolution occurred. 100 | temporary-resolver-failure, 101 | 102 | /// A permanent failure in name resolution occurred. 103 | permanent-resolver-failure, 104 | } 105 | 106 | enum ip-address-family { 107 | /// Similar to `AF_INET` in POSIX. 108 | ipv4, 109 | 110 | /// Similar to `AF_INET6` in POSIX. 111 | ipv6, 112 | } 113 | 114 | type ipv4-address = tuple; 115 | type ipv6-address = tuple; 116 | 117 | variant ip-address { 118 | ipv4(ipv4-address), 119 | ipv6(ipv6-address), 120 | } 121 | 122 | record ipv4-socket-address { 123 | /// sin_port 124 | port: u16, 125 | /// sin_addr 126 | address: ipv4-address, 127 | } 128 | 129 | record ipv6-socket-address { 130 | /// sin6_port 131 | port: u16, 132 | /// sin6_flowinfo 133 | flow-info: u32, 134 | /// sin6_addr 135 | address: ipv6-address, 136 | /// sin6_scope_id 137 | scope-id: u32, 138 | } 139 | 140 | variant ip-socket-address { 141 | ipv4(ipv4-socket-address), 142 | ipv6(ipv6-socket-address), 143 | } 144 | 145 | } 146 | -------------------------------------------------------------------------------- /wit/deps/sockets-2023-11-10/network.wit: -------------------------------------------------------------------------------- 1 | 2 | interface network { 3 | /// An opaque resource that represents access to (a subset of) the network. 4 | /// This enables context-based security for networking. 5 | /// There is no need for this to map 1:1 to a physical network interface. 6 | resource network; 7 | 8 | /// Error codes. 9 | /// 10 | /// In theory, every API can return any error code. 11 | /// In practice, API's typically only return the errors documented per API 12 | /// combined with a couple of errors that are always possible: 13 | /// - `unknown` 14 | /// - `access-denied` 15 | /// - `not-supported` 16 | /// - `out-of-memory` 17 | /// - `concurrency-conflict` 18 | /// 19 | /// See each individual API for what the POSIX equivalents are. They sometimes differ per API. 20 | enum error-code { 21 | // ### GENERAL ERRORS ### 22 | 23 | /// Unknown error 24 | unknown, 25 | 26 | /// Access denied. 27 | /// 28 | /// POSIX equivalent: EACCES, EPERM 29 | access-denied, 30 | 31 | /// The operation is not supported. 32 | /// 33 | /// POSIX equivalent: EOPNOTSUPP 34 | not-supported, 35 | 36 | /// One of the arguments is invalid. 37 | /// 38 | /// POSIX equivalent: EINVAL 39 | invalid-argument, 40 | 41 | /// Not enough memory to complete the operation. 42 | /// 43 | /// POSIX equivalent: ENOMEM, ENOBUFS, EAI_MEMORY 44 | out-of-memory, 45 | 46 | /// The operation timed out before it could finish completely. 47 | timeout, 48 | 49 | /// This operation is incompatible with another asynchronous operation that is already in progress. 50 | /// 51 | /// POSIX equivalent: EALREADY 52 | concurrency-conflict, 53 | 54 | /// Trying to finish an asynchronous operation that: 55 | /// - has not been started yet, or: 56 | /// - was already finished by a previous `finish-*` call. 57 | /// 58 | /// Note: this is scheduled to be removed when `future`s are natively supported. 59 | not-in-progress, 60 | 61 | /// The operation has been aborted because it could not be completed immediately. 62 | /// 63 | /// Note: this is scheduled to be removed when `future`s are natively supported. 64 | would-block, 65 | 66 | 67 | 68 | // ### TCP & UDP SOCKET ERRORS ### 69 | 70 | /// The operation is not valid in the socket's current state. 71 | invalid-state, 72 | 73 | /// A new socket resource could not be created because of a system limit. 74 | new-socket-limit, 75 | 76 | /// A bind operation failed because the provided address is not an address that the `network` can bind to. 77 | address-not-bindable, 78 | 79 | /// A bind operation failed because the provided address is already in use or because there are no ephemeral ports available. 80 | address-in-use, 81 | 82 | /// The remote address is not reachable 83 | remote-unreachable, 84 | 85 | 86 | // ### TCP SOCKET ERRORS ### 87 | 88 | /// The connection was forcefully rejected 89 | connection-refused, 90 | 91 | /// The connection was reset. 92 | connection-reset, 93 | 94 | /// A connection was aborted. 95 | connection-aborted, 96 | 97 | 98 | // ### UDP SOCKET ERRORS ### 99 | datagram-too-large, 100 | 101 | 102 | // ### NAME LOOKUP ERRORS ### 103 | 104 | /// Name does not exist or has no suitable associated IP addresses. 105 | name-unresolvable, 106 | 107 | /// A temporary failure in name resolution occurred. 108 | temporary-resolver-failure, 109 | 110 | /// A permanent failure in name resolution occurred. 111 | permanent-resolver-failure, 112 | } 113 | 114 | enum ip-address-family { 115 | /// Similar to `AF_INET` in POSIX. 116 | ipv4, 117 | 118 | /// Similar to `AF_INET6` in POSIX. 119 | ipv6, 120 | } 121 | 122 | type ipv4-address = tuple; 123 | type ipv6-address = tuple; 124 | 125 | variant ip-address { 126 | ipv4(ipv4-address), 127 | ipv6(ipv6-address), 128 | } 129 | 130 | record ipv4-socket-address { 131 | port: u16, // sin_port 132 | address: ipv4-address, // sin_addr 133 | } 134 | 135 | record ipv6-socket-address { 136 | port: u16, // sin6_port 137 | flow-info: u32, // sin6_flowinfo 138 | address: ipv6-address, // sin6_addr 139 | scope-id: u32, // sin6_scope_id 140 | } 141 | 142 | variant ip-socket-address { 143 | ipv4(ipv4-socket-address), 144 | ipv6(ipv6-socket-address), 145 | } 146 | 147 | } 148 | -------------------------------------------------------------------------------- /wit/deps/spin-postgres@4.0.0/postgres.wit: -------------------------------------------------------------------------------- 1 | package spin:postgres@4.0.0; 2 | 3 | interface postgres { 4 | /// Errors related to interacting with a database. 5 | variant error { 6 | connection-failed(string), 7 | bad-parameter(string), 8 | query-failed(query-error), 9 | value-conversion-failed(string), 10 | other(string) 11 | } 12 | 13 | variant query-error { 14 | /// An error occurred but we do not have structured info for it 15 | text(string), 16 | /// Postgres returned a structured database error 17 | db-error(db-error), 18 | } 19 | 20 | record db-error { 21 | /// Stringised version of the error. This is primarily to facilitate migration of older code. 22 | as-text: string, 23 | severity: string, 24 | code: string, 25 | message: string, 26 | detail: option, 27 | /// Any error information provided by Postgres and not captured above. 28 | extras: list>, 29 | } 30 | 31 | /// Data types for a database column 32 | variant db-data-type { 33 | boolean, 34 | int8, 35 | int16, 36 | int32, 37 | int64, 38 | floating32, 39 | floating64, 40 | str, 41 | binary, 42 | date, 43 | time, 44 | datetime, 45 | timestamp, 46 | uuid, 47 | jsonb, 48 | decimal, 49 | range-int32, 50 | range-int64, 51 | range-decimal, 52 | array-int32, 53 | array-int64, 54 | array-decimal, 55 | array-str, 56 | interval, 57 | other(string), 58 | } 59 | 60 | /// Database values 61 | variant db-value { 62 | boolean(bool), 63 | int8(s8), 64 | int16(s16), 65 | int32(s32), 66 | int64(s64), 67 | floating32(f32), 68 | floating64(f64), 69 | str(string), 70 | binary(list), 71 | date(tuple), // (year, month, day) 72 | time(tuple), // (hour, minute, second, nanosecond) 73 | /// Date-time types are always treated as UTC (without timezone info). 74 | /// The instant is represented as a (year, month, day, hour, minute, second, nanosecond) tuple. 75 | datetime(tuple), 76 | /// Unix timestamp (seconds since epoch) 77 | timestamp(s64), 78 | uuid(string), 79 | jsonb(list), 80 | decimal(string), // I admit defeat. Base 10 81 | range-int32(tuple>, option>>), 82 | range-int64(tuple>, option>>), 83 | range-decimal(tuple>, option>>), 84 | array-int32(list>), 85 | array-int64(list>), 86 | array-decimal(list>), 87 | array-str(list>), 88 | interval(interval), 89 | db-null, 90 | unsupported(list), 91 | } 92 | 93 | /// Values used in parameterized queries 94 | variant parameter-value { 95 | boolean(bool), 96 | int8(s8), 97 | int16(s16), 98 | int32(s32), 99 | int64(s64), 100 | floating32(f32), 101 | floating64(f64), 102 | str(string), 103 | binary(list), 104 | date(tuple), // (year, month, day) 105 | time(tuple), // (hour, minute, second, nanosecond) 106 | /// Date-time types are always treated as UTC (without timezone info). 107 | /// The instant is represented as a (year, month, day, hour, minute, second, nanosecond) tuple. 108 | datetime(tuple), 109 | /// Unix timestamp (seconds since epoch) 110 | timestamp(s64), 111 | uuid(string), 112 | jsonb(list), 113 | decimal(string), // base 10 114 | range-int32(tuple>, option>>), 115 | range-int64(tuple>, option>>), 116 | range-decimal(tuple>, option>>), 117 | array-int32(list>), 118 | array-int64(list>), 119 | array-decimal(list>), 120 | array-str(list>), 121 | interval(interval), 122 | db-null, 123 | } 124 | 125 | record interval { 126 | micros: s64, 127 | days: s32, 128 | months: s32, 129 | } 130 | 131 | /// A database column 132 | record column { 133 | name: string, 134 | data-type: db-data-type, 135 | } 136 | 137 | /// A database row 138 | type row = list; 139 | 140 | /// A set of database rows 141 | record row-set { 142 | columns: list, 143 | rows: list, 144 | } 145 | 146 | /// For range types, indicates if each bound is inclusive or exclusive 147 | enum range-bound-kind { 148 | inclusive, 149 | exclusive, 150 | } 151 | 152 | /// A connection to a postgres database. 153 | resource connection { 154 | /// Open a connection to the Postgres instance at `address`. 155 | open: static func(address: string) -> result; 156 | 157 | /// Query the database. 158 | query: func(statement: string, params: list) -> result; 159 | 160 | /// Execute command to the database. 161 | execute: func(statement: string, params: list) -> result; 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # http-auth-middleware 2 | 3 | This repo is an example of how to compose a middleware component with a business logic component. 4 | 5 | ## Repo structure 6 | 7 | The `github-oauth/` directory contains an API for using GitHub oauth in an application. It consists of 8 | 9 | 1. The `authorize` handler which kicks off the github oauth flow allowing a user to give permissions to a GitHub app 10 | 2. The `callback` handler which GitHub uses as the redirect url in the oauth flow. The callback handler is responsible for taking a code from the URL param and exchanging it for authentication token from GitHub for the user. 11 | 3. The `authenticate` handler which validates a given access token in an incoming request with the GitHub user API. 12 | 4. The `login` handler which returns a login button. 13 | 14 | The `example-app/` directory contains a Spin application which consists of one http handler which returns an HTTP response contains `Hello, Fermyon!` in the body. 15 | 16 | 17 | ## Demo instructions 18 | 19 | ### Pre-requisites 20 | 21 | - Add the `wasm32-wasip2` target 22 | 23 | ```bash 24 | rustup target add wasm32-wasip2 25 | ``` 26 | 27 | - Install latest [Spin](https://github.com/fermyon/spin) 28 | 29 | - Create an OAuth App in your [GitHub Developer Settings](https://github.com/settings/developers). Set the callback URL to `http://127.0.0.1:3000/login/callback`. Accept defaults and input dummy values for the rest of the fields. 30 | - Save the Client ID 31 | - Generate a new Client Secret and save that as well 32 | 33 | ### Build the components and run the demo 34 | 35 | > NOTE: The build script `build.sh` in the `Spin.toml` will build both the `example-app` and `github-oauth` projects. 36 | 37 | ```bash 38 | # Build and run the example 39 | spin up --build -e CLIENT_ID= -e CLIENT_SECRET= 40 | 41 | # Open http://127.0.0.1:3000/ in a browser 42 | ``` 43 | 44 | ### Running with Wasmtime 45 | 46 | This component can be universally run by runtimes that support WASI preview 2's [HTTP proxy 47 | world](https://github.com/WebAssembly/wasi-http/blob/main/wit/proxy.wit). For example, it can be 48 | served directly by Wasmtime, the runtime embedded in Spin. First, ensure you have installed the 49 | [Wasmtime CLI](https://github.com/bytecodealliance/wasmtime/releases) with at least version 50 | `v21.0.1`. We will use the `wasmtime serve` subcommand which serves requests to/from a WASI HTTP 51 | component. 52 | 53 | - Install [wac](https://github.com/bytecodealliance/wac): 54 | 55 | ```bash 56 | cargo install wac-cli --locked 57 | ``` 58 | 59 | ```bash 60 | export CLIENT_ID= 61 | export CLIENT_SECRET= 62 | 63 | # Build the example-app and github-oauth component 64 | spin build 65 | 66 | # Compose the example-app with the github-oauth component 67 | wac plug --plug target/wasm32-wasip2/release/example.wasm target/wasm32-wasip2/release/github_oauth.wasm -o service.wasm 68 | 69 | # Serve the component on the expected host and port 70 | wasmtime serve service.wasm -S cli --addr 127.0.0.1:3000 71 | ``` 72 | 73 | ### Configuring the callback URL 74 | 75 | Instead of using the default callback URL of `http://127.0.0.1:3000/login/callback`, you can configure the URL in an environment variable that is resolved at build time. This is useful in the case that the component is not running locally, rather in a hosted environment such as Fermyon Cloud. 76 | 77 | ```sh 78 | export AUTH_CALLBACK_URL=http://my-auth-app.fermyon.app/login/callback 79 | export CLIENT_ID= 80 | export CLIENT_SECRET= 81 | cargo build --target wasm32-wasip2 --manifest-path github-oauth/Cargo.toml --release --features compile-time-secrets 82 | spin deploy 83 | ``` 84 | 85 | ### Using Runtime Environment Variables 86 | 87 | Not all WebAssembly runtimes fully support exporting the [`wasi:cli/environment`](https://github.com/WebAssembly/wasi-cli/blob/main/wit/environment.wit) interface to components. Spin, however, does support this and can load environment variables into a component's environment. Simply pass the environment variables during a `spin up`: 88 | ```sh 89 | spin up --build -e CLIENT_ID= -e CLIENT_SECRET= 90 | ``` 91 | 92 | To deploy an app to Fermyon Cloud that uses environment variables, you need to [configure them in your `spin.toml`](https://developer.fermyon.com/spin/v2/writing-apps#adding-environment-variables-to-components). Update [the example application manifest](./example/spin.toml) to contain your `CLIENT_ID` and `CLIENT_SECRET` environment variables. Since we do not know the endpoint for our Fermyon Cloud application until after the first deploy, we cannot yet configure the `AUTH_CALLBACK_URL`. 93 | 94 | ```toml 95 | [component.frontend] 96 | # ... 97 | environment = { CLIENT_ID = "YOUR_GITHUB_APP_CLIENT_ID", CLIENT_SECRET = "YOUR_GITHUB_APP_CLIENT_SECRET" } 98 | ``` 99 | 100 | Now deploy your application. 101 | 102 | ```sh 103 | $ spin deploy 104 | Uploading github-oauth2-example version 0.1.0 to Fermyon Cloud... 105 | Deploying... 106 | Waiting for application to become ready............. ready 107 | Available Routes: 108 | example: https://github-oauth2-example-12345.fermyon.app (wildcard) 109 | ``` 110 | 111 | In the example deploy output above, the app now exists at endpoint `https://github-oauth2-example-12345.fermyon.app`. This means our callback URL should be `https://github-oauth2-example-12345.fermyon.app/login/callback`. Configure this in the `spin.toml` with another environment variable: 112 | 113 | ```toml 114 | [component.frontend] 115 | # ... 116 | environment = { CLIENT_ID = "YOUR_GITHUB_APP_CLIENT_ID", CLIENT_SECRET = "YOUR_GITHUB_APP_CLIENT_SECRET", AUTH_CALLBACK_URL = "https://github-oauth2-example-.fermyon.app/login/callback" } 117 | ``` 118 | 119 | Now, redeploy with another `spin deploy`. Be sure to update your GitHub OAuth App to update the callback URL. 120 | 121 | This example uses environment variable to import secrets, since that is a ubiquitous interface and enables cross cloud portability of your component. If you are interested in configuring dynamic secrets that are not exposed in text in your `spin.toml` and can be updated with the `spin cloud variables` CLI, see [Spin's documentation on configuring application variables](https://developer.fermyon.com/spin/v2/variables#application-variables). -------------------------------------------------------------------------------- /wit/deps/keyvalue-2024-10-17/store.wit: -------------------------------------------------------------------------------- 1 | /// A keyvalue interface that provides eventually consistent key-value operations. 2 | /// 3 | /// Each of these operations acts on a single key-value pair. 4 | /// 5 | /// The value in the key-value pair is defined as a `u8` byte array and the intention is that it is 6 | /// the common denominator for all data types defined by different key-value stores to handle data, 7 | /// ensuring compatibility between different key-value stores. Note: the clients will be expecting 8 | /// serialization/deserialization overhead to be handled by the key-value store. The value could be 9 | /// a serialized object from JSON, HTML or vendor-specific data types like AWS S3 objects. 10 | /// 11 | /// Data consistency in a key value store refers to the guarantee that once a write operation 12 | /// completes, all subsequent read operations will return the value that was written. 13 | /// 14 | /// Any implementation of this interface must have enough consistency to guarantee "reading your 15 | /// writes." In particular, this means that the client should never get a value that is older than 16 | /// the one it wrote, but it MAY get a newer value if one was written around the same time. These 17 | /// guarantees only apply to the same client (which will likely be provided by the host or an 18 | /// external capability of some kind). In this context a "client" is referring to the caller or 19 | /// guest that is consuming this interface. Once a write request is committed by a specific client, 20 | /// all subsequent read requests by the same client will reflect that write or any subsequent 21 | /// writes. Another client running in a different context may or may not immediately see the result 22 | /// due to the replication lag. As an example of all of this, if a value at a given key is A, and 23 | /// the client writes B, then immediately reads, it should get B. If something else writes C in 24 | /// quick succession, then the client may get C. However, a client running in a separate context may 25 | /// still see A or B 26 | interface store { 27 | /// The set of errors which may be raised by functions in this package 28 | variant error { 29 | /// The host does not recognize the store identifier requested. 30 | no-such-store, 31 | 32 | /// The requesting component does not have access to the specified store 33 | /// (which may or may not exist). 34 | access-denied, 35 | 36 | /// Some implementation-specific error has occurred (e.g. I/O) 37 | other(string) 38 | } 39 | 40 | /// A response to a `list-keys` operation. 41 | record key-response { 42 | /// The list of keys returned by the query. 43 | keys: list, 44 | /// The continuation token to use to fetch the next page of keys. If this is `null`, then 45 | /// there are no more keys to fetch. 46 | cursor: option 47 | } 48 | 49 | /// Get the bucket with the specified identifier. 50 | /// 51 | /// `identifier` must refer to a bucket provided by the host. 52 | /// 53 | /// `error::no-such-store` will be raised if the `identifier` is not recognized. 54 | open: func(identifier: string) -> result; 55 | 56 | /// A bucket is a collection of key-value pairs. Each key-value pair is stored as a entry in the 57 | /// bucket, and the bucket itself acts as a collection of all these entries. 58 | /// 59 | /// It is worth noting that the exact terminology for bucket in key-value stores can very 60 | /// depending on the specific implementation. For example: 61 | /// 62 | /// 1. Amazon DynamoDB calls a collection of key-value pairs a table 63 | /// 2. Redis has hashes, sets, and sorted sets as different types of collections 64 | /// 3. Cassandra calls a collection of key-value pairs a column family 65 | /// 4. MongoDB calls a collection of key-value pairs a collection 66 | /// 5. Riak calls a collection of key-value pairs a bucket 67 | /// 6. Memcached calls a collection of key-value pairs a slab 68 | /// 7. Azure Cosmos DB calls a collection of key-value pairs a container 69 | /// 70 | /// In this interface, we use the term `bucket` to refer to a collection of key-value pairs 71 | resource bucket { 72 | /// Get the value associated with the specified `key` 73 | /// 74 | /// The value is returned as an option. If the key-value pair exists in the 75 | /// store, it returns `Ok(value)`. If the key does not exist in the 76 | /// store, it returns `Ok(none)`. 77 | /// 78 | /// If any other error occurs, it returns an `Err(error)`. 79 | get: func(key: string) -> result>, error>; 80 | 81 | /// Set the value associated with the key in the store. If the key already 82 | /// exists in the store, it overwrites the value. 83 | /// 84 | /// If the key does not exist in the store, it creates a new key-value pair. 85 | /// 86 | /// If any other error occurs, it returns an `Err(error)`. 87 | set: func(key: string, value: list) -> result<_, error>; 88 | 89 | /// Delete the key-value pair associated with the key in the store. 90 | /// 91 | /// If the key does not exist in the store, it does nothing. 92 | /// 93 | /// If any other error occurs, it returns an `Err(error)`. 94 | delete: func(key: string) -> result<_, error>; 95 | 96 | /// Check if the key exists in the store. 97 | /// 98 | /// If the key exists in the store, it returns `Ok(true)`. If the key does 99 | /// not exist in the store, it returns `Ok(false)`. 100 | /// 101 | /// If any other error occurs, it returns an `Err(error)`. 102 | exists: func(key: string) -> result; 103 | 104 | /// Get all the keys in the store with an optional cursor (for use in pagination). It 105 | /// returns a list of keys. Please note that for most KeyValue implementations, this is a 106 | /// can be a very expensive operation and so it should be used judiciously. Implementations 107 | /// can return any number of keys in a single response, but they should never attempt to 108 | /// send more data than is reasonable (i.e. on a small edge device, this may only be a few 109 | /// KB, while on a large machine this could be several MB). Any response should also return 110 | /// a cursor that can be used to fetch the next page of keys. See the `key-response` record 111 | /// for more information. 112 | /// 113 | /// Note that the keys are not guaranteed to be returned in any particular order. 114 | /// 115 | /// If the store is empty, it returns an empty list. 116 | /// 117 | /// MAY show an out-of-date list of keys if there are concurrent writes to the store. 118 | /// 119 | /// If any error occurs, it returns an `Err(error)`. 120 | list-keys: func(cursor: option) -> result; 121 | } 122 | } 123 | --------------------------------------------------------------------------------