├── examples ├── redis │ ├── go.sum │ ├── README.md │ ├── go.mod │ ├── main.go │ └── spin.toml ├── llm │ ├── .gitignore │ ├── go.sum │ ├── go.mod │ ├── spin.toml │ └── main.go ├── sqlite │ ├── .gitignore │ ├── go.sum │ ├── go.mod │ ├── db │ │ └── pets.sql │ ├── spin.toml │ └── main.go ├── mysql-outbound │ ├── .gitignore │ ├── go.sum │ ├── go.mod │ ├── db │ │ └── pets.sql │ ├── spin.toml │ └── main.go ├── http │ ├── go.sum │ ├── go.mod │ ├── spin.toml │ ├── main.go │ └── README.md ├── key-value │ ├── go.sum │ ├── go.mod │ ├── spin.toml │ ├── README.md │ └── main.go ├── variables │ ├── go.sum │ ├── go.mod │ ├── README.md │ ├── main.go │ └── spin.toml ├── http-router │ ├── go.sum │ ├── go.mod │ ├── spin.toml │ ├── README.md │ └── main.go ├── pg-outbound │ ├── go.sum │ ├── go.mod │ ├── db │ │ └── pets.sql │ ├── spin.toml │ └── main.go ├── redis-outbound │ ├── go.sum │ ├── go.mod │ ├── README.md │ ├── spin.toml │ └── main.go └── http-outbound │ ├── hello │ ├── go.sum │ ├── go.mod │ └── main.go │ ├── http-to-same-app │ ├── go.sum │ ├── go.mod │ └── main.go │ ├── spin.toml │ └── README.md ├── .gitignore ├── go.mod ├── wit ├── spin-http.wit ├── spin-redis.wit ├── wasi-outbound-http.wit ├── pg-types.wit ├── spin-config.wit ├── mysql-types.wit ├── outbound-pg.wit ├── outbound-mysql.wit ├── redis-types.wit ├── rdbms-types.wit ├── sqlite.wit ├── http-types.wit ├── outbound-redis.wit └── key-value.wit ├── http ├── testdata │ ├── http-tinygo │ │ ├── Makefile │ │ ├── spin.toml │ │ └── main.go │ └── spin-roundtrip │ │ ├── Makefile │ │ ├── spin.toml │ │ └── main.go ├── response.go ├── spin-http.h ├── wasi-outbound-http.h ├── internals.go ├── http.go ├── spin-http.c ├── outbound_internals.go └── wasi-outbound-http.c ├── go.sum ├── variables ├── variables.go ├── internals.go ├── spin-config.h └── spin-config.c ├── MAINTAINERS.md ├── sdk_version └── sdk-version-go-template.c ├── internal └── db │ └── driver.go ├── readme.md ├── sqlite ├── internals_test.go ├── doc.go ├── sqlite.h ├── internals.go ├── sqlite.go └── sqlite.c ├── redis ├── internals_test.go ├── spin-redis.h ├── spin-redis.c ├── outbound-redis.h ├── redis.go └── internals.go ├── .github └── workflows │ └── build.yml ├── llm ├── llm.go ├── llm.h ├── internals.go └── llm.c ├── kv ├── key-value.h └── kv.go ├── Makefile ├── integration_test.go ├── mysql ├── mysql.go ├── outbound-mysql.h └── outbound-mysql.c └── pg ├── pg.go ├── outbound-pg.h └── outbound-pg.c /examples/redis/go.sum: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/llm/.gitignore: -------------------------------------------------------------------------------- 1 | main.wasm 2 | .spin/ 3 | -------------------------------------------------------------------------------- /examples/sqlite/.gitignore: -------------------------------------------------------------------------------- 1 | main.wasm 2 | .spin/ 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .spin 2 | main.wasm 3 | */sdk-version-go.c 4 | -------------------------------------------------------------------------------- /examples/mysql-outbound/.gitignore: -------------------------------------------------------------------------------- 1 | main.wasm 2 | .spin/ 3 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/spinframework/spin-go-sdk/v2 2 | 3 | go 1.20 4 | 5 | require github.com/julienschmidt/httprouter v1.3.0 6 | -------------------------------------------------------------------------------- /wit/spin-http.wit: -------------------------------------------------------------------------------- 1 | use * from http-types 2 | 3 | // The entrypoint for an HTTP handler. 4 | handle-http-request: func(req: request) -> response 5 | -------------------------------------------------------------------------------- /http/testdata/http-tinygo/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: build 2 | build: 3 | tinygo build -target=wasip1 -gc=leaking -buildmode=c-shared -no-debug -o main.wasm . 4 | -------------------------------------------------------------------------------- /http/testdata/spin-roundtrip/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: build 2 | build: 3 | tinygo build -target=wasip1 -gc=leaking -buildmode=c-shared -no-debug -o main.wasm . 4 | -------------------------------------------------------------------------------- /wit/spin-redis.wit: -------------------------------------------------------------------------------- 1 | use * from redis-types 2 | 3 | // The entrypoint for a Redis handler. 4 | handle-redis-message: func(message: payload) -> expected 5 | -------------------------------------------------------------------------------- /wit/wasi-outbound-http.wit: -------------------------------------------------------------------------------- 1 | use * from http-types 2 | 3 | // Send an HTTP request and return a response or a potential error. 4 | request: func(req: request) -> expected 5 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= 2 | github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= 3 | -------------------------------------------------------------------------------- /examples/http/go.sum: -------------------------------------------------------------------------------- 1 | github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= 2 | github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= 3 | -------------------------------------------------------------------------------- /examples/llm/go.sum: -------------------------------------------------------------------------------- 1 | github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= 2 | github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= 3 | -------------------------------------------------------------------------------- /examples/key-value/go.sum: -------------------------------------------------------------------------------- 1 | github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= 2 | github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= 3 | -------------------------------------------------------------------------------- /examples/sqlite/go.sum: -------------------------------------------------------------------------------- 1 | github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= 2 | github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= 3 | -------------------------------------------------------------------------------- /examples/variables/go.sum: -------------------------------------------------------------------------------- 1 | github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= 2 | github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= 3 | -------------------------------------------------------------------------------- /examples/http-router/go.sum: -------------------------------------------------------------------------------- 1 | github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= 2 | github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= 3 | -------------------------------------------------------------------------------- /examples/mysql-outbound/go.sum: -------------------------------------------------------------------------------- 1 | github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= 2 | github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= 3 | -------------------------------------------------------------------------------- /examples/pg-outbound/go.sum: -------------------------------------------------------------------------------- 1 | github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= 2 | github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= 3 | -------------------------------------------------------------------------------- /examples/redis-outbound/go.sum: -------------------------------------------------------------------------------- 1 | github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= 2 | github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= 3 | -------------------------------------------------------------------------------- /examples/http-outbound/hello/go.sum: -------------------------------------------------------------------------------- 1 | github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= 2 | github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= 3 | -------------------------------------------------------------------------------- /examples/http-outbound/http-to-same-app/go.sum: -------------------------------------------------------------------------------- 1 | github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= 2 | github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= 3 | -------------------------------------------------------------------------------- /variables/variables.go: -------------------------------------------------------------------------------- 1 | package variables 2 | 3 | // Get an application variable value for the current component. 4 | // 5 | // The name must match one defined in in the component manifest. 6 | func Get(key string) (string, error) { 7 | return get(key) 8 | } 9 | -------------------------------------------------------------------------------- /examples/redis/README.md: -------------------------------------------------------------------------------- 1 | # Spin component in TinyGo using the Redis trigger 2 | 3 | ```shell 4 | $ RUST_LOG=spin=trace spin build --up 5 | ``` 6 | 7 | ```shell 8 | $ redis-cli 9 | 127.0.0.1:6379> PUBLISH messages test-message 10 | (integer) 1 11 | ``` 12 | -------------------------------------------------------------------------------- /wit/pg-types.wit: -------------------------------------------------------------------------------- 1 | // General purpose error. 2 | variant pg-error { 3 | success, 4 | connection-failed(string), 5 | bad-parameter(string), 6 | query-failed(string), 7 | value-conversion-failed(string), 8 | other-error(string) 9 | } 10 | -------------------------------------------------------------------------------- /examples/http/go.mod: -------------------------------------------------------------------------------- 1 | module examples/http 2 | 3 | go 1.20 4 | 5 | require github.com/spinframework/spin-go-sdk/v2 v2.2.1 6 | 7 | require github.com/julienschmidt/httprouter v1.3.0 // indirect 8 | 9 | replace github.com/spinframework/spin-go-sdk/v2 v2.2.1 => ../../ 10 | -------------------------------------------------------------------------------- /examples/llm/go.mod: -------------------------------------------------------------------------------- 1 | module examples/llm 2 | 3 | go 1.20 4 | 5 | require github.com/spinframework/spin-go-sdk/v2 v2.2.1 6 | 7 | require github.com/julienschmidt/httprouter v1.3.0 // indirect 8 | 9 | replace github.com/spinframework/spin-go-sdk/v2 v2.2.1 => ../../ 10 | -------------------------------------------------------------------------------- /examples/redis/go.mod: -------------------------------------------------------------------------------- 1 | module examples/redis 2 | 3 | go 1.20 4 | 5 | require github.com/spinframework/spin-go-sdk/v2 v2.2.1 6 | 7 | require github.com/julienschmidt/httprouter v1.3.0 // indirect 8 | 9 | replace github.com/spinframework/spin-go-sdk/v2 v2.2.1 => ../../ 10 | -------------------------------------------------------------------------------- /examples/sqlite/go.mod: -------------------------------------------------------------------------------- 1 | module examples/sqlite 2 | 3 | go 1.20 4 | 5 | require github.com/spinframework/spin-go-sdk/v2 v2.2.1 6 | 7 | require github.com/julienschmidt/httprouter v1.3.0 // indirect 8 | 9 | replace github.com/spinframework/spin-go-sdk/v2 v2.2.1 => ../../ 10 | -------------------------------------------------------------------------------- /examples/key-value/go.mod: -------------------------------------------------------------------------------- 1 | module examples/key-value 2 | 3 | go 1.20 4 | 5 | require github.com/spinframework/spin-go-sdk/v2 v2.2.1 6 | 7 | require github.com/julienschmidt/httprouter v1.3.0 // indirect 8 | 9 | replace github.com/spinframework/spin-go-sdk/v2 v2.2.1 => ../../ 10 | -------------------------------------------------------------------------------- /examples/variables/go.mod: -------------------------------------------------------------------------------- 1 | module examples/variables 2 | 3 | go 1.20 4 | 5 | require github.com/spinframework/spin-go-sdk/v2 v2.2.1 6 | 7 | require github.com/julienschmidt/httprouter v1.3.0 // indirect 8 | 9 | replace github.com/spinframework/spin-go-sdk/v2 v2.2.1 => ../../ 10 | -------------------------------------------------------------------------------- /examples/http-router/go.mod: -------------------------------------------------------------------------------- 1 | module examples/http-router 2 | 3 | go 1.20 4 | 5 | require github.com/spinframework/spin-go-sdk/v2 v2.2.1 6 | 7 | require github.com/julienschmidt/httprouter v1.3.0 // indirect 8 | 9 | replace github.com/spinframework/spin-go-sdk/v2 v2.2.1 => ../../ 10 | -------------------------------------------------------------------------------- /examples/pg-outbound/go.mod: -------------------------------------------------------------------------------- 1 | module examples/pg-outbound 2 | 3 | go 1.20 4 | 5 | require github.com/spinframework/spin-go-sdk/v2 v2.2.1 6 | 7 | require github.com/julienschmidt/httprouter v1.3.0 // indirect 8 | 9 | replace github.com/spinframework/spin-go-sdk/v2 v2.2.1 => ../../ 10 | -------------------------------------------------------------------------------- /examples/mysql-outbound/go.mod: -------------------------------------------------------------------------------- 1 | module examples/mysql-outbound 2 | 3 | go 1.20 4 | 5 | require github.com/spinframework/spin-go-sdk/v2 v2.2.1 6 | 7 | require github.com/julienschmidt/httprouter v1.3.0 // indirect 8 | 9 | replace github.com/spinframework/spin-go-sdk/v2 v2.2.1 => ../../ 10 | -------------------------------------------------------------------------------- /examples/redis-outbound/go.mod: -------------------------------------------------------------------------------- 1 | module examples/redis-outbound 2 | 3 | go 1.20 4 | 5 | require github.com/spinframework/spin-go-sdk/v2 v2.2.1 6 | 7 | require github.com/julienschmidt/httprouter v1.3.0 // indirect 8 | 9 | replace github.com/spinframework/spin-go-sdk/v2 v2.2.1 => ../../ 10 | -------------------------------------------------------------------------------- /examples/http-outbound/hello/go.mod: -------------------------------------------------------------------------------- 1 | module examples/http-outbound/hello 2 | 3 | go 1.20 4 | 5 | require github.com/spinframework/spin-go-sdk/v2 v2.2.1 6 | 7 | require github.com/julienschmidt/httprouter v1.3.0 // indirect 8 | 9 | replace github.com/spinframework/spin-go-sdk/v2 v2.2.1 => ../../../ 10 | -------------------------------------------------------------------------------- /examples/sqlite/db/pets.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE pets (id INT PRIMARY KEY, name VARCHAR(100) NOT NULL, prey VARCHAR(100), is_finicky BOOL NOT NULL); 2 | INSERT INTO pets VALUES (1, 'Splodge', NULL, false); 3 | INSERT INTO pets VALUES (2, 'Kiki', 'Cicadas', false); 4 | INSERT INTO pets VALUES (3, 'Slats', 'Temptations', true); 5 | -------------------------------------------------------------------------------- /examples/pg-outbound/db/pets.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE pets (id INT PRIMARY KEY, name VARCHAR(100) NOT NULL, prey VARCHAR(100), is_finicky BOOL NOT NULL); 2 | INSERT INTO pets VALUES (1, 'Splodge', NULL, false); 3 | INSERT INTO pets VALUES (2, 'Kiki', 'Cicadas', false); 4 | INSERT INTO pets VALUES (3, 'Slats', 'Temptations', true); 5 | -------------------------------------------------------------------------------- /examples/mysql-outbound/db/pets.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE pets (id INT PRIMARY KEY, name VARCHAR(100) NOT NULL, prey VARCHAR(100), is_finicky BOOL NOT NULL); 2 | INSERT INTO pets VALUES (1, 'Splodge', NULL, false); 3 | INSERT INTO pets VALUES (2, 'Kiki', 'Cicadas', false); 4 | INSERT INTO pets VALUES (3, 'Slats', 'Temptations', true); 5 | -------------------------------------------------------------------------------- /examples/http-outbound/http-to-same-app/go.mod: -------------------------------------------------------------------------------- 1 | module examples/http-outbound/http-to-same-app 2 | 3 | go 1.20 4 | 5 | require github.com/spinframework/spin-go-sdk/v2 v2.2.1 6 | 7 | require github.com/julienschmidt/httprouter v1.3.0 // indirect 8 | 9 | replace github.com/spinframework/spin-go-sdk/v2 v2.2.1 => ../../../ 10 | -------------------------------------------------------------------------------- /wit/spin-config.wit: -------------------------------------------------------------------------------- 1 | // Get a configuration value for the current component. 2 | // The config key must match one defined in in the component manifest. 3 | get-config: func(key: string) -> expected 4 | 5 | variant error { 6 | provider(string), 7 | invalid-key(string), 8 | invalid-schema(string), 9 | other(string), 10 | } -------------------------------------------------------------------------------- /wit/mysql-types.wit: -------------------------------------------------------------------------------- 1 | // General purpose error. 2 | // TODO: We can provide richer info than this: https://docs.rs/mysql/latest/mysql/error/enum.Error.html 3 | variant mysql-error { 4 | success, 5 | connection-failed(string), 6 | bad-parameter(string), 7 | query-failed(string), 8 | value-conversion-failed(string), 9 | other-error(string) 10 | } 11 | -------------------------------------------------------------------------------- /MAINTAINERS.md: -------------------------------------------------------------------------------- 1 | # MAINTAINERS 2 | 3 | ## Current Maintainers 4 | 5 | _Listed in alphabetical order by first name_ 6 | 7 | | Name | GitHub Username | 8 | | --- | --- | 9 | | Adam Reese | adamreese | 10 | | Lann Martin | lann | 11 | | Radu Matei | radu-matei | 12 | | Rajat Jindal | rajatjindal | 13 | | Ryan Levick | rylev | 14 | 15 | ## Emeritus Maintainers 16 | 17 | None 18 | -------------------------------------------------------------------------------- /wit/outbound-pg.wit: -------------------------------------------------------------------------------- 1 | use * from pg-types 2 | use * from rdbms-types 3 | 4 | // query the database: select 5 | query: func(address: string, statement: string, params: list) -> expected 6 | 7 | // execute command to the database: insert, update, delete 8 | execute: func(address: string, statement: string, params: list) -> expected 9 | -------------------------------------------------------------------------------- /sdk_version/sdk-version-go-template.c: -------------------------------------------------------------------------------- 1 | __attribute__((weak, export_name("spin-sdk-version-{{VERSION}}"))) 2 | void __spin_sdk_version(void) { 3 | 4 | } 5 | 6 | __attribute__((weak, export_name("spin-sdk-language-go"))) 7 | void __spin_sdk_language(void) { 8 | 9 | } 10 | 11 | __attribute__((weak, export_name("spin-sdk-commit-{{COMMIT}}"))) 12 | void __spin_sdk_commit(void) { 13 | 14 | } 15 | -------------------------------------------------------------------------------- /wit/outbound-mysql.wit: -------------------------------------------------------------------------------- 1 | use * from mysql-types 2 | use * from rdbms-types 3 | 4 | // query the database: select 5 | query: func(address: string, statement: string, params: list) -> expected 6 | 7 | // execute command to the database: insert, update, delete 8 | execute: func(address: string, statement: string, params: list) -> expected 9 | -------------------------------------------------------------------------------- /examples/variables/README.md: -------------------------------------------------------------------------------- 1 | # Spin component in TinyGo using variables 2 | 3 | ```shell 4 | $ go mod tidy 5 | $ RUST_LOG=spin=trace spin build --up 6 | ``` 7 | 8 | The application can now receive requests on `http://localhost:3000`: 9 | 10 | ```shell 11 | $ curl -i localhost:3000 12 | HTTP/1.1 200 OK 13 | content-length: 23 14 | date: Tue, 29 Nov 2022 06:59:24 GMT 15 | 16 | message: I'm a teapot 17 | ``` 18 | -------------------------------------------------------------------------------- /examples/redis/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/spinframework/spin-go-sdk/v2/redis" 7 | ) 8 | 9 | func init() { 10 | // redis.Handle() must be called in the init() function. 11 | redis.Handle(func(payload []byte) error { 12 | fmt.Println("Payload::::") 13 | fmt.Println(string(payload)) 14 | return nil 15 | }) 16 | } 17 | 18 | // main function must be included for the compiler but is not executed. 19 | func main() {} 20 | -------------------------------------------------------------------------------- /examples/key-value/spin.toml: -------------------------------------------------------------------------------- 1 | spin_manifest_version = 2 2 | 3 | [application] 4 | authors = ["Fermyon Engineering "] 5 | name = "key-value-example" 6 | version = "0.1.0" 7 | 8 | [[trigger.http]] 9 | route = "/..." 10 | component = "key-value" 11 | 12 | [component.key-value] 13 | source = "main.wasm" 14 | key_value_stores = ["default"] 15 | [component.key-value.build] 16 | command = "tinygo build -target=wasip1 -gc=leaking -buildmode=c-shared -no-debug -o main.wasm ." 17 | -------------------------------------------------------------------------------- /internal/db/driver.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "database/sql/driver" 5 | ) 6 | 7 | // GlobalParameterConverter is a global valueConverter instance to convert parameters. 8 | var GlobalParameterConverter = &valueConverter{} 9 | 10 | var _ driver.ValueConverter = (*valueConverter)(nil) 11 | 12 | // valueConverter is a no-op value converter. 13 | type valueConverter struct{} 14 | 15 | func (c *valueConverter) ConvertValue(v any) (driver.Value, error) { 16 | return driver.Value(v), nil 17 | } 18 | -------------------------------------------------------------------------------- /examples/http/spin.toml: -------------------------------------------------------------------------------- 1 | spin_manifest_version = 2 2 | 3 | [application] 4 | authors = ["Fermyon Engineering "] 5 | description = "A simple Spin application written in (Tiny)Go." 6 | name = "hello-example" 7 | version = "1.0.0" 8 | 9 | [[trigger.http]] 10 | route = "/hello" 11 | component = "hello" 12 | 13 | [component.hello] 14 | source = "main.wasm" 15 | [component.hello.build] 16 | command = "tinygo build -target=wasip1 -gc=leaking -buildmode=c-shared -no-debug -o main.wasm ." 17 | -------------------------------------------------------------------------------- /examples/redis-outbound/README.md: -------------------------------------------------------------------------------- 1 | # Spin component in TinyGo making an outbound http call to Redis 2 | 3 | ```shell 4 | $ go mod tidy 5 | $ RUST_LOG=spin=trace spin build --up 6 | ``` 7 | 8 | The application can now receive requests on `http://localhost:3000/publish`: 9 | 10 | ```shell 11 | $ curl -i localhost:3000/publish 12 | HTTP/1.1 200 OK 13 | content-length: 67 14 | date: Tue, 29 Nov 2022 07:03:52 GMT 15 | 16 | mykey value was: myvalue 17 | spin-go-incr value: 1 18 | deleted keys num: 2 19 | ``` 20 | -------------------------------------------------------------------------------- /wit/redis-types.wit: -------------------------------------------------------------------------------- 1 | // General purpose error. 2 | enum error { 3 | success, 4 | error, 5 | } 6 | 7 | // The message payload. 8 | type payload = list 9 | 10 | // A parameter type for the general-purpose `execute` function. 11 | variant redis-parameter { 12 | int64(s64), 13 | binary(payload) 14 | } 15 | 16 | // A return type for the general-purpose `execute` function. 17 | variant redis-result { 18 | nil, 19 | status(string), 20 | int64(s64), 21 | binary(payload) 22 | } 23 | -------------------------------------------------------------------------------- /examples/http-router/spin.toml: -------------------------------------------------------------------------------- 1 | spin_manifest_version = 2 2 | 3 | [application] 4 | authors = ["Fermyon Engineering "] 5 | description = "A simple Spin application written in (Tiny)Go." 6 | name = "hello-router-example" 7 | version = "1.0.0" 8 | 9 | [[trigger.http]] 10 | route = "/..." 11 | component = "hello" 12 | 13 | [component.hello] 14 | source = "main.wasm" 15 | [component.hello.build] 16 | command = "tinygo build -target=wasip1 -gc=leaking -buildmode=c-shared -no-debug -o main.wasm ." 17 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # The (Tiny)Go SDK for Spin 2 | 3 | This package contains an SDK that facilitates building Spin components in 4 | (Tiny)Go. It allows building HTTP components that target the Spin 5 | executor. 6 | 7 | ```go 8 | import ( 9 | "fmt" 10 | spinhttp "github.com/spinframework/spin-go-sdk/v2/http" 11 | ) 12 | 13 | func init() { 14 | // call the Handle function 15 | spinhttp.Handle(func(w http.ResponseWriter, r *http.Request) { 16 | fmt.Fprintln(w, "Hello, Fermyon!") 17 | }) 18 | } 19 | 20 | func main() {} 21 | ``` 22 | -------------------------------------------------------------------------------- /examples/sqlite/spin.toml: -------------------------------------------------------------------------------- 1 | spin_manifest_version = 2 2 | 3 | [application] 4 | authors = ["Adam Reese "] 5 | description = "" 6 | name = "sqlite-example" 7 | version = "0.1.0" 8 | 9 | [[trigger.http]] 10 | route = "/..." 11 | component = "sqlite" 12 | 13 | [component.sqlite] 14 | source = "main.wasm" 15 | allowed_outbound_hosts = [] 16 | sqlite_databases = ["default"] 17 | [component.sqlite.build] 18 | command = "tinygo build -target=wasip1 -gc=leaking -buildmode=c-shared -no-debug -o main.wasm ." 19 | watch = ["**/*.go", "go.mod"] 20 | -------------------------------------------------------------------------------- /examples/redis/spin.toml: -------------------------------------------------------------------------------- 1 | spin_manifest_version = 2 2 | 3 | [application] 4 | authors = ["Fermyon Engineering "] 5 | description = "A redis application." 6 | name = "redis-example" 7 | version = "0.1.0" 8 | 9 | [application.trigger.redis] 10 | address = "redis://localhost:6379" 11 | 12 | [[trigger.redis]] 13 | channel = "messages" 14 | component = "echo-message" 15 | 16 | [component.echo-message] 17 | source = "main.wasm" 18 | [component.echo-message.build] 19 | command = "tinygo build -target=wasip1 -gc=leaking -buildmode=c-shared -no-debug -o main.wasm ." 20 | -------------------------------------------------------------------------------- /examples/http-router/README.md: -------------------------------------------------------------------------------- 1 | # Spin component in TinyGo using the Spin router 2 | 3 | ```shell 4 | $ go mod tidy 5 | $ RUST_LOG=spin=trace spin build --up 6 | ``` 7 | 8 | The application can now receive requests on `http://localhost:3000`: 9 | 10 | ```shell 11 | $ curl -i localhost:3000/hello/Fermyon 12 | HTTP/1.1 200 OK 13 | content-length: 16 14 | date: Thu, 26 Oct 2023 18:30:05 GMT 15 | 16 | hello, Fermyon! 17 | 18 | $ curl -i localhost:3000/this/will/be-special 19 | HTTP/1.1 200 OK 20 | content-length: 24 21 | date: Thu, 26 Oct 2023 18:30:21 GMT 22 | 23 | catch all: /be-special! 24 | ``` 25 | -------------------------------------------------------------------------------- /examples/llm/spin.toml: -------------------------------------------------------------------------------- 1 | spin_manifest_version = 2 2 | 3 | [application] 4 | authors = ["Fermyon Engineering "] 5 | description = "Simple example using the llm sdk." 6 | name = "llm-example" 7 | version = "0.1.0" 8 | 9 | [[trigger.http]] 10 | route = "/..." 11 | component = "llm" 12 | 13 | [component.llm] 14 | source = "main.wasm" 15 | allowed_outbound_hosts = [] 16 | ai_models = ["llama2-chat", "all-minilm-l6-v2"] 17 | [component.llm.build] 18 | command = "tinygo build -target=wasip1 -gc=leaking -buildmode=c-shared -no-debug -o main.wasm ." 19 | watch = ["**/*.go", "go.mod"] 20 | -------------------------------------------------------------------------------- /examples/variables/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | spinhttp "github.com/spinframework/spin-go-sdk/v2/http" 8 | "github.com/spinframework/spin-go-sdk/v2/variables" 9 | ) 10 | 11 | func init() { 12 | spinhttp.Handle(func(w http.ResponseWriter, r *http.Request) { 13 | 14 | // Get variable value `message` defined in spin.toml. 15 | val, err := variables.Get("message") 16 | if err != nil { 17 | http.Error(w, err.Error(), http.StatusInternalServerError) 18 | return 19 | } 20 | fmt.Fprintln(w, "message: ", val) 21 | }) 22 | } 23 | 24 | func main() {} 25 | -------------------------------------------------------------------------------- /http/testdata/http-tinygo/spin.toml: -------------------------------------------------------------------------------- 1 | spin_manifest_version = 2 2 | 3 | [application] 4 | name = "spin-http-tinygo-test" 5 | version = "0.1.0" 6 | authors = ["Fermyon Engineering "] 7 | description = "A simple Spin application written in (Tiny)Go." 8 | 9 | [[trigger.http]] 10 | route = "/hello/..." 11 | component = "http-test" 12 | 13 | [component.http-test] 14 | source = "main.wasm" 15 | allowed_outbound_hosts = [] 16 | [component.http-test.build] 17 | command = "tinygo build -target=wasip1 -gc=leaking -buildmode=c-shared -no-debug -o main.wasm ." 18 | watch = ["**/*.go", "go.mod"] 19 | -------------------------------------------------------------------------------- /examples/mysql-outbound/spin.toml: -------------------------------------------------------------------------------- 1 | spin_manifest_version = 2 2 | 3 | [application] 4 | authors = ["Patrick Jiang "] 5 | description = "" 6 | name = "mysql-outbound-example" 7 | version = "0.1.0" 8 | 9 | [[trigger.http]] 10 | route = "/..." 11 | component = "mysql" 12 | 13 | [component.mysql] 14 | environment = { DB_URL = "mysql://spin:spin@127.0.0.1/spin_dev" } 15 | source = "main.wasm" 16 | allowed_outbound_hosts = ["mysql://127.0.0.1"] 17 | [component.mysql.build] 18 | command = "tinygo build -target=wasip1 -gc=leaking -buildmode=c-shared -no-debug -o main.wasm ." 19 | watch = ["**/*.go", "go.mod"] 20 | -------------------------------------------------------------------------------- /examples/redis-outbound/spin.toml: -------------------------------------------------------------------------------- 1 | spin_manifest_version = 2 2 | 3 | [application] 4 | authors = ["Fermyon Engineering "] 5 | name = "redis-outbound-example" 6 | version = "0.1.0" 7 | 8 | [[trigger.http]] 9 | route = "/publish" 10 | component = "outbound-redis" 11 | 12 | [component.outbound-redis] 13 | source = "main.wasm" 14 | environment = { REDIS_ADDRESS = "redis://127.0.0.1:6379", REDIS_CHANNEL = "messages" } 15 | allowed_outbound_hosts = ["redis://127.0.0.1"] 16 | [component.outbound-redis.build] 17 | command = "tinygo build -target=wasip1 -gc=leaking -buildmode=c-shared -no-debug -o main.wasm ." 18 | -------------------------------------------------------------------------------- /examples/pg-outbound/spin.toml: -------------------------------------------------------------------------------- 1 | spin_manifest_version = 2 2 | 3 | [application] 4 | authors = ["Patrick Jiang "] 5 | description = "" 6 | name = "pg-outbound-example" 7 | version = "0.1.0" 8 | 9 | [[trigger.http]] 10 | route = "/..." 11 | component = "pg-outbound" 12 | 13 | [component.pg-outbound] 14 | environment = { DB_URL = "host=localhost user=postgres dbname=spin_dev" } 15 | source = "main.wasm" 16 | allowed_outbound_hosts = ["postgres://localhost"] 17 | [component.pg-outbound.build] 18 | command = "tinygo build -target=wasip1 -gc=leaking -buildmode=c-shared -no-debug -o main.wasm ." 19 | watch = ["**/*.go", "go.mod"] 20 | -------------------------------------------------------------------------------- /examples/variables/spin.toml: -------------------------------------------------------------------------------- 1 | spin_manifest_version = 2 2 | 3 | [application] 4 | name = "variables-example" 5 | authors = ["Fermyon Engineering "] 6 | description = "A simple Spin application written in (Tiny)Go." 7 | version = "1.0.0" 8 | 9 | [variables] 10 | object = { default = "teapot" } 11 | 12 | [[trigger.http]] 13 | route = "/..." 14 | component = "variables" 15 | 16 | [component.variables] 17 | source = "main.wasm" 18 | [component.variables.variables] 19 | message = "I'm a {{object}}" 20 | [component.variables.build] 21 | command = "tinygo build -target=wasip1 -gc=leaking -buildmode=c-shared -no-debug -o main.wasm ." 22 | -------------------------------------------------------------------------------- /http/testdata/spin-roundtrip/spin.toml: -------------------------------------------------------------------------------- 1 | spin_manifest_version = 2 2 | 3 | [application] 4 | name = "spin-roundtrip-test" 5 | version = "0.1.0" 6 | authors = ["Fermyon Engineering "] 7 | description = "A simple Spin application written in (Tiny)Go." 8 | 9 | [[trigger.http]] 10 | route = "/hello/..." 11 | component = "http-roundtrip-test" 12 | 13 | [component.http-roundtrip-test] 14 | source = "main.wasm" 15 | allowed_outbound_hosts = ["https://example.com"] 16 | [component.http-roundtrip-test.build] 17 | command = "tinygo build -target=wasip1 -gc=leaking -buildmode=c-shared -no-debug -o main.wasm ." 18 | watch = ["**/*.go", "go.mod"] 19 | -------------------------------------------------------------------------------- /examples/http-outbound/http-to-same-app/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | spinhttp "github.com/spinframework/spin-go-sdk/v2/http" 8 | ) 9 | 10 | func init() { 11 | spinhttp.Handle(func(w http.ResponseWriter, r *http.Request) { 12 | // Because we included self in `allowed_http_hosts`, we can make outbound 13 | // HTTP requests to our own app using a relative path. 14 | resp, err := spinhttp.Get("/hello") 15 | if err != nil { 16 | http.Error(w, err.Error(), http.StatusInternalServerError) 17 | return 18 | } 19 | 20 | fmt.Fprintln(w, resp.Body) 21 | fmt.Fprintln(w, resp.Header.Get("content-type")) 22 | }) 23 | } 24 | 25 | func main() {} 26 | -------------------------------------------------------------------------------- /examples/http-router/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | spinhttp "github.com/spinframework/spin-go-sdk/v2/http" 8 | ) 9 | 10 | func init() { 11 | spinhttp.Handle(func(w http.ResponseWriter, r *http.Request) { 12 | router := spinhttp.NewRouter() 13 | router.GET("/hello/:name", Hello) 14 | router.GET("/this/will/*catchAll", CatchAll) 15 | 16 | router.ServeHTTP(w, r) 17 | }) 18 | } 19 | 20 | func Hello(w http.ResponseWriter, _ *http.Request, ps spinhttp.Params) { 21 | fmt.Fprintf(w, "hello, %s!\n", ps.ByName("name")) 22 | } 23 | 24 | func CatchAll(w http.ResponseWriter, _ *http.Request, ps spinhttp.Params) { 25 | fmt.Fprintf(w, "catch all: %s!\n", ps.ByName("catchAll")) 26 | } 27 | 28 | func main() {} 29 | -------------------------------------------------------------------------------- /sqlite/internals_test.go: -------------------------------------------------------------------------------- 1 | package sqlite 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestValue(t *testing.T) { 9 | tests := []any{ 10 | int64(1234), 11 | 3.14, 12 | "foo", 13 | []byte("bar"), 14 | nil, 15 | } 16 | 17 | for _, tc := range tests { 18 | got := fromSqliteValue(toSqliteValue(tc)) 19 | if !reflect.DeepEqual(tc, got) { 20 | t.Errorf("want %T(%#v), got %T(%#v)", tc, tc, got, got) 21 | } 22 | } 23 | } 24 | 25 | func TestValueList(t *testing.T) { 26 | tc := []any{ 27 | int64(1234), 28 | 3.14, 29 | "foo", 30 | []byte("bar"), 31 | nil, 32 | } 33 | 34 | got := fromSqliteListValue(toSqliteListValue(tc)) 35 | if !reflect.DeepEqual(tc, got) { 36 | t.Errorf("want %v, got %v", tc, got) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /http/testdata/http-tinygo/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | spinhttp "github.com/spinframework/spin-go-sdk/v2/http" 8 | ) 9 | 10 | func init() { 11 | spinhttp.Handle(func(w http.ResponseWriter, r *http.Request) { 12 | if r.Header.Get("Spin-Raw-Component-Route") != "/hello/..." { 13 | http.Error(w, "Spin-Raw-Component-Route is not /hello/...", http.StatusInternalServerError) 14 | return 15 | } 16 | 17 | if r.Method != "GET" { 18 | http.Error(w, "Method should be GET", http.StatusInternalServerError) 19 | return 20 | } 21 | 22 | w.Header().Set("spin-path-info", r.Header.Get("spin-path-info")) 23 | w.Header().Set("foo", "bar") 24 | 25 | fmt.Fprintln(w, "Hello world!") 26 | }) 27 | } 28 | 29 | func main() {} 30 | -------------------------------------------------------------------------------- /sqlite/doc.go: -------------------------------------------------------------------------------- 1 | // Package sqlite provides an interface to sqlite database stores within Spin 2 | // components. 3 | // 4 | // This package is implemented as a driver that conforms to the built-in 5 | // database/sql interface. 6 | // 7 | // db := sqlite.Open("default") 8 | // defer db.Close() 9 | // 10 | // s, err := db.Prepare("REPLACE INTO pets VALUES (4, 'Maya', ?, false);") 11 | // // if err != nil { ... } 12 | // 13 | // _, err = s.Query("bananas") 14 | // // if err != nil { ... } 15 | // 16 | // rows, err := db.Query("SELECT * FROM pets") 17 | // // if err != nil { ... } 18 | // 19 | // var pets []*Pet 20 | // for rows.Next() { 21 | // var pet Pet 22 | // if err := rows.Scan(&pet.ID, &pet.Name, &pet.Prey, &pet.IsFinicky); err != nil { 23 | // ... 24 | // } 25 | // pets = append(pets, &pet) 26 | // } 27 | package sqlite 28 | -------------------------------------------------------------------------------- /http/response.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "bytes" 5 | "net/http" 6 | ) 7 | 8 | var _ http.ResponseWriter = (*response)(nil) 9 | 10 | // response implements http.ResponseWriter 11 | type response struct { 12 | // status code passed to WriteHeader 13 | status int 14 | 15 | header http.Header 16 | w *bytes.Buffer 17 | } 18 | 19 | func newResponse() *response { 20 | return &response{ 21 | // set default status to StatusOK 22 | status: http.StatusOK, 23 | 24 | header: make(http.Header), 25 | w: new(bytes.Buffer), 26 | } 27 | } 28 | 29 | func (r *response) Header() http.Header { 30 | return r.header 31 | } 32 | 33 | func (r *response) WriteHeader(statusCode int) { 34 | r.status = statusCode 35 | } 36 | 37 | func (r *response) Write(data []byte) (int, error) { 38 | return r.w.Write(data) 39 | } 40 | -------------------------------------------------------------------------------- /http/testdata/spin-roundtrip/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | spinhttp "github.com/spinframework/spin-go-sdk/v2/http" 8 | ) 9 | 10 | func init() { 11 | spinhttp.Handle(func(w http.ResponseWriter, r *http.Request) { 12 | transport := spinhttp.NewTransport() 13 | 14 | // or you can also do client := spinhttp.NewClient() 15 | client := &http.Client{ 16 | Transport: transport, 17 | } 18 | 19 | resp, err := client.Get("https://example.com") 20 | if err != nil { 21 | http.Error(w, err.Error(), http.StatusInternalServerError) 22 | return 23 | } 24 | 25 | w.WriteHeader(resp.StatusCode) 26 | w.Header().Set("spin-path-info", r.Header.Get("spin-path-info")) 27 | w.Header().Set("foo", "bar") 28 | fmt.Fprintln(w, "Hello world!") 29 | }) 30 | } 31 | 32 | func main() {} 33 | -------------------------------------------------------------------------------- /examples/http/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net/http" 7 | 8 | spinhttp "github.com/spinframework/spin-go-sdk/v2/http" 9 | ) 10 | 11 | func init() { 12 | spinhttp.Handle(func(w http.ResponseWriter, r *http.Request) { 13 | w.Header().Set("Content-Type", "text/plain") 14 | w.Header().Set("foo", "bar") 15 | 16 | fmt.Fprintln(w, "== REQUEST ==") 17 | fmt.Fprintln(w, "URL: ", r.URL) 18 | fmt.Fprintln(w, "Method: ", r.Method) 19 | fmt.Fprintln(w, "Headers:") 20 | for k, v := range r.Header { 21 | fmt.Fprintf(w, " %q: %q \n", k, v[0]) 22 | } 23 | 24 | body, err := io.ReadAll(r.Body) 25 | if err != nil { 26 | fmt.Fprintln(w, "Body Error: ", err) 27 | } else { 28 | fmt.Fprintln(w, "Body: ", string(body)) 29 | } 30 | 31 | fmt.Fprintln(w, "== RESPONSE ==") 32 | fmt.Fprintln(w, "Hello Fermyon!") 33 | }) 34 | } 35 | 36 | func main() {} 37 | -------------------------------------------------------------------------------- /examples/http/README.md: -------------------------------------------------------------------------------- 1 | # Spin component in TinyGo 2 | 3 | ```shell 4 | $ go mod tidy 5 | $ RUST_LOG=spin=trace spin build --up 6 | ``` 7 | 8 | The application can now receive requests on `http://localhost:3000`: 9 | 10 | ```shell 11 | $ curl -i localhost:3000/hello 12 | HTTP/1.1 200 OK 13 | content-type: text/plain 14 | foo: bar 15 | content-length: 440 16 | date: Thu, 26 Oct 2023 18:18:19 GMT 17 | 18 | == REQUEST == 19 | URL: http://localhost:3000/hello 20 | Method: GET 21 | Headers: 22 | "Host": "localhost:3000" 23 | "User-Agent": "curl/8.1.2" 24 | "Spin-Full-Url": "http://localhost:3000/hello" 25 | "Spin-Base-Path": "/" 26 | "Spin-Client-Addr": "127.0.0.1:52164" 27 | "Accept": "*/*" 28 | "Spin-Path-Info": "" 29 | "Spin-Matched-Route": "/hello" 30 | "Spin-Raw-Component-Route": "/hello" 31 | "Spin-Component-Route": "/hello" 32 | Body: 33 | == RESPONSE == 34 | Hello Fermyon! 35 | ``` 36 | -------------------------------------------------------------------------------- /examples/key-value/README.md: -------------------------------------------------------------------------------- 1 | # Spin Key Value component in TinyGo 2 | 3 | ```shell 4 | $ go mod tidy 5 | $ RUST_LOG=spin=trace spin build --up 6 | ``` 7 | 8 | The application can now receive requests on `http://localhost:3000`: 9 | 10 | ```shell 11 | $ curl -i -X POST -d "ok!" localhost:3000/test 12 | HTTP/1.1 200 OK 13 | content-length: 0 14 | date: Tue, 25 Apr 2023 14:25:43 GMT 15 | 16 | $ curl -i -X GET localhost:3000/test 17 | HTTP/1.1 200 OK 18 | content-length: 3 19 | date: Tue, 25 Apr 2023 14:25:54 GMT 20 | 21 | ok! 22 | 23 | $ curl -i -X DELETE localhost:3000/test 24 | HTTP/1.1 200 OK 25 | content-length: 0 26 | date: Tue, 25 Apr 2023 14:26:30 GMT 27 | 28 | $ curl -i -X GET localhost:3000/test 29 | HTTP/1.1 500 Internal Server Error 30 | content-type: text/plain; charset=utf-8 31 | x-content-type-options: nosniff 32 | content-length: 12 33 | date: Tue, 25 Apr 2023 14:26:32 GMT 34 | 35 | no such key 36 | ``` 37 | -------------------------------------------------------------------------------- /redis/internals_test.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestCreateParameter(t *testing.T) { 9 | tests := []struct { 10 | in any 11 | want argumentKind 12 | }{ 13 | {in: "a", want: argumentKindBinary}, 14 | {in: []byte("b"), want: argumentKindBinary}, 15 | {in: 1, want: argumentKindInt}, 16 | {in: int64(2), want: argumentKindInt}, 17 | {in: int32(3), want: argumentKindInt}, 18 | } 19 | 20 | for _, tc := range tests { 21 | p, err := createParameter(tc.in) 22 | if err != nil { 23 | t.Error(err) 24 | } 25 | if p.kind != tc.want { 26 | t.Errorf("want %s, got %s", tc.want, p.kind) 27 | } 28 | } 29 | } 30 | 31 | func TestRedisListString(t *testing.T) { 32 | list := []string{"a", "b", "c"} 33 | 34 | rlist := redisListStr(list) 35 | got := fromRedisListStr(&rlist) 36 | 37 | if !reflect.DeepEqual(list, got) { 38 | t.Errorf("want %s, got %s", list, got) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | test: 11 | name: Test 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | 16 | - name: Setup Go 17 | uses: actions/setup-go@v5 18 | with: 19 | go-version: '1.23' 20 | 21 | - name: Setup TinyGo 22 | uses: acifani/setup-tinygo@v2 23 | with: 24 | tinygo-version: '0.35.0' 25 | 26 | - name: Setup Spin 27 | uses: fermyon/actions/spin/setup@v1 28 | with: 29 | version: "v3.1.2" 30 | 31 | - name: Setup Wasmtime 32 | uses: bytecodealliance/actions/wasmtime/setup@v1 33 | with: 34 | version: "28.0.0" 35 | 36 | - name: Run unit tests 37 | run: make test 38 | 39 | - name: Run integration tests 40 | run: make test-integration 41 | -------------------------------------------------------------------------------- /examples/llm/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | spinhttp "github.com/spinframework/spin-go-sdk/v2/http" 8 | "github.com/spinframework/spin-go-sdk/v2/llm" 9 | ) 10 | 11 | func init() { 12 | spinhttp.Handle(func(w http.ResponseWriter, r *http.Request) { 13 | result, err := llm.Infer("llama2-chat", "Tell me a joke", nil) 14 | if err != nil { 15 | http.Error(w, err.Error(), http.StatusInternalServerError) 16 | return 17 | } 18 | fmt.Printf("Prompt tokens: %d\n", result.Usage.PromptTokenCount) 19 | fmt.Printf("Generated tokens: %d\n", result.Usage.GeneratedTokenCount) 20 | fmt.Fprint(w, result.Text) 21 | fmt.Fprintf(w, "\n\n") 22 | 23 | embeddings, err := llm.GenerateEmbeddings("all-minilm-l6-v2", []string{"Hello world"}) 24 | if err != nil { 25 | http.Error(w, err.Error(), http.StatusInternalServerError) 26 | return 27 | } 28 | fmt.Printf("%d\n", len(embeddings.Embeddings[0])) 29 | fmt.Printf("Prompt Tokens: %d\n", embeddings.Usage.PromptTokenCount) 30 | 31 | }) 32 | } 33 | 34 | func main() {} 35 | -------------------------------------------------------------------------------- /examples/http-outbound/spin.toml: -------------------------------------------------------------------------------- 1 | spin_manifest_version = 2 2 | 3 | [application] 4 | authors = ["Fermyon Engineering "] 5 | description = "A simple Spin application written in (Tiny)Go that performs outbound HTTP requests." 6 | name = "http-outbound-example" 7 | version = "1.0.0" 8 | 9 | [[trigger.http]] 10 | route = "/hello" 11 | component = "hello" 12 | 13 | [[trigger.http]] 14 | route = "/http-to-same-app" 15 | component = "http-to-same-app" 16 | 17 | [component.hello] 18 | source = "hello/main.wasm" 19 | allowed_outbound_hosts = [ 20 | "https://random-data-api.fermyon.app:443", 21 | "https://postman-echo.com:443", 22 | ] 23 | [component.hello.build] 24 | workdir = "hello" 25 | command = "tinygo build -target=wasip1 -gc=leaking -buildmode=c-shared -no-debug -o main.wasm ." 26 | 27 | [component.http-to-same-app] 28 | source = "http-to-same-app/main.wasm" 29 | # Use self to make outbound requests to components in the same Spin application. 30 | allowed_outbound_hosts = ["http://self"] 31 | [component.http-to-same-app.build] 32 | workdir = "http-to-same-app" 33 | command = "tinygo build -target=wasip1 -gc=leaking -buildmode=c-shared -no-debug -o main.wasm ." 34 | -------------------------------------------------------------------------------- /wit/rdbms-types.wit: -------------------------------------------------------------------------------- 1 | enum db-data-type { 2 | boolean, 3 | int8, 4 | int16, 5 | int32, 6 | int64, 7 | uint8, 8 | uint16, 9 | uint32, 10 | uint64, 11 | floating32, 12 | floating64, 13 | str, 14 | binary, 15 | other, 16 | } 17 | 18 | variant db-value { 19 | boolean(bool), 20 | int8(s8), 21 | int16(s16), 22 | int32(s32), 23 | int64(s64), 24 | uint8(u8), 25 | uint16(u16), 26 | uint32(u32), 27 | uint64(u64), 28 | floating32(float32), 29 | floating64(float64), 30 | str(string), 31 | binary(list), 32 | db-null, 33 | unsupported, 34 | } 35 | 36 | variant parameter-value { 37 | boolean(bool), 38 | int8(s8), 39 | int16(s16), 40 | int32(s32), 41 | int64(s64), 42 | uint8(u8), 43 | uint16(u16), 44 | uint32(u32), 45 | uint64(u64), 46 | floating32(float32), 47 | floating64(float64), 48 | str(string), 49 | binary(list), 50 | db-null, 51 | } 52 | 53 | record column { 54 | name: string, 55 | data-type: db-data-type, 56 | } 57 | 58 | type row = list 59 | 60 | record row-set { 61 | columns: list, 62 | rows: list, 63 | } 64 | -------------------------------------------------------------------------------- /examples/sqlite/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | 8 | spinhttp "github.com/spinframework/spin-go-sdk/v2/http" 9 | "github.com/spinframework/spin-go-sdk/v2/sqlite" 10 | ) 11 | 12 | type Pet struct { 13 | ID int64 14 | Name string 15 | Prey *string // nullable field must be a pointer 16 | IsFinicky bool 17 | } 18 | 19 | func init() { 20 | spinhttp.Handle(func(w http.ResponseWriter, r *http.Request) { 21 | db := sqlite.Open("default") 22 | defer db.Close() 23 | 24 | _, err := db.Query("REPLACE INTO pets VALUES (4, 'Maya', ?, false);", "bananas") 25 | if err != nil { 26 | http.Error(w, err.Error(), http.StatusInternalServerError) 27 | return 28 | } 29 | 30 | rows, err := db.Query("SELECT * FROM pets") 31 | if err != nil { 32 | http.Error(w, err.Error(), http.StatusInternalServerError) 33 | return 34 | } 35 | 36 | var pets []*Pet 37 | for rows.Next() { 38 | var pet Pet 39 | if err := rows.Scan(&pet.ID, &pet.Name, &pet.Prey, &pet.IsFinicky); err != nil { 40 | fmt.Println(err) 41 | } 42 | pets = append(pets, &pet) 43 | } 44 | json.NewEncoder(w).Encode(pets) 45 | }) 46 | } 47 | 48 | func main() {} 49 | -------------------------------------------------------------------------------- /variables/internals.go: -------------------------------------------------------------------------------- 1 | package variables 2 | 3 | // #cgo CFLAGS: -Wno-unused-parameter -Wno-switch-bool 4 | // #include 5 | // #include 6 | import "C" 7 | import ( 8 | "errors" 9 | "unsafe" 10 | ) 11 | 12 | func get(key string) (string, error) { 13 | var spinResponse C.spin_config_expected_string_error_t 14 | 15 | spinKey := C.spin_config_string_t{ptr: C.CString(key), len: C.size_t(len(key))} 16 | defer func() { 17 | C.spin_config_expected_string_error_free(&spinResponse) 18 | C.spin_config_string_free(&spinKey) 19 | }() 20 | 21 | C.spin_config_get_config(&spinKey, &spinResponse) 22 | 23 | if spinResponse.is_err { // error response from spin 24 | spinErr := (*C.spin_config_error_t)(unsafe.Pointer(&spinResponse.val)) 25 | return "", toError(spinErr) 26 | } 27 | 28 | ok := (*spinString)(unsafe.Pointer(&spinResponse.val)) 29 | return ok.String(), nil 30 | } 31 | 32 | func toError(err *C.spin_config_error_t) error { 33 | spinErr := (*spinString)(unsafe.Pointer(&err.val)) 34 | return errors.New(spinErr.String()) 35 | } 36 | 37 | type spinString C.spin_config_string_t 38 | 39 | // String returns the spinString as a go string. 40 | func (ss spinString) String() string { 41 | return C.GoStringN(ss.ptr, C.int(ss.len)) 42 | } 43 | -------------------------------------------------------------------------------- /examples/mysql-outbound/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | "os" 8 | 9 | spinhttp "github.com/spinframework/spin-go-sdk/v2/http" 10 | "github.com/spinframework/spin-go-sdk/v2/mysql" 11 | ) 12 | 13 | type Pet struct { 14 | ID int64 15 | Name string 16 | Prey *string // nullable field must be a pointer 17 | IsFinicky bool 18 | } 19 | 20 | func init() { 21 | spinhttp.Handle(func(w http.ResponseWriter, r *http.Request) { 22 | 23 | // addr is the environment variable set in `spin.toml` that points to the 24 | // address of the Mysql server. 25 | addr := os.Getenv("DB_URL") 26 | 27 | db := mysql.Open(addr) 28 | defer db.Close() 29 | 30 | _, err := db.Query("REPLACE INTO pets VALUES (?, 'Maya', ?, ?);", 4, "bananas", true) 31 | if err != nil { 32 | http.Error(w, err.Error(), http.StatusInternalServerError) 33 | return 34 | } 35 | 36 | rows, err := db.Query("SELECT * FROM pets") 37 | if err != nil { 38 | http.Error(w, err.Error(), http.StatusInternalServerError) 39 | return 40 | } 41 | 42 | var pets []*Pet 43 | for rows.Next() { 44 | var pet Pet 45 | if err := rows.Scan(&pet.ID, &pet.Name, &pet.Prey, &pet.IsFinicky); err != nil { 46 | fmt.Println(err) 47 | } 48 | pets = append(pets, &pet) 49 | } 50 | json.NewEncoder(w).Encode(pets) 51 | }) 52 | } 53 | 54 | func main() {} 55 | -------------------------------------------------------------------------------- /examples/pg-outbound/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | "os" 8 | 9 | spinhttp "github.com/spinframework/spin-go-sdk/v2/http" 10 | "github.com/spinframework/spin-go-sdk/v2/pg" 11 | ) 12 | 13 | type Pet struct { 14 | ID int64 15 | Name string 16 | Prey *string // nullable field must be a pointer 17 | IsFinicky bool 18 | } 19 | 20 | func init() { 21 | spinhttp.Handle(func(w http.ResponseWriter, r *http.Request) { 22 | 23 | // addr is the environment variable set in `spin.toml` that points to the 24 | // address of the Mysql server. 25 | addr := os.Getenv("DB_URL") 26 | 27 | db := pg.Open(addr) 28 | defer db.Close() 29 | 30 | _, err := db.Query("INSERT INTO pets VALUES ($1, 'Maya', $2, $3);", int32(4), "bananas", true) 31 | if err != nil { 32 | http.Error(w, err.Error(), http.StatusInternalServerError) 33 | return 34 | } 35 | 36 | rows, err := db.Query("SELECT * FROM pets") 37 | if err != nil { 38 | http.Error(w, err.Error(), http.StatusInternalServerError) 39 | return 40 | } 41 | 42 | var pets []*Pet 43 | for rows.Next() { 44 | var pet Pet 45 | if err := rows.Scan(&pet.ID, &pet.Name, &pet.Prey, &pet.IsFinicky); err != nil { 46 | fmt.Println(err) 47 | } 48 | pets = append(pets, &pet) 49 | } 50 | json.NewEncoder(w).Encode(pets) 51 | }) 52 | } 53 | 54 | func main() {} 55 | -------------------------------------------------------------------------------- /variables/spin-config.h: -------------------------------------------------------------------------------- 1 | #ifndef __BINDINGS_SPIN_CONFIG_H 2 | #define __BINDINGS_SPIN_CONFIG_H 3 | #ifdef __cplusplus 4 | extern "C" 5 | { 6 | #endif 7 | 8 | #include 9 | #include 10 | 11 | typedef struct { 12 | char *ptr; 13 | size_t len; 14 | } spin_config_string_t; 15 | 16 | void spin_config_string_set(spin_config_string_t *ret, const char *s); 17 | void spin_config_string_dup(spin_config_string_t *ret, const char *s); 18 | void spin_config_string_free(spin_config_string_t *ret); 19 | typedef struct { 20 | uint8_t tag; 21 | union { 22 | spin_config_string_t provider; 23 | spin_config_string_t invalid_key; 24 | spin_config_string_t invalid_schema; 25 | spin_config_string_t other; 26 | } val; 27 | } spin_config_error_t; 28 | #define SPIN_CONFIG_ERROR_PROVIDER 0 29 | #define SPIN_CONFIG_ERROR_INVALID_KEY 1 30 | #define SPIN_CONFIG_ERROR_INVALID_SCHEMA 2 31 | #define SPIN_CONFIG_ERROR_OTHER 3 32 | void spin_config_error_free(spin_config_error_t *ptr); 33 | typedef struct { 34 | bool is_err; 35 | union { 36 | spin_config_string_t ok; 37 | spin_config_error_t err; 38 | } val; 39 | } spin_config_expected_string_error_t; 40 | void spin_config_expected_string_error_free(spin_config_expected_string_error_t *ptr); 41 | void spin_config_get_config(spin_config_string_t *key, spin_config_expected_string_error_t *ret0); 42 | #ifdef __cplusplus 43 | } 44 | #endif 45 | #endif 46 | -------------------------------------------------------------------------------- /examples/http-outbound/hello/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "net/http" 7 | "os" 8 | 9 | spinhttp "github.com/spinframework/spin-go-sdk/v2/http" 10 | ) 11 | 12 | func init() { 13 | spinhttp.Handle(func(w http.ResponseWriter, r *http.Request) { 14 | r1, err := spinhttp.Get("https://random-data-api.fermyon.app/animals/json") 15 | if err != nil { 16 | http.Error(w, err.Error(), http.StatusInternalServerError) 17 | return 18 | } 19 | 20 | fmt.Fprintln(w, r1.Body) 21 | fmt.Fprintln(w, r1.Header.Get("content-type")) 22 | 23 | r2, err := spinhttp.Post("https://postman-echo.com/post", "text/plain", r.Body) 24 | if err != nil { 25 | http.Error(w, err.Error(), http.StatusInternalServerError) 26 | return 27 | } 28 | fmt.Fprintln(w, r2.Body) 29 | 30 | req, err := http.NewRequest("PUT", "https://postman-echo.com/put", bytes.NewBufferString("General Kenobi!")) 31 | if err != nil { 32 | http.Error(w, err.Error(), http.StatusInternalServerError) 33 | return 34 | } 35 | req.Header.Add("foo", "bar") 36 | r3, err := spinhttp.Send(req) 37 | if err != nil { 38 | http.Error(w, err.Error(), http.StatusInternalServerError) 39 | return 40 | } 41 | 42 | fmt.Fprintln(w, r3.Body) 43 | 44 | // `spin.toml` is not configured to allow outbound HTTP requests to this host, 45 | // so this request will fail. 46 | if _, err := spinhttp.Get("https://fermyon.com"); err != nil { 47 | fmt.Fprintf(os.Stderr, "Cannot send HTTP request: %v", err) 48 | } 49 | }) 50 | } 51 | 52 | func main() {} 53 | -------------------------------------------------------------------------------- /wit/sqlite.wit: -------------------------------------------------------------------------------- 1 | // A handle to an open sqlite instance 2 | type connection = u32 3 | 4 | // The set of errors which may be raised by functions in this interface 5 | variant error { 6 | // The host does not recognize the database name requested. 7 | no-such-database, 8 | // The requesting component does not have access to the specified database (which may or may not exist). 9 | access-denied, 10 | // The provided connection is not valid 11 | invalid-connection, 12 | // The database has reached its capacity 13 | database-full, 14 | // Some implementation-specific error has occurred (e.g. I/O) 15 | io(string) 16 | } 17 | 18 | // Open a connection to a named database instance. 19 | // 20 | // If `database` is "default", the default instance is opened. 21 | // 22 | // `error::no-such-database` will be raised if the `name` is not recognized. 23 | open: func(name: string) -> expected 24 | 25 | // Execute a statement 26 | execute: func(conn: connection, statement: string, parameters: list) -> expected 27 | 28 | // Close the specified `connection`. 29 | close: func(conn: connection) 30 | 31 | // A result of a query 32 | record query-result { 33 | // The names of the columns retrieved in the query 34 | columns: list, 35 | // the row results each containing the values for all the columns for a given row 36 | rows: list, 37 | } 38 | 39 | // A set of values for each of the columns in a query-result 40 | record row-result { 41 | values: list 42 | } 43 | 44 | variant value { 45 | integer(s64), 46 | real(float64), 47 | text(string), 48 | blob(list), 49 | null 50 | } 51 | -------------------------------------------------------------------------------- /wit/http-types.wit: -------------------------------------------------------------------------------- 1 | // This is a temporary workaround very similar to https://github.com/deislabs/wasi-experimental-http. 2 | // Once asynchronous functions, streams, and the upstream HTTP API are available, this should be removed. 3 | 4 | // The HTTP status code. 5 | // This is currently an unsigned 16-bit integer, 6 | // but it could be represented as an enum containing 7 | // all possible HTTP status codes. 8 | type http-status = u16 9 | 10 | // The HTTP body. 11 | // Currently, this is a synchonous byte array, but it should be 12 | // possible to have a stream for both request and response bodies. 13 | type body = list 14 | 15 | // The HTTP headers represented as a list of (name, value) pairs. 16 | type headers = list> 17 | 18 | // The HTTP parameter queries, represented as a list of (name, value) pairs. 19 | type params = list> 20 | 21 | // The HTTP URI of the current request. 22 | type uri = string 23 | 24 | // The HTTP method. 25 | enum method { 26 | get, 27 | post, 28 | put, 29 | delete, 30 | patch, 31 | head, 32 | options, 33 | } 34 | 35 | // An HTTP request. 36 | record request { 37 | method: method, 38 | uri: uri, 39 | headers: headers, 40 | params: params, 41 | body: option, 42 | } 43 | 44 | // An HTTP response. 45 | record response { 46 | status: http-status, 47 | headers: option, 48 | body: option, 49 | } 50 | 51 | // HTTP errors returned by the runtime. 52 | enum http-error { 53 | success, 54 | destination-not-allowed, 55 | invalid-url, 56 | request-error, 57 | runtime-error, 58 | too-many-requests, 59 | } 60 | -------------------------------------------------------------------------------- /wit/outbound-redis.wit: -------------------------------------------------------------------------------- 1 | use * from redis-types 2 | 3 | // Publish a Redis message to the specificed channel and return an error, if any. 4 | publish: func(address: string, channel: string, payload: payload) -> expected 5 | 6 | // Get the value of a key. 7 | get: func(address: string, key: string) -> expected 8 | 9 | // Set key to value. If key alreads holds a value, it is overwritten. 10 | set: func(address: string, key: string, value: payload) -> expected 11 | 12 | // Increments the number stored at key by one. If the key does not exist, it is set to 0 before performing the operation. 13 | // 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. 14 | incr: func(address: string, key: string) -> expected 15 | 16 | // Removes the specified keys. A key is ignored if it does not exist. 17 | del: func(address: string, keys: list) -> expected 18 | 19 | // Add the specified `values` to the set named `key`, returning the number of newly-added values. 20 | sadd: func(address: string, key: string, values: list) -> expected 21 | 22 | // Retrieve the contents of the set named `key`. 23 | smembers: func(address: string, key: string) -> expected, error> 24 | 25 | // Remove the specified `values` from the set named `key`, returning the number of newly-removed values. 26 | srem: func(address: string, key: string, values: list) -> expected 27 | 28 | // Execute an arbitrary Redis command and receive the result. 29 | execute: func(address: string, command: string, arguments: list) -> expected, error> 30 | -------------------------------------------------------------------------------- /redis/spin-redis.h: -------------------------------------------------------------------------------- 1 | #ifndef __BINDINGS_SPIN_REDIS_H 2 | #define __BINDINGS_SPIN_REDIS_H 3 | #ifdef __cplusplus 4 | extern "C" 5 | { 6 | #endif 7 | 8 | #include 9 | #include 10 | 11 | typedef struct { 12 | char *ptr; 13 | size_t len; 14 | } spin_redis_string_t; 15 | 16 | void spin_redis_string_set(spin_redis_string_t *ret, const char *s); 17 | void spin_redis_string_dup(spin_redis_string_t *ret, const char *s); 18 | void spin_redis_string_free(spin_redis_string_t *ret); 19 | typedef uint8_t spin_redis_error_t; 20 | #define SPIN_REDIS_ERROR_SUCCESS 0 21 | #define SPIN_REDIS_ERROR_ERROR 1 22 | typedef struct { 23 | uint8_t *ptr; 24 | size_t len; 25 | } spin_redis_payload_t; 26 | void spin_redis_payload_free(spin_redis_payload_t *ptr); 27 | typedef struct { 28 | uint8_t tag; 29 | union { 30 | int64_t int64; 31 | spin_redis_payload_t binary; 32 | } val; 33 | } spin_redis_redis_parameter_t; 34 | #define SPIN_REDIS_REDIS_PARAMETER_INT64 0 35 | #define SPIN_REDIS_REDIS_PARAMETER_BINARY 1 36 | void spin_redis_redis_parameter_free(spin_redis_redis_parameter_t *ptr); 37 | typedef struct { 38 | uint8_t tag; 39 | union { 40 | spin_redis_string_t status; 41 | int64_t int64; 42 | spin_redis_payload_t binary; 43 | } val; 44 | } spin_redis_redis_result_t; 45 | #define SPIN_REDIS_REDIS_RESULT_NIL 0 46 | #define SPIN_REDIS_REDIS_RESULT_STATUS 1 47 | #define SPIN_REDIS_REDIS_RESULT_INT64 2 48 | #define SPIN_REDIS_REDIS_RESULT_BINARY 3 49 | void spin_redis_redis_result_free(spin_redis_redis_result_t *ptr); 50 | spin_redis_error_t spin_redis_handle_redis_message(spin_redis_payload_t *message); 51 | #ifdef __cplusplus 52 | } 53 | #endif 54 | #endif 55 | -------------------------------------------------------------------------------- /examples/key-value/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io" 5 | "net/http" 6 | 7 | spin_http "github.com/spinframework/spin-go-sdk/v2/http" 8 | "github.com/spinframework/spin-go-sdk/v2/kv" 9 | ) 10 | 11 | func init() { 12 | // handler for the http trigger 13 | spin_http.Handle(func(w http.ResponseWriter, r *http.Request) { 14 | store, err := kv.OpenStore("default") 15 | if err != nil { 16 | http.Error(w, err.Error(), http.StatusInternalServerError) 17 | return 18 | } 19 | defer store.Close() 20 | 21 | body, err := io.ReadAll(r.Body) 22 | if err != nil { 23 | http.Error(w, err.Error(), http.StatusInternalServerError) 24 | return 25 | } 26 | 27 | switch r.Method { 28 | case http.MethodPost: 29 | err := store.Set(r.URL.Path, body) 30 | if err != nil { 31 | http.Error(w, err.Error(), http.StatusInternalServerError) 32 | return 33 | } 34 | 35 | w.WriteHeader(http.StatusOK) 36 | case http.MethodGet: 37 | value, err := store.Get(r.URL.Path) 38 | if err != nil { 39 | http.Error(w, err.Error(), http.StatusInternalServerError) 40 | return 41 | } 42 | 43 | w.WriteHeader(http.StatusOK) 44 | w.Write(value) 45 | case http.MethodDelete: 46 | if err := store.Delete(r.URL.Path); err != nil { 47 | http.Error(w, err.Error(), http.StatusInternalServerError) 48 | return 49 | } 50 | 51 | w.WriteHeader(http.StatusOK) 52 | case http.MethodHead: 53 | exists, err := store.Exists(r.URL.Path) 54 | if err != nil { 55 | http.Error(w, err.Error(), http.StatusInternalServerError) 56 | return 57 | } 58 | 59 | if exists { 60 | w.WriteHeader(http.StatusOK) 61 | return 62 | } 63 | 64 | w.WriteHeader(http.StatusNotFound) 65 | default: 66 | http.Error(w, "method not allowed", http.StatusMethodNotAllowed) 67 | } 68 | }) 69 | } 70 | 71 | func main() {} 72 | -------------------------------------------------------------------------------- /llm/llm.go: -------------------------------------------------------------------------------- 1 | // Package llm provides the interface to use Large Language Models in Spin. 2 | package llm 3 | 4 | // InferenceParams is the optional request parameters. 5 | type InferencingParams struct { 6 | // MaxTokens is the maximum tokens that should be inferred. 7 | // Default: 100 8 | // 9 | // Note: the backing implementation may return less tokens. 10 | MaxTokens int32 11 | // RepeatPenalty is the amount the model should avoid repeating tokens. 12 | // Default: 1.1 13 | RepeatPenalty float32 14 | // RepeatPenaltyLastNTokenCount the number of tokens the model should 15 | // apply the repeat penalty to. 16 | // Default: 64 17 | RepeatPenaltyLastNTokenCount int32 18 | // Temperature is the randomness with which the next token is selected. 19 | // Default: 0.8 20 | Temperature float32 21 | // TopK is the number of possible next tokens the model will choose from. 22 | // Default: 40 23 | TopK int32 24 | // TopP is the probability total of next tokens the model will choose 25 | // from. 26 | // Default: 0.9 27 | TopP float32 28 | } 29 | 30 | // InferencingResult is the result of an inference. 31 | type InferencingResult struct { 32 | // Text is the text generated by the model. 33 | Text string 34 | // Usage is information about the inferencing request. 35 | Usage *InferencingUsage 36 | } 37 | 38 | // InferencingUsage represents information related to the inferencing result. 39 | type InferencingUsage struct { 40 | // PromptTokenCount is the number of tokens in the prompt. 41 | PromptTokenCount int 42 | // GeneratedTokenCount is the number of tokens generated by the 43 | // inferencing operation. 44 | GeneratedTokenCount int 45 | } 46 | 47 | // Infer performs inferencing using the provided model and prompt with the 48 | // given optional parameters. 49 | func Infer(model, prompt string, params *InferencingParams) (*InferencingResult, error) { 50 | return infer(model, prompt, params) 51 | } 52 | 53 | // EmbeddingsResult of generating embeddings. 54 | type EmbeddingsResult struct { 55 | // Embeddings are the embeddings generated by the request. 56 | Embeddings [][]float32 57 | // Usage is usage related to an embeddings generation request. 58 | Usage *EmbeddingsUsage 59 | } 60 | 61 | // Embeddings is usage related to an embeddings generation request. 62 | type EmbeddingsUsage struct { 63 | // PromptTokenCount is number of tokens in the prompt. 64 | PromptTokenCount int 65 | } 66 | 67 | // GenerateEmbeddings generates the embeddings for the supplied list of text. 68 | func GenerateEmbeddings(model string, text []string) (*EmbeddingsResult, error) { 69 | return generateEmbeddings(model, text) 70 | } 71 | -------------------------------------------------------------------------------- /redis/spin-redis.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | __attribute__((weak, export_name("canonical_abi_realloc"))) 5 | void *canonical_abi_realloc( 6 | void *ptr, 7 | size_t orig_size, 8 | size_t align, 9 | size_t new_size 10 | ) { 11 | if (new_size == 0) 12 | return (void*) align; 13 | void *ret = realloc(ptr, new_size); 14 | if (!ret) 15 | abort(); 16 | return ret; 17 | } 18 | 19 | __attribute__((weak, export_name("canonical_abi_free"))) 20 | void canonical_abi_free( 21 | void *ptr, 22 | size_t size, 23 | size_t align 24 | ) { 25 | if (size == 0) 26 | return; 27 | free(ptr); 28 | } 29 | #include 30 | 31 | void spin_redis_string_set(spin_redis_string_t *ret, const char *s) { 32 | ret->ptr = (char*) s; 33 | ret->len = strlen(s); 34 | } 35 | 36 | void spin_redis_string_dup(spin_redis_string_t *ret, const char *s) { 37 | ret->len = strlen(s); 38 | ret->ptr = canonical_abi_realloc(NULL, 0, 1, ret->len); 39 | memcpy(ret->ptr, s, ret->len); 40 | } 41 | 42 | void spin_redis_string_free(spin_redis_string_t *ret) { 43 | canonical_abi_free(ret->ptr, ret->len, 1); 44 | ret->ptr = NULL; 45 | ret->len = 0; 46 | } 47 | void spin_redis_payload_free(spin_redis_payload_t *ptr) { 48 | canonical_abi_free(ptr->ptr, ptr->len * 1, 1); 49 | } 50 | void spin_redis_redis_parameter_free(spin_redis_redis_parameter_t *ptr) { 51 | switch ((int32_t) ptr->tag) { 52 | case 1: { 53 | spin_redis_payload_free(&ptr->val.binary); 54 | break; 55 | } 56 | } 57 | } 58 | void spin_redis_redis_result_free(spin_redis_redis_result_t *ptr) { 59 | switch ((int32_t) ptr->tag) { 60 | case 1: { 61 | spin_redis_string_free(&ptr->val.status); 62 | break; 63 | } 64 | case 3: { 65 | spin_redis_payload_free(&ptr->val.binary); 66 | break; 67 | } 68 | } 69 | } 70 | typedef struct { 71 | bool is_err; 72 | union { 73 | spin_redis_error_t err; 74 | } val; 75 | } spin_redis_expected_unit_error_t; 76 | 77 | __attribute__((aligned(1))) 78 | static uint8_t RET_AREA[2]; 79 | __attribute__((export_name("handle-redis-message"))) 80 | int32_t __wasm_export_spin_redis_handle_redis_message(int32_t arg, int32_t arg0) { 81 | spin_redis_payload_t arg1 = (spin_redis_payload_t) { (uint8_t*)(arg), (size_t)(arg0) }; 82 | spin_redis_error_t ret = spin_redis_handle_redis_message(&arg1); 83 | 84 | spin_redis_expected_unit_error_t ret2; 85 | if (ret <= 2) { 86 | ret2.is_err = true; 87 | ret2.val.err = ret; 88 | } else { 89 | ret2.is_err = false; 90 | 91 | } 92 | int32_t ptr = (int32_t) &RET_AREA; 93 | 94 | if ((ret2).is_err) { 95 | const spin_redis_error_t *payload3 = &(ret2).val.err; 96 | *((int8_t*)(ptr + 0)) = 1; 97 | *((int8_t*)(ptr + 1)) = (int32_t) *payload3; 98 | 99 | } else { 100 | 101 | *((int8_t*)(ptr + 0)) = 0; 102 | 103 | } 104 | return ptr; 105 | } 106 | -------------------------------------------------------------------------------- /http/spin-http.h: -------------------------------------------------------------------------------- 1 | #ifndef __BINDINGS_SPIN_HTTP_H 2 | #define __BINDINGS_SPIN_HTTP_H 3 | #ifdef __cplusplus 4 | extern "C" 5 | { 6 | #endif 7 | 8 | #include 9 | #include 10 | 11 | typedef struct { 12 | char *ptr; 13 | size_t len; 14 | } spin_http_string_t; 15 | 16 | void spin_http_string_set(spin_http_string_t *ret, const char *s); 17 | void spin_http_string_dup(spin_http_string_t *ret, const char *s); 18 | void spin_http_string_free(spin_http_string_t *ret); 19 | typedef struct { 20 | uint8_t *ptr; 21 | size_t len; 22 | } spin_http_body_t; 23 | void spin_http_body_free(spin_http_body_t *ptr); 24 | typedef struct { 25 | spin_http_string_t f0; 26 | spin_http_string_t f1; 27 | } spin_http_tuple2_string_string_t; 28 | void spin_http_tuple2_string_string_free(spin_http_tuple2_string_string_t *ptr); 29 | typedef struct { 30 | spin_http_tuple2_string_string_t *ptr; 31 | size_t len; 32 | } spin_http_headers_t; 33 | void spin_http_headers_free(spin_http_headers_t *ptr); 34 | typedef uint8_t spin_http_http_error_t; 35 | #define SPIN_HTTP_HTTP_ERROR_SUCCESS 0 36 | #define SPIN_HTTP_HTTP_ERROR_DESTINATION_NOT_ALLOWED 1 37 | #define SPIN_HTTP_HTTP_ERROR_INVALID_URL 2 38 | #define SPIN_HTTP_HTTP_ERROR_REQUEST_ERROR 3 39 | #define SPIN_HTTP_HTTP_ERROR_RUNTIME_ERROR 4 40 | #define SPIN_HTTP_HTTP_ERROR_TOO_MANY_REQUESTS 5 41 | typedef uint16_t spin_http_http_status_t; 42 | typedef uint8_t spin_http_method_t; 43 | #define SPIN_HTTP_METHOD_GET 0 44 | #define SPIN_HTTP_METHOD_POST 1 45 | #define SPIN_HTTP_METHOD_PUT 2 46 | #define SPIN_HTTP_METHOD_DELETE 3 47 | #define SPIN_HTTP_METHOD_PATCH 4 48 | #define SPIN_HTTP_METHOD_HEAD 5 49 | #define SPIN_HTTP_METHOD_OPTIONS 6 50 | typedef struct { 51 | spin_http_tuple2_string_string_t *ptr; 52 | size_t len; 53 | } spin_http_params_t; 54 | void spin_http_params_free(spin_http_params_t *ptr); 55 | typedef spin_http_string_t spin_http_uri_t; 56 | void spin_http_uri_free(spin_http_uri_t *ptr); 57 | typedef struct { 58 | bool is_some; 59 | spin_http_body_t val; 60 | } spin_http_option_body_t; 61 | void spin_http_option_body_free(spin_http_option_body_t *ptr); 62 | typedef struct { 63 | spin_http_method_t method; 64 | spin_http_uri_t uri; 65 | spin_http_headers_t headers; 66 | spin_http_params_t params; 67 | spin_http_option_body_t body; 68 | } spin_http_request_t; 69 | void spin_http_request_free(spin_http_request_t *ptr); 70 | typedef struct { 71 | bool is_some; 72 | spin_http_headers_t val; 73 | } spin_http_option_headers_t; 74 | void spin_http_option_headers_free(spin_http_option_headers_t *ptr); 75 | typedef struct { 76 | spin_http_http_status_t status; 77 | spin_http_option_headers_t headers; 78 | spin_http_option_body_t body; 79 | } spin_http_response_t; 80 | void spin_http_response_free(spin_http_response_t *ptr); 81 | void spin_http_handle_http_request(spin_http_request_t *req, spin_http_response_t *ret0); 82 | #ifdef __cplusplus 83 | } 84 | #endif 85 | #endif 86 | -------------------------------------------------------------------------------- /wit/key-value.wit: -------------------------------------------------------------------------------- 1 | // A handle to an open key-value store 2 | type store = u32 3 | 4 | // The set of errors which may be raised by functions in this interface 5 | variant error { 6 | // Too many stores have been opened simultaneously. Closing one or more 7 | // stores prior to retrying may address this. 8 | store-table-full, 9 | 10 | // The host does not recognize the store name requested. Defining and 11 | // configuring a store with that name in a runtime configuration file 12 | // may address this. 13 | no-such-store, 14 | 15 | // The requesting component does not have access to the specified store 16 | // (which may or may not exist). 17 | access-denied, 18 | 19 | // The store handle provided is not recognized, i.e. it was either never 20 | // opened or has been closed. 21 | invalid-store, 22 | 23 | // No key-value tuple exists for the specified key in the specified 24 | // store. 25 | no-such-key, 26 | 27 | // Some implementation-specific error has occurred (e.g. I/O) 28 | io(string) 29 | } 30 | 31 | // Open the store with the specified name. 32 | // 33 | // If `name` is "default", the default store is opened. Otherwise, 34 | // `name` must refer to a store defined and configured in a runtime 35 | // configuration file supplied with the application. 36 | // 37 | // `error::no-such-store` will be raised if the `name` is not recognized. 38 | open: func(name: string) -> expected 39 | 40 | // Get the value associated with the specified `key` from the specified 41 | // `store`. 42 | // 43 | // `error::invalid-store` will be raised if `store` is not a valid handle 44 | // to an open store, and `error::no-such-key` will be raised if there is no 45 | // tuple for `key` in `store`. 46 | get: func(store: store, key: string) -> expected, error> 47 | 48 | // Set the `value` associated with the specified `key` in the specified 49 | // `store`, overwriting any existing value. 50 | // 51 | // `error::invalid-store` will be raised if `store` is not a valid handle 52 | // to an open store. 53 | set: func(store: store, key: string, value: list) -> expected 54 | 55 | // Delete the tuple with the specified `key` from the specified `store`. 56 | // 57 | // `error::invalid-store` will be raised if `store` is not a valid handle 58 | // to an open store. No error is raised if a tuple did not previously 59 | // exist for `key`. 60 | delete: func(store: store, key: string) -> expected 61 | 62 | // Return whether a tuple exists for the specified `key` in the specified 63 | // `store`. 64 | // 65 | // `error::invalid-store` will be raised if `store` is not a valid handle 66 | // to an open store. 67 | exists: func(store: store, key: string) -> expected 68 | 69 | // Return a list of all the keys in the specified `store`. 70 | // 71 | // `error::invalid-store` will be raised if `store` is not a valid handle 72 | // to an open store. 73 | get-keys: func(store: store) -> expected, error> 74 | 75 | // Close the specified `store`. 76 | // 77 | // This has no effect if `store` is not a valid handle to an open store. 78 | close: func(store: store) 79 | -------------------------------------------------------------------------------- /examples/http-outbound/README.md: -------------------------------------------------------------------------------- 1 | # Making outbound HTTP requests from TinyGo Spin components 2 | 3 | The TinyGo SDK for building Spin components allows us to granularly allow 4 | components to send HTTP requests to certain hosts. This is configured in 5 | `spin.toml`. 6 | 7 | > For more information and examples for using TinyGo with WebAssembly, check 8 | > [the official TinyGo documentation](https://tinygo.org/docs/guides/webassembly/) 9 | > and 10 | > [the Wasm examples](https://github.com/tinygo-org/tinygo/tree/release/src/examples/wasm). 11 | 12 | Creating and sending HTTP requests from Spin components closely follows the Go 13 | `net/http` API. See [tinygo-hello/main.go](./tinygo-hello/main.go). 14 | 15 | Building this as a WebAssembly module can be done using the `tinygo` compiler: 16 | 17 | ```shell 18 | $ spin build 19 | Building component outbound-http-to-same-app with `tinygo build -target=wasip1 -gc=leaking -buildmode=c-shared -no-debug -o main.wasm .` 20 | Working directory: "./outbound-http-to-same-app" 21 | Building component tinygo-hello with `tinygo build -target=wasip1 -gc=leaking -buildmode=c-shared -no-debug -o main.wasm .` 22 | Working directory: "./tinygo-hello" 23 | Finished building all Spin components 24 | ``` 25 | 26 | The component configuration must contain a list of all hosts allowed to send 27 | HTTP requests to, otherwise sending the request results in an error: 28 | 29 | ``` 30 | Cannot send HTTP request: Destination not allowed: 31 | ``` 32 | 33 | The `tinygo-hello` component has the following allowed hosts set: 34 | 35 | ```toml 36 | [component.tinygo-hello] 37 | source = "tinygo-hello/main.wasm" 38 | allowed_outbound_hosts = [ 39 | "https://random-data-api.fermyon.app", 40 | "https://postman-echo.com", 41 | ] 42 | ``` 43 | 44 | And the `outbound-http-to-same-app` uses the dedicated `self` keyword to enable making 45 | a request to another component in this same app, via a relative path (in this case, the component 46 | is `tinygo-hello` at `/hello`): 47 | 48 | ```toml 49 | [component.outbound-http-to-same-app] 50 | source = "outbound-http-to-same-app/main.wasm" 51 | # Use self to make outbound requests to components in the same Spin application. 52 | allowed_outbound_hosts = ["http://self"] 53 | ``` 54 | 55 | At this point, we can execute the application with the `spin` CLI: 56 | 57 | ```shell 58 | $ RUST_LOG=spin=trace,wasi_outbound_http=trace spin up 59 | ``` 60 | 61 | The application can now receive requests on `http://localhost:3000/hello`: 62 | 63 | ```shell 64 | $ curl -i localhost:3000/hello -X POST -d "hello there" 65 | HTTP/1.1 200 OK 66 | content-length: 976 67 | date: Thu, 26 Oct 2023 18:26:17 GMT 68 | 69 | {{"timestamp":1698344776965,"fact":"Reindeer grow new antlers every year"}} 70 | ... 71 | ``` 72 | 73 | As well as via the `/outbound-http-to-same-app` path to verify outbound http to the `tinygo-hello` component: 74 | 75 | ```shell 76 | $ curl -i localhost:3000/outbound-http-to-same-app 77 | HTTP/1.1 200 OK 78 | content-length: 946 79 | date: Thu, 26 Oct 2023 18:26:53 GMT 80 | 81 | {{{"timestamp":1698344813408,"fact":"Some hummingbirds weigh less than a penny"}} 82 | ... 83 | ``` 84 | 85 | ## Notes 86 | 87 | - this only implements sending HTTP/1.1 requests 88 | - requests are currently blocking and synchronous 89 | -------------------------------------------------------------------------------- /sqlite/sqlite.h: -------------------------------------------------------------------------------- 1 | #ifndef __BINDINGS_SQLITE_H 2 | #define __BINDINGS_SQLITE_H 3 | #ifdef __cplusplus 4 | extern "C" 5 | { 6 | #endif 7 | 8 | #include 9 | #include 10 | 11 | typedef struct { 12 | char *ptr; 13 | size_t len; 14 | } sqlite_string_t; 15 | 16 | void sqlite_string_set(sqlite_string_t *ret, const char *s); 17 | void sqlite_string_dup(sqlite_string_t *ret, const char *s); 18 | void sqlite_string_free(sqlite_string_t *ret); 19 | typedef uint32_t sqlite_connection_t; 20 | typedef struct { 21 | uint8_t tag; 22 | union { 23 | sqlite_string_t io; 24 | } val; 25 | } sqlite_error_t; 26 | #define SQLITE_ERROR_NO_SUCH_DATABASE 0 27 | #define SQLITE_ERROR_ACCESS_DENIED 1 28 | #define SQLITE_ERROR_INVALID_CONNECTION 2 29 | #define SQLITE_ERROR_DATABASE_FULL 3 30 | #define SQLITE_ERROR_IO 4 31 | void sqlite_error_free(sqlite_error_t *ptr); 32 | typedef struct { 33 | sqlite_string_t *ptr; 34 | size_t len; 35 | } sqlite_list_string_t; 36 | void sqlite_list_string_free(sqlite_list_string_t *ptr); 37 | typedef struct { 38 | uint8_t *ptr; 39 | size_t len; 40 | } sqlite_list_u8_t; 41 | void sqlite_list_u8_free(sqlite_list_u8_t *ptr); 42 | typedef struct { 43 | uint8_t tag; 44 | union { 45 | int64_t integer; 46 | double real; 47 | sqlite_string_t text; 48 | sqlite_list_u8_t blob; 49 | } val; 50 | } sqlite_value_t; 51 | #define SQLITE_VALUE_INTEGER 0 52 | #define SQLITE_VALUE_REAL 1 53 | #define SQLITE_VALUE_TEXT 2 54 | #define SQLITE_VALUE_BLOB 3 55 | #define SQLITE_VALUE_NULL 4 56 | void sqlite_value_free(sqlite_value_t *ptr); 57 | typedef struct { 58 | sqlite_value_t *ptr; 59 | size_t len; 60 | } sqlite_list_value_t; 61 | void sqlite_list_value_free(sqlite_list_value_t *ptr); 62 | typedef struct { 63 | sqlite_list_value_t values; 64 | } sqlite_row_result_t; 65 | void sqlite_row_result_free(sqlite_row_result_t *ptr); 66 | typedef struct { 67 | sqlite_row_result_t *ptr; 68 | size_t len; 69 | } sqlite_list_row_result_t; 70 | void sqlite_list_row_result_free(sqlite_list_row_result_t *ptr); 71 | typedef struct { 72 | sqlite_list_string_t columns; 73 | sqlite_list_row_result_t rows; 74 | } sqlite_query_result_t; 75 | void sqlite_query_result_free(sqlite_query_result_t *ptr); 76 | typedef struct { 77 | bool is_err; 78 | union { 79 | sqlite_connection_t ok; 80 | sqlite_error_t err; 81 | } val; 82 | } sqlite_expected_connection_error_t; 83 | void sqlite_expected_connection_error_free(sqlite_expected_connection_error_t *ptr); 84 | typedef struct { 85 | bool is_err; 86 | union { 87 | sqlite_query_result_t ok; 88 | sqlite_error_t err; 89 | } val; 90 | } sqlite_expected_query_result_error_t; 91 | void sqlite_expected_query_result_error_free(sqlite_expected_query_result_error_t *ptr); 92 | void sqlite_open(sqlite_string_t *name, sqlite_expected_connection_error_t *ret0); 93 | void sqlite_execute(sqlite_connection_t conn, sqlite_string_t *statement, sqlite_list_value_t *parameters, sqlite_expected_query_result_error_t *ret0); 94 | void sqlite_close(sqlite_connection_t conn); 95 | #ifdef __cplusplus 96 | } 97 | #endif 98 | #endif 99 | -------------------------------------------------------------------------------- /kv/key-value.h: -------------------------------------------------------------------------------- 1 | #ifndef __BINDINGS_KEY_VALUE_H 2 | #define __BINDINGS_KEY_VALUE_H 3 | #ifdef __cplusplus 4 | extern "C" 5 | { 6 | #endif 7 | 8 | #include 9 | #include 10 | 11 | typedef struct { 12 | char *ptr; 13 | size_t len; 14 | } key_value_string_t; 15 | 16 | void key_value_string_set(key_value_string_t *ret, const char *s); 17 | void key_value_string_dup(key_value_string_t *ret, const char *s); 18 | void key_value_string_free(key_value_string_t *ret); 19 | typedef uint32_t key_value_store_t; 20 | typedef struct { 21 | uint8_t tag; 22 | union { 23 | key_value_string_t io; 24 | } val; 25 | } key_value_error_t; 26 | #define KEY_VALUE_ERROR_STORE_TABLE_FULL 0 27 | #define KEY_VALUE_ERROR_NO_SUCH_STORE 1 28 | #define KEY_VALUE_ERROR_ACCESS_DENIED 2 29 | #define KEY_VALUE_ERROR_INVALID_STORE 3 30 | #define KEY_VALUE_ERROR_NO_SUCH_KEY 4 31 | #define KEY_VALUE_ERROR_IO 5 32 | void key_value_error_free(key_value_error_t *ptr); 33 | typedef struct { 34 | bool is_err; 35 | union { 36 | key_value_store_t ok; 37 | key_value_error_t err; 38 | } val; 39 | } key_value_expected_store_error_t; 40 | void key_value_expected_store_error_free(key_value_expected_store_error_t *ptr); 41 | typedef struct { 42 | uint8_t *ptr; 43 | size_t len; 44 | } key_value_list_u8_t; 45 | void key_value_list_u8_free(key_value_list_u8_t *ptr); 46 | typedef struct { 47 | bool is_err; 48 | union { 49 | key_value_list_u8_t ok; 50 | key_value_error_t err; 51 | } val; 52 | } key_value_expected_list_u8_error_t; 53 | void key_value_expected_list_u8_error_free(key_value_expected_list_u8_error_t *ptr); 54 | typedef struct { 55 | bool is_err; 56 | union { 57 | key_value_error_t err; 58 | } val; 59 | } key_value_expected_unit_error_t; 60 | void key_value_expected_unit_error_free(key_value_expected_unit_error_t *ptr); 61 | typedef struct { 62 | bool is_err; 63 | union { 64 | bool ok; 65 | key_value_error_t err; 66 | } val; 67 | } key_value_expected_bool_error_t; 68 | void key_value_expected_bool_error_free(key_value_expected_bool_error_t *ptr); 69 | typedef struct { 70 | key_value_string_t *ptr; 71 | size_t len; 72 | } key_value_list_string_t; 73 | void key_value_list_string_free(key_value_list_string_t *ptr); 74 | typedef struct { 75 | bool is_err; 76 | union { 77 | key_value_list_string_t ok; 78 | key_value_error_t err; 79 | } val; 80 | } key_value_expected_list_string_error_t; 81 | void key_value_expected_list_string_error_free(key_value_expected_list_string_error_t *ptr); 82 | void key_value_open(key_value_string_t *name, key_value_expected_store_error_t *ret0); 83 | void key_value_get(key_value_store_t store, key_value_string_t *key, key_value_expected_list_u8_error_t *ret0); 84 | void key_value_set(key_value_store_t store, key_value_string_t *key, key_value_list_u8_t *value, key_value_expected_unit_error_t *ret0); 85 | void key_value_delete(key_value_store_t store, key_value_string_t *key, key_value_expected_unit_error_t *ret0); 86 | void key_value_exists(key_value_store_t store, key_value_string_t *key, key_value_expected_bool_error_t *ret0); 87 | void key_value_get_keys(key_value_store_t store, key_value_expected_list_string_error_t *ret0); 88 | void key_value_close(key_value_store_t store); 89 | #ifdef __cplusplus 90 | } 91 | #endif 92 | #endif 93 | -------------------------------------------------------------------------------- /http/wasi-outbound-http.h: -------------------------------------------------------------------------------- 1 | #ifndef __BINDINGS_WASI_OUTBOUND_HTTP_H 2 | #define __BINDINGS_WASI_OUTBOUND_HTTP_H 3 | #ifdef __cplusplus 4 | extern "C" 5 | { 6 | #endif 7 | 8 | #include 9 | #include 10 | 11 | typedef struct { 12 | char *ptr; 13 | size_t len; 14 | } wasi_outbound_http_string_t; 15 | 16 | void wasi_outbound_http_string_set(wasi_outbound_http_string_t *ret, const char *s); 17 | void wasi_outbound_http_string_dup(wasi_outbound_http_string_t *ret, const char *s); 18 | void wasi_outbound_http_string_free(wasi_outbound_http_string_t *ret); 19 | typedef struct { 20 | uint8_t *ptr; 21 | size_t len; 22 | } wasi_outbound_http_body_t; 23 | void wasi_outbound_http_body_free(wasi_outbound_http_body_t *ptr); 24 | typedef struct { 25 | wasi_outbound_http_string_t f0; 26 | wasi_outbound_http_string_t f1; 27 | } wasi_outbound_http_tuple2_string_string_t; 28 | void wasi_outbound_http_tuple2_string_string_free(wasi_outbound_http_tuple2_string_string_t *ptr); 29 | typedef struct { 30 | wasi_outbound_http_tuple2_string_string_t *ptr; 31 | size_t len; 32 | } wasi_outbound_http_headers_t; 33 | void wasi_outbound_http_headers_free(wasi_outbound_http_headers_t *ptr); 34 | typedef uint8_t wasi_outbound_http_http_error_t; 35 | #define WASI_OUTBOUND_HTTP_HTTP_ERROR_SUCCESS 0 36 | #define WASI_OUTBOUND_HTTP_HTTP_ERROR_DESTINATION_NOT_ALLOWED 1 37 | #define WASI_OUTBOUND_HTTP_HTTP_ERROR_INVALID_URL 2 38 | #define WASI_OUTBOUND_HTTP_HTTP_ERROR_REQUEST_ERROR 3 39 | #define WASI_OUTBOUND_HTTP_HTTP_ERROR_RUNTIME_ERROR 4 40 | #define WASI_OUTBOUND_HTTP_HTTP_ERROR_TOO_MANY_REQUESTS 5 41 | typedef uint16_t wasi_outbound_http_http_status_t; 42 | typedef uint8_t wasi_outbound_http_method_t; 43 | #define WASI_OUTBOUND_HTTP_METHOD_GET 0 44 | #define WASI_OUTBOUND_HTTP_METHOD_POST 1 45 | #define WASI_OUTBOUND_HTTP_METHOD_PUT 2 46 | #define WASI_OUTBOUND_HTTP_METHOD_DELETE 3 47 | #define WASI_OUTBOUND_HTTP_METHOD_PATCH 4 48 | #define WASI_OUTBOUND_HTTP_METHOD_HEAD 5 49 | #define WASI_OUTBOUND_HTTP_METHOD_OPTIONS 6 50 | typedef struct { 51 | wasi_outbound_http_tuple2_string_string_t *ptr; 52 | size_t len; 53 | } wasi_outbound_http_params_t; 54 | void wasi_outbound_http_params_free(wasi_outbound_http_params_t *ptr); 55 | typedef wasi_outbound_http_string_t wasi_outbound_http_uri_t; 56 | void wasi_outbound_http_uri_free(wasi_outbound_http_uri_t *ptr); 57 | typedef struct { 58 | bool is_some; 59 | wasi_outbound_http_body_t val; 60 | } wasi_outbound_http_option_body_t; 61 | void wasi_outbound_http_option_body_free(wasi_outbound_http_option_body_t *ptr); 62 | typedef struct { 63 | wasi_outbound_http_method_t method; 64 | wasi_outbound_http_uri_t uri; 65 | wasi_outbound_http_headers_t headers; 66 | wasi_outbound_http_params_t params; 67 | wasi_outbound_http_option_body_t body; 68 | } wasi_outbound_http_request_t; 69 | void wasi_outbound_http_request_free(wasi_outbound_http_request_t *ptr); 70 | typedef struct { 71 | bool is_some; 72 | wasi_outbound_http_headers_t val; 73 | } wasi_outbound_http_option_headers_t; 74 | void wasi_outbound_http_option_headers_free(wasi_outbound_http_option_headers_t *ptr); 75 | typedef struct { 76 | wasi_outbound_http_http_status_t status; 77 | wasi_outbound_http_option_headers_t headers; 78 | wasi_outbound_http_option_body_t body; 79 | } wasi_outbound_http_response_t; 80 | void wasi_outbound_http_response_free(wasi_outbound_http_response_t *ptr); 81 | wasi_outbound_http_http_error_t wasi_outbound_http_request(wasi_outbound_http_request_t *req, wasi_outbound_http_response_t *ret0); 82 | #ifdef __cplusplus 83 | } 84 | #endif 85 | #endif 86 | -------------------------------------------------------------------------------- /redis/outbound-redis.h: -------------------------------------------------------------------------------- 1 | #ifndef __BINDINGS_OUTBOUND_REDIS_H 2 | #define __BINDINGS_OUTBOUND_REDIS_H 3 | #ifdef __cplusplus 4 | extern "C" 5 | { 6 | #endif 7 | 8 | #include 9 | #include 10 | 11 | typedef struct { 12 | char *ptr; 13 | size_t len; 14 | } outbound_redis_string_t; 15 | 16 | void outbound_redis_string_set(outbound_redis_string_t *ret, const char *s); 17 | void outbound_redis_string_dup(outbound_redis_string_t *ret, const char *s); 18 | void outbound_redis_string_free(outbound_redis_string_t *ret); 19 | typedef uint8_t outbound_redis_error_t; 20 | #define OUTBOUND_REDIS_ERROR_SUCCESS 0 21 | #define OUTBOUND_REDIS_ERROR_ERROR 1 22 | typedef struct { 23 | uint8_t *ptr; 24 | size_t len; 25 | } outbound_redis_payload_t; 26 | void outbound_redis_payload_free(outbound_redis_payload_t *ptr); 27 | typedef struct { 28 | uint8_t tag; 29 | union { 30 | int64_t int64; 31 | outbound_redis_payload_t binary; 32 | } val; 33 | } outbound_redis_redis_parameter_t; 34 | #define OUTBOUND_REDIS_REDIS_PARAMETER_INT64 0 35 | #define OUTBOUND_REDIS_REDIS_PARAMETER_BINARY 1 36 | void outbound_redis_redis_parameter_free(outbound_redis_redis_parameter_t *ptr); 37 | typedef struct { 38 | uint8_t tag; 39 | union { 40 | outbound_redis_string_t status; 41 | int64_t int64; 42 | outbound_redis_payload_t binary; 43 | } val; 44 | } outbound_redis_redis_result_t; 45 | #define OUTBOUND_REDIS_REDIS_RESULT_NIL 0 46 | #define OUTBOUND_REDIS_REDIS_RESULT_STATUS 1 47 | #define OUTBOUND_REDIS_REDIS_RESULT_INT64 2 48 | #define OUTBOUND_REDIS_REDIS_RESULT_BINARY 3 49 | void outbound_redis_redis_result_free(outbound_redis_redis_result_t *ptr); 50 | typedef struct { 51 | outbound_redis_string_t *ptr; 52 | size_t len; 53 | } outbound_redis_list_string_t; 54 | void outbound_redis_list_string_free(outbound_redis_list_string_t *ptr); 55 | typedef struct { 56 | outbound_redis_redis_parameter_t *ptr; 57 | size_t len; 58 | } outbound_redis_list_redis_parameter_t; 59 | void outbound_redis_list_redis_parameter_free(outbound_redis_list_redis_parameter_t *ptr); 60 | typedef struct { 61 | outbound_redis_redis_result_t *ptr; 62 | size_t len; 63 | } outbound_redis_list_redis_result_t; 64 | void outbound_redis_list_redis_result_free(outbound_redis_list_redis_result_t *ptr); 65 | outbound_redis_error_t outbound_redis_publish(outbound_redis_string_t *address, outbound_redis_string_t *channel, outbound_redis_payload_t *payload); 66 | outbound_redis_error_t outbound_redis_get(outbound_redis_string_t *address, outbound_redis_string_t *key, outbound_redis_payload_t *ret0); 67 | outbound_redis_error_t outbound_redis_set(outbound_redis_string_t *address, outbound_redis_string_t *key, outbound_redis_payload_t *value); 68 | outbound_redis_error_t outbound_redis_incr(outbound_redis_string_t *address, outbound_redis_string_t *key, int64_t *ret0); 69 | outbound_redis_error_t outbound_redis_del(outbound_redis_string_t *address, outbound_redis_list_string_t *keys, int64_t *ret0); 70 | outbound_redis_error_t outbound_redis_sadd(outbound_redis_string_t *address, outbound_redis_string_t *key, outbound_redis_list_string_t *values, int64_t *ret0); 71 | outbound_redis_error_t outbound_redis_smembers(outbound_redis_string_t *address, outbound_redis_string_t *key, outbound_redis_list_string_t *ret0); 72 | outbound_redis_error_t outbound_redis_srem(outbound_redis_string_t *address, outbound_redis_string_t *key, outbound_redis_list_string_t *values, int64_t *ret0); 73 | outbound_redis_error_t outbound_redis_execute(outbound_redis_string_t *address, outbound_redis_string_t *command, outbound_redis_list_redis_parameter_t *arguments, outbound_redis_list_redis_result_t *ret0); 74 | #ifdef __cplusplus 75 | } 76 | #endif 77 | #endif 78 | -------------------------------------------------------------------------------- /variables/spin-config.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | __attribute__((weak, export_name("canonical_abi_realloc"))) 5 | void *canonical_abi_realloc( 6 | void *ptr, 7 | size_t orig_size, 8 | size_t align, 9 | size_t new_size 10 | ) { 11 | if (new_size == 0) 12 | return (void*) align; 13 | void *ret = realloc(ptr, new_size); 14 | if (!ret) 15 | abort(); 16 | return ret; 17 | } 18 | 19 | __attribute__((weak, export_name("canonical_abi_free"))) 20 | void canonical_abi_free( 21 | void *ptr, 22 | size_t size, 23 | size_t align 24 | ) { 25 | if (size == 0) 26 | return; 27 | free(ptr); 28 | } 29 | #include 30 | 31 | void spin_config_string_set(spin_config_string_t *ret, const char *s) { 32 | ret->ptr = (char*) s; 33 | ret->len = strlen(s); 34 | } 35 | 36 | void spin_config_string_dup(spin_config_string_t *ret, const char *s) { 37 | ret->len = strlen(s); 38 | ret->ptr = canonical_abi_realloc(NULL, 0, 1, ret->len); 39 | memcpy(ret->ptr, s, ret->len); 40 | } 41 | 42 | void spin_config_string_free(spin_config_string_t *ret) { 43 | canonical_abi_free(ret->ptr, ret->len, 1); 44 | ret->ptr = NULL; 45 | ret->len = 0; 46 | } 47 | void spin_config_error_free(spin_config_error_t *ptr) { 48 | switch ((int32_t) ptr->tag) { 49 | case 0: { 50 | spin_config_string_free(&ptr->val.provider); 51 | break; 52 | } 53 | case 1: { 54 | spin_config_string_free(&ptr->val.invalid_key); 55 | break; 56 | } 57 | case 2: { 58 | spin_config_string_free(&ptr->val.invalid_schema); 59 | break; 60 | } 61 | case 3: { 62 | spin_config_string_free(&ptr->val.other); 63 | break; 64 | } 65 | } 66 | } 67 | void spin_config_expected_string_error_free(spin_config_expected_string_error_t *ptr) { 68 | if (!ptr->is_err) { 69 | spin_config_string_free(&ptr->val.ok); 70 | } else { 71 | spin_config_error_free(&ptr->val.err); 72 | } 73 | } 74 | 75 | __attribute__((aligned(4))) 76 | static uint8_t RET_AREA[16]; 77 | __attribute__((import_module("spin-config"), import_name("get-config"))) 78 | void __wasm_import_spin_config_get_config(int32_t, int32_t, int32_t); 79 | void spin_config_get_config(spin_config_string_t *key, spin_config_expected_string_error_t *ret0) { 80 | int32_t ptr = (int32_t) &RET_AREA; 81 | __wasm_import_spin_config_get_config((int32_t) (*key).ptr, (int32_t) (*key).len, ptr); 82 | spin_config_expected_string_error_t expected; 83 | switch ((int32_t) (*((uint8_t*) (ptr + 0)))) { 84 | case 0: { 85 | expected.is_err = false; 86 | 87 | expected.val.ok = (spin_config_string_t) { (char*)(*((int32_t*) (ptr + 4))), (size_t)(*((int32_t*) (ptr + 8))) }; 88 | break; 89 | } 90 | case 1: { 91 | expected.is_err = true; 92 | spin_config_error_t variant; 93 | variant.tag = (int32_t) (*((uint8_t*) (ptr + 4))); 94 | switch ((int32_t) variant.tag) { 95 | case 0: { 96 | variant.val.provider = (spin_config_string_t) { (char*)(*((int32_t*) (ptr + 8))), (size_t)(*((int32_t*) (ptr + 12))) }; 97 | break; 98 | } 99 | case 1: { 100 | variant.val.invalid_key = (spin_config_string_t) { (char*)(*((int32_t*) (ptr + 8))), (size_t)(*((int32_t*) (ptr + 12))) }; 101 | break; 102 | } 103 | case 2: { 104 | variant.val.invalid_schema = (spin_config_string_t) { (char*)(*((int32_t*) (ptr + 8))), (size_t)(*((int32_t*) (ptr + 12))) }; 105 | break; 106 | } 107 | case 3: { 108 | variant.val.other = (spin_config_string_t) { (char*)(*((int32_t*) (ptr + 8))), (size_t)(*((int32_t*) (ptr + 12))) }; 109 | break; 110 | } 111 | } 112 | 113 | expected.val.err = variant; 114 | break; 115 | } 116 | }*ret0 = expected; 117 | } 118 | -------------------------------------------------------------------------------- /llm/llm.h: -------------------------------------------------------------------------------- 1 | #ifndef __BINDINGS_LLM_H 2 | #define __BINDINGS_LLM_H 3 | #ifdef __cplusplus 4 | extern "C" 5 | { 6 | #endif 7 | 8 | #include 9 | #include 10 | 11 | typedef struct { 12 | char *ptr; 13 | size_t len; 14 | } llm_string_t; 15 | 16 | void llm_string_set(llm_string_t *ret, const char *s); 17 | void llm_string_dup(llm_string_t *ret, const char *s); 18 | void llm_string_free(llm_string_t *ret); 19 | // A Large Language Model. 20 | typedef llm_string_t llm_inferencing_model_t; 21 | void llm_inferencing_model_free(llm_inferencing_model_t *ptr); 22 | // Inference request parameters 23 | typedef struct { 24 | uint32_t max_tokens; 25 | float repeat_penalty; 26 | uint32_t repeat_penalty_last_n_token_count; 27 | float temperature; 28 | uint32_t top_k; 29 | float top_p; 30 | } llm_inferencing_params_t; 31 | // The set of errors which may be raised by functions in this interface 32 | typedef struct { 33 | uint8_t tag; 34 | union { 35 | llm_string_t runtime_error; 36 | llm_string_t invalid_input; 37 | } val; 38 | } llm_error_t; 39 | #define LLM_ERROR_MODEL_NOT_SUPPORTED 0 40 | #define LLM_ERROR_RUNTIME_ERROR 1 41 | #define LLM_ERROR_INVALID_INPUT 2 42 | void llm_error_free(llm_error_t *ptr); 43 | // Usage information related to the inferencing result 44 | typedef struct { 45 | uint32_t prompt_token_count; 46 | uint32_t generated_token_count; 47 | } llm_inferencing_usage_t; 48 | // An inferencing result 49 | typedef struct { 50 | llm_string_t text; 51 | llm_inferencing_usage_t usage; 52 | } llm_inferencing_result_t; 53 | void llm_inferencing_result_free(llm_inferencing_result_t *ptr); 54 | // The model used for generating embeddings 55 | typedef llm_string_t llm_embedding_model_t; 56 | void llm_embedding_model_free(llm_embedding_model_t *ptr); 57 | typedef struct { 58 | float *ptr; 59 | size_t len; 60 | } llm_list_float32_t; 61 | void llm_list_float32_free(llm_list_float32_t *ptr); 62 | typedef struct { 63 | llm_list_float32_t *ptr; 64 | size_t len; 65 | } llm_list_list_float32_t; 66 | void llm_list_list_float32_free(llm_list_list_float32_t *ptr); 67 | // Usage related to an embeddings generation request 68 | typedef struct { 69 | uint32_t prompt_token_count; 70 | } llm_embeddings_usage_t; 71 | // Result of generating embeddings 72 | typedef struct { 73 | llm_list_list_float32_t embeddings; 74 | llm_embeddings_usage_t usage; 75 | } llm_embeddings_result_t; 76 | void llm_embeddings_result_free(llm_embeddings_result_t *ptr); 77 | typedef struct { 78 | bool is_some; 79 | llm_inferencing_params_t val; 80 | } llm_option_inferencing_params_t; 81 | typedef struct { 82 | bool is_err; 83 | union { 84 | llm_inferencing_result_t ok; 85 | llm_error_t err; 86 | } val; 87 | } llm_expected_inferencing_result_error_t; 88 | void llm_expected_inferencing_result_error_free(llm_expected_inferencing_result_error_t *ptr); 89 | typedef struct { 90 | llm_string_t *ptr; 91 | size_t len; 92 | } llm_list_string_t; 93 | void llm_list_string_free(llm_list_string_t *ptr); 94 | typedef struct { 95 | bool is_err; 96 | union { 97 | llm_embeddings_result_t ok; 98 | llm_error_t err; 99 | } val; 100 | } llm_expected_embeddings_result_error_t; 101 | void llm_expected_embeddings_result_error_free(llm_expected_embeddings_result_error_t *ptr); 102 | void llm_infer(llm_inferencing_model_t *model, llm_string_t *prompt, llm_option_inferencing_params_t *params, llm_expected_inferencing_result_error_t *ret0); 103 | void llm_generate_embeddings(llm_embedding_model_t *model, llm_list_string_t *text, llm_expected_embeddings_result_error_t *ret0); 104 | #ifdef __cplusplus 105 | } 106 | #endif 107 | #endif 108 | -------------------------------------------------------------------------------- /http/internals.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | // #cgo CFLAGS: -Wno-unused-parameter -Wno-switch-bool 4 | // #include 5 | // #include 6 | import "C" 7 | import ( 8 | "bytes" 9 | "fmt" 10 | "io" 11 | "net/http" 12 | "os" 13 | "unsafe" 14 | ) 15 | 16 | //export spin_http_handle_http_request 17 | func handle_http_request(req *C.spin_http_request_t, res *C.spin_http_response_t) { 18 | var body []byte 19 | if req.body.is_some { 20 | body = C.GoBytes(unsafe.Pointer(req.body.val.ptr), C.int(req.body.val.len)) 21 | } 22 | method := methods[req.method] 23 | header := fromSpinHeaders(&req.headers) 24 | url := header.Get(HeaderFullUrl) 25 | 26 | r, err := http.NewRequest(method, url, bytes.NewReader(body)) 27 | if err != nil { 28 | fmt.Fprintln(os.Stderr, err) 29 | res.status = C.uint16_t(http.StatusInternalServerError) 30 | return 31 | } 32 | 33 | r.Header = header 34 | r.Host = r.Header.Get("Host") 35 | r.RequestURI = C.GoStringN(req.uri.ptr, C.int(req.uri.len)) 36 | r.RemoteAddr = r.Header.Get(HeaderClientAddr) 37 | 38 | w := newResponse() 39 | 40 | // call user function 41 | handler(w, r) 42 | 43 | res.status = C.uint16_t(w.status) 44 | if len(w.header) > 0 { 45 | res.headers = C.spin_http_option_headers_t{ 46 | is_some: true, 47 | val: toSpinHeaders(w.header), 48 | } 49 | } else { 50 | res.headers = C.spin_http_option_headers_t{is_some: false} 51 | } 52 | 53 | res.body, err = toSpinBody(w.w) 54 | if err != nil { 55 | fmt.Fprintln(os.Stderr, err) 56 | } 57 | } 58 | 59 | func toSpinHeaders(hm http.Header) C.spin_http_headers_t { 60 | var reqHeaders C.spin_http_headers_t 61 | 62 | headersLen := len(hm) 63 | 64 | if headersLen > 0 { 65 | reqHeaders.len = C.ulong(headersLen) 66 | var x C.spin_http_tuple2_string_string_t 67 | reqHeaders.ptr = (*C.spin_http_tuple2_string_string_t)(C.malloc(C.size_t(headersLen) * C.size_t(unsafe.Sizeof(x)))) 68 | headers := unsafe.Slice(reqHeaders.ptr, headersLen) 69 | 70 | idx := 0 71 | for k, v := range hm { 72 | headers[idx] = newSpinHeader(k, v[0]) 73 | idx++ 74 | } 75 | } 76 | return reqHeaders 77 | } 78 | 79 | func toSpinBody(body io.Reader) (C.spin_http_option_body_t, error) { 80 | var spinBody C.spin_http_option_body_t 81 | spinBody.is_some = false 82 | 83 | if body == nil { 84 | return spinBody, nil 85 | } 86 | 87 | buf := new(bytes.Buffer) 88 | len, err := buf.ReadFrom(body) 89 | if err != nil { 90 | return spinBody, err 91 | } 92 | 93 | if len > 0 { 94 | bodyPtr := (*C.uint8_t)(C.malloc(C.size_t(len))) 95 | copy(unsafe.Slice(bodyPtr, len), buf.Bytes()) 96 | 97 | spinBody.is_some = true 98 | spinBody.val = C.spin_http_body_t{ 99 | ptr: bodyPtr, 100 | len: C.size_t(len), 101 | } 102 | } 103 | 104 | return spinBody, nil 105 | } 106 | 107 | // newSpinHeader creates a new spinHeader with the given key/value. 108 | func newSpinHeader(k, v string) C.spin_http_tuple2_string_string_t { 109 | return C.spin_http_tuple2_string_string_t{ 110 | f0: C.spin_http_string_t{ptr: C.CString(k), len: C.size_t(len(k))}, 111 | f1: C.spin_http_string_t{ptr: C.CString(v), len: C.size_t(len(v))}, 112 | } 113 | } 114 | 115 | var methods = [...]string{ 116 | "GET", 117 | "POST", 118 | "PUT", 119 | "DELETE", 120 | "PATCH", 121 | "HEAD", 122 | "OPTIONS", 123 | } 124 | 125 | func fromSpinHeaders(hm *C.spin_http_headers_t) http.Header { 126 | headersLen := int(hm.len) 127 | headers := make(http.Header, headersLen) 128 | 129 | var headersArr *C.spin_http_tuple2_string_string_t = hm.ptr 130 | headersSlice := unsafe.Slice(headersArr, headersLen) 131 | for i := 0; i < headersLen; i++ { 132 | tuple := headersSlice[i] 133 | k := C.GoStringN(tuple.f0.ptr, C.int(tuple.f0.len)) 134 | v := C.GoStringN(tuple.f1.ptr, C.int(tuple.f1.len)) 135 | 136 | headers.Add(k, v) 137 | } 138 | 139 | return headers 140 | } 141 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | VERSION = 2.3.0-pre0 2 | 3 | # ---------------------------------------------------------------------- 4 | # Test 5 | # ---------------------------------------------------------------------- 6 | .PHONY: test 7 | test: 8 | tinygo test -target=wasip1 -gc=leaking -buildmode=c-shared -v ./http 9 | tinygo test -target=wasip1 -gc=leaking -buildmode=c-shared -v ./redis 10 | 11 | .PHONY: test-integration 12 | test-integration: 13 | go test -v -count=1 . 14 | 15 | # ---------------------------------------------------------------------- 16 | # Generate C bindings 17 | # ---------------------------------------------------------------------- 18 | GENERATED_SPIN_VARIABLES = variables/spin-config.c variables/spin-config.h 19 | GENERATED_OUTBOUND_HTTP = http/wasi-outbound-http.c http/wasi-outbound-http.h 20 | GENERATED_SPIN_HTTP = http/spin-http.c http/spin-http.h 21 | GENERATED_OUTBOUND_REDIS = redis/outbound-redis.c redis/outbound-redis.h 22 | GENERATED_SPIN_REDIS = redis/spin-redis.c redis/spin-redis.h 23 | GENERATED_KEY_VALUE = kv/key-value.c kv/key-value.h 24 | GENERATED_SQLITE = sqlite/sqlite.c sqlite/sqlite.h 25 | GENERATED_LLM = llm/llm.c llm/llm.h 26 | GENERATED_OUTBOUND_MYSQL = mysql/outbound-mysql.c mysql/outbound-mysql.h 27 | GENERATED_OUTBOUND_PG = pg/outbound-pg.c pg/outbound-pg.h 28 | 29 | SDK_VERSION_SOURCE_FILE = sdk_version/sdk-version-go-template.c 30 | 31 | # NOTE: Please update this list if you add a new directory to the SDK: 32 | SDK_VERSION_DEST_FILES = variables/sdk-version-go.c http/sdk-version-go.c \ 33 | kv/sdk-version-go.c redis/sdk-version-go.c \ 34 | sqlite/sdk-version-go.c llm/sdk-version-go.c 35 | 36 | # NOTE: To generate the C bindings you need to install a forked version of wit-bindgen. 37 | # 38 | # cargo install wit-bindgen-cli --git https://github.com/fermyon/wit-bindgen-backport --rev "b89d5079ba5b07b319631a1b191d2139f126c976" 39 | # 40 | .PHONY: generate 41 | generate: $(GENERATED_OUTBOUND_HTTP) $(GENERATED_SPIN_HTTP) 42 | generate: $(GENERATED_OUTBOUND_REDIS) $(GENERATED_SPIN_REDIS) 43 | generate: $(GENERATED_SPIN_VARIABLES) $(GENERATED_KEY_VALUE) 44 | generate: $(GENERATED_SQLITE) $(GENERATED_LLM) 45 | generate: $(GENERATED_OUTBOUND_MYSQL) $(GENERATED_OUTBOUND_PG) 46 | generate: $(SDK_VERSION_DEST_FILES) 47 | 48 | $(SDK_VERSION_DEST_FILES): $(SDK_VERSION_SOURCE_FILE) 49 | export commit="$$(git rev-parse HEAD)"; \ 50 | sed -e "s/{{VERSION}}/${VERSION}/" -e "s/{{COMMIT}}/$${commit}/" < $< > $@ 51 | 52 | $(GENERATED_SPIN_VARIABLES): 53 | wit-bindgen c --import wit/spin-config.wit --out-dir ./variables 54 | 55 | $(GENERATED_OUTBOUND_HTTP): 56 | wit-bindgen c --import wit/wasi-outbound-http.wit --out-dir ./http 57 | 58 | $(GENERATED_SPIN_HTTP): 59 | wit-bindgen c --export wit/spin-http.wit --out-dir ./http 60 | 61 | $(GENERATED_OUTBOUND_REDIS): 62 | wit-bindgen c --import wit/outbound-redis.wit --out-dir ./redis 63 | 64 | $(GENERATED_SPIN_REDIS): 65 | wit-bindgen c --export wit/spin-redis.wit --out-dir ./redis 66 | 67 | $(GENERATED_KEY_VALUE): 68 | wit-bindgen c --import wit/key-value.wit --out-dir ./kv 69 | 70 | $(GENERATED_SQLITE): 71 | wit-bindgen c --import wit/sqlite.wit --out-dir ./sqlite 72 | 73 | $(GENERATED_LLM): 74 | wit-bindgen c --import wit/llm.wit --out-dir ./llm 75 | 76 | $(GENERATED_OUTBOUND_MYSQL): 77 | wit-bindgen c --import wit/outbound-mysql.wit --out-dir ./mysql 78 | 79 | $(GENERATED_OUTBOUND_PG): 80 | wit-bindgen c --import wit/outbound-pg.wit --out-dir ./pg 81 | 82 | # ---------------------------------------------------------------------- 83 | # Cleanup 84 | # ---------------------------------------------------------------------- 85 | .PHONY: clean 86 | clean: 87 | rm -f $(GENERATED_SPIN_CONFIG) 88 | rm -f $(GENERATED_OUTBOUND_HTTP) $(GENERATED_SPIN_HTTP) 89 | rm -f $(GENERATED_OUTBOUND_REDIS) $(GENERATED_SPIN_REDIS) 90 | rm -f $(GENERATED_KEY_VALUE) $(GENERATED_SQLITE) 91 | rm -f $(GENERATED_LLM) 92 | rm -f $(GENERATED_OUTBOUND_MYSQL) 93 | rm -f $(GENERATED_SDK_VERSION) 94 | rm -f $(SDK_VERSION_DEST_FILES) 95 | -------------------------------------------------------------------------------- /integration_test.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "fmt" 7 | "io" 8 | "net" 9 | "net/http" 10 | "os" 11 | "os/exec" 12 | "path/filepath" 13 | "testing" 14 | "time" 15 | ) 16 | 17 | const spinBinary = "spin" 18 | 19 | func retryGet(t *testing.T, url string) *http.Response { 20 | t.Helper() 21 | 22 | const maxTries = 600 // (10min) 23 | for i := 1; i < maxTries; i++ { 24 | // Catch call to `Fail` in other goroutine 25 | if t.Failed() { 26 | t.FailNow() 27 | } 28 | if res, err := http.Get(url); err != nil { 29 | t.Log(err) 30 | } else { 31 | return res 32 | } 33 | time.Sleep(1 * time.Second) 34 | } 35 | t.Fatal("Get request timeout: ", url) 36 | return nil 37 | } 38 | 39 | type testSpin struct { 40 | cancel func() 41 | url string 42 | cmd *exec.Cmd 43 | } 44 | 45 | func startSpin(t *testing.T, dir string) *testSpin { 46 | buildApp(t, dir) 47 | 48 | url := getFreePort(t) 49 | 50 | // long timeout because... ci 51 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute) 52 | 53 | cmd := exec.CommandContext(ctx, spinBinary, "up", "--listen", url) 54 | cmd.Dir = dir 55 | stderr := new(bytes.Buffer) 56 | cmd.Stderr = stderr 57 | if err := cmd.Start(); err != nil { 58 | t.Log(stderr.String()) 59 | t.Fatal(err) 60 | } 61 | 62 | go func() { 63 | cmd.Wait() 64 | if ctx.Err() == nil { 65 | t.Log("spin exited before the test finished:", cmd.ProcessState) 66 | t.Log("stderr:\n", stderr.String()) 67 | t.Fail() 68 | } 69 | }() 70 | 71 | return &testSpin{ 72 | cancel: cancel, 73 | url: fmt.Sprintf("http://%s", url), 74 | cmd: cmd, 75 | } 76 | } 77 | 78 | func buildApp(t *testing.T, dir string) { 79 | t.Helper() 80 | 81 | t.Log("building application:", dir) 82 | 83 | cmd := exec.Command(spinBinary, "build") 84 | cmd.Dir = dir 85 | 86 | stderr := new(bytes.Buffer) 87 | cmd.Stderr = stderr 88 | if err := cmd.Run(); err != nil { 89 | t.Log(stderr.String()) 90 | t.Errorf("Failed to build %q, %v", dir, err) 91 | } 92 | } 93 | 94 | func TestSpinRoundTrip(t *testing.T) { 95 | spin := startSpin(t, "http/testdata/spin-roundtrip") 96 | defer spin.cancel() 97 | 98 | resp := retryGet(t, spin.url+"/hello") 99 | spin.cancel() 100 | if resp.Body == nil { 101 | t.Fatal("body is nil") 102 | } 103 | t.Log(resp.Status) 104 | b, err := io.ReadAll(resp.Body) 105 | resp.Body.Close() 106 | if err != nil { 107 | t.Fatal(err) 108 | } 109 | 110 | // assert response body 111 | want := "Hello world!\n" 112 | got := string(b) 113 | if want != got { 114 | t.Fatalf("body is not equal: want = %q got = %q", want, got) 115 | } 116 | } 117 | 118 | func TestHTTPTriger(t *testing.T) { 119 | spin := startSpin(t, "http/testdata/http-tinygo") 120 | defer spin.cancel() 121 | 122 | resp := retryGet(t, spin.url+"/hello") 123 | spin.cancel() 124 | if resp.Body == nil { 125 | t.Fatal("body is nil") 126 | } 127 | t.Log(resp.Status) 128 | b, err := io.ReadAll(resp.Body) 129 | resp.Body.Close() 130 | if err != nil { 131 | t.Fatal(err) 132 | } 133 | 134 | // assert response body 135 | want := "Hello world!\n" 136 | got := string(b) 137 | if want != got { 138 | t.Fatalf("body is not equal: want = %q got = %q", want, got) 139 | } 140 | 141 | // assert response header 142 | if resp.Header.Get("foo") != "bar" { 143 | t.Fatal("header 'foo' was not set") 144 | } 145 | } 146 | 147 | // TestBuildExamples ensures that the tinygo examples will build successfully. 148 | func TestBuildExamples(t *testing.T) { 149 | examples, err := os.ReadDir("examples") 150 | if err != nil { 151 | t.Fatal(err) 152 | } 153 | for _, example := range examples { 154 | example := example 155 | t.Run(example.Name(), func(t *testing.T) { 156 | t.Parallel() 157 | buildApp(t, filepath.Join("examples", example.Name())) 158 | }) 159 | } 160 | } 161 | 162 | func getFreePort(t *testing.T) string { 163 | t.Helper() 164 | 165 | a, err := net.ResolveTCPAddr("tcp", "127.0.0.1:0") 166 | if err != nil { 167 | t.Fatal("failed to get free port: ", err) 168 | } 169 | 170 | l, err := net.ListenTCP("tcp", a) 171 | if err != nil { 172 | t.Fatal("failed to get free port: ", err) 173 | } 174 | l.Close() 175 | return l.Addr().String() 176 | } 177 | -------------------------------------------------------------------------------- /redis/redis.go: -------------------------------------------------------------------------------- 1 | // Package redis provides the handler function for the Redis trigger, as well 2 | // as access to Redis within Spin components. 3 | package redis 4 | 5 | import ( 6 | "errors" 7 | "fmt" 8 | "os" 9 | ) 10 | 11 | // handler is the function that will be called by the Redis trigger in Spin. 12 | var handler = defaultHandler 13 | 14 | // defaultHandler is a placeholder for returning a useful error to stdout when 15 | // the handler is not set. 16 | var defaultHandler = func(payload []byte) error { 17 | fmt.Fprintln(os.Stderr, "redis handler undefined") 18 | return nil 19 | } 20 | 21 | // Handle sets the handler function for redis. 22 | // It must be set in an init() function. 23 | func Handle(fn func(payload []byte) error) { 24 | handler = fn 25 | } 26 | 27 | // Client is a Redis client. 28 | type Client struct { 29 | addr string 30 | } 31 | 32 | // NewClient returns a Redis client. 33 | func NewClient(address string) *Client { 34 | return &Client{addr: address} 35 | } 36 | 37 | // Publish a Redis message to the specified channel. 38 | func (c *Client) Publish(channel string, payload []byte) error { 39 | if len(payload) == 0 { 40 | return errors.New("payload is empty") 41 | } 42 | return publish(c.addr, channel, payload) 43 | } 44 | 45 | // Get the value of a key. An error is returned if the value stored at key is 46 | // not a string. 47 | func (c *Client) Get(key string) ([]byte, error) { 48 | return get(c.addr, key) 49 | } 50 | 51 | // Set key to value. If key alreads holds a value, it is overwritten. 52 | func (c *Client) Set(key string, payload []byte) error { 53 | if len(payload) == 0 { 54 | return errors.New("payload is empty") 55 | } 56 | return set(c.addr, key, payload) 57 | } 58 | 59 | // Incr increments the number stored at key by one. If the key does not exist, 60 | // it is set to 0 before performing the operation. An error is returned if 61 | // the key contains a value of the wrong type or contains a string that can not 62 | // be represented as integer. 63 | func (c *Client) Incr(key string) (int64, error) { 64 | return incr(c.addr, key) 65 | } 66 | 67 | // Del removes the specified keys. A key is ignored if it does not exist. 68 | func (c *Client) Del(keys ...string) (int64, error) { 69 | return del(c.addr, keys) 70 | } 71 | 72 | // Sadd adds the specified values to the set for the specified key, creating 73 | // it if it does not already exist. 74 | func (c *Client) Sadd(key string, values ...string) (int64, error) { 75 | return sadd(c.addr, key, values) 76 | } 77 | 78 | // Smembers gets the elements of the set for the specified key. 79 | func (c *Client) Smembers(key string) ([]string, error) { 80 | return smembers(c.addr, key) 81 | } 82 | 83 | // Srem removes the specified elements from the set for the specified key. 84 | // This has no effect if the key does not exist. 85 | func (c *Client) Srem(key string, values ...string) (int64, error) { 86 | return srem(c.addr, key, values) 87 | } 88 | 89 | // ResultKind represents a result type returned from executing a Redis command. 90 | type ResultKind uint8 91 | 92 | const ( 93 | ResultKindNil ResultKind = iota 94 | ResultKindStatus 95 | ResultKindInt64 96 | ResultKindBinary 97 | ) 98 | 99 | // String implements fmt.Stringer. 100 | func (r ResultKind) String() string { 101 | switch r { 102 | case ResultKindNil: 103 | return "nil" 104 | case ResultKindStatus: 105 | return "status" 106 | case ResultKindInt64: 107 | return "int64" 108 | case ResultKindBinary: 109 | return "binary" 110 | default: 111 | return "unknown" 112 | } 113 | } 114 | 115 | // GoString implements fmt.GoStringer. 116 | func (r ResultKind) GoString() string { return r.String() } 117 | 118 | // Result represents a value returned from a Redis command. 119 | type Result struct { 120 | Kind ResultKind 121 | Val any 122 | } 123 | 124 | // Execute runs the specified Redis command with the specified arguments, 125 | // returning zero or more results. This is a general-purpose function which 126 | // should work with any Redis command. 127 | // 128 | // Arguments must be string, []byte, int, int64, or int32. 129 | func (c *Client) Execute(command string, arguments ...any) ([]*Result, error) { 130 | var params []*argument 131 | for _, a := range arguments { 132 | p, err := createParameter(a) 133 | if err != nil { 134 | return nil, err 135 | } 136 | params = append(params, p) 137 | } 138 | return execute(c.addr, command, params) 139 | } 140 | -------------------------------------------------------------------------------- /examples/redis-outbound/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "os" 7 | "reflect" 8 | "sort" 9 | "strconv" 10 | 11 | spin_http "github.com/spinframework/spin-go-sdk/v2/http" 12 | "github.com/spinframework/spin-go-sdk/v2/redis" 13 | ) 14 | 15 | func init() { 16 | 17 | // handler for the http trigger 18 | spin_http.Handle(func(w http.ResponseWriter, _ *http.Request) { 19 | 20 | // addr is the environment variable set in `spin.toml` that points to the 21 | // address of the Redis server. 22 | addr := os.Getenv("REDIS_ADDRESS") 23 | 24 | // channel is the environment variable set in `spin.toml` that specifies 25 | // the Redis channel that the component will publish to. 26 | channel := os.Getenv("REDIS_CHANNEL") 27 | 28 | // payload is the data publish to the redis channel. 29 | payload := []byte(`Hello redis from tinygo!`) 30 | 31 | rdb := redis.NewClient(addr) 32 | 33 | if err := rdb.Publish(channel, payload); err != nil { 34 | http.Error(w, err.Error(), http.StatusInternalServerError) 35 | return 36 | } 37 | 38 | // set redis `mykey` = `myvalue` 39 | if err := rdb.Set("mykey", []byte("myvalue")); err != nil { 40 | http.Error(w, err.Error(), http.StatusInternalServerError) 41 | return 42 | } 43 | 44 | // get redis payload for `mykey` 45 | if payload, err := rdb.Get("mykey"); err != nil { 46 | http.Error(w, err.Error(), http.StatusInternalServerError) 47 | return 48 | } else { 49 | w.Write([]byte("mykey value was: ")) 50 | w.Write(payload) 51 | w.Write([]byte("\n")) 52 | } 53 | 54 | // incr `spin-go-incr` by 1 55 | if payload, err := rdb.Incr("spin-go-incr"); err != nil { 56 | http.Error(w, err.Error(), http.StatusInternalServerError) 57 | return 58 | } else { 59 | w.Write([]byte("spin-go-incr value: ")) 60 | w.Write([]byte(strconv.FormatInt(payload, 10))) 61 | w.Write([]byte("\n")) 62 | } 63 | 64 | // delete `spin-go-incr` and `mykey` 65 | if payload, err := rdb.Del("spin-go-incr", "mykey", "non-existing-key"); err != nil { 66 | http.Error(w, err.Error(), http.StatusInternalServerError) 67 | } else { 68 | w.Write([]byte("deleted keys num: ")) 69 | w.Write([]byte(strconv.FormatInt(payload, 10))) 70 | w.Write([]byte("\n")) 71 | } 72 | 73 | if _, err := rdb.Sadd("myset", "foo", "bar"); err != nil { 74 | http.Error(w, err.Error(), http.StatusInternalServerError) 75 | return 76 | } 77 | 78 | { 79 | expected := []string{"bar", "foo"} 80 | payload, err := rdb.Smembers("myset") 81 | if err != nil { 82 | http.Error(w, err.Error(), http.StatusInternalServerError) 83 | return 84 | } 85 | sort.Strings(payload) 86 | if !reflect.DeepEqual(payload, expected) { 87 | http.Error( 88 | w, 89 | fmt.Sprintf( 90 | "unexpected SMEMBERS result: expected %v, got %v", 91 | expected, 92 | payload, 93 | ), 94 | http.StatusInternalServerError, 95 | ) 96 | return 97 | } 98 | } 99 | 100 | if _, err := rdb.Srem("myset", "bar"); err != nil { 101 | http.Error(w, err.Error(), http.StatusInternalServerError) 102 | return 103 | } 104 | 105 | { 106 | expected := []string{"foo"} 107 | if payload, err := rdb.Smembers("myset"); err != nil { 108 | http.Error(w, err.Error(), http.StatusInternalServerError) 109 | return 110 | } else if !reflect.DeepEqual(payload, expected) { 111 | http.Error( 112 | w, 113 | fmt.Sprintf( 114 | "unexpected SMEMBERS result: expected %v, got %v", 115 | expected, 116 | payload, 117 | ), 118 | http.StatusInternalServerError, 119 | ) 120 | return 121 | } 122 | } 123 | 124 | if _, err := rdb.Execute("set", "message", "hello"); err != nil { 125 | http.Error(w, err.Error(), http.StatusInternalServerError) 126 | return 127 | } 128 | 129 | if _, err := rdb.Execute("append", "message", " world"); err != nil { 130 | http.Error(w, err.Error(), http.StatusInternalServerError) 131 | return 132 | } 133 | 134 | if payload, err := rdb.Execute("get", "message"); err != nil { 135 | http.Error(w, err.Error(), http.StatusInternalServerError) 136 | return 137 | } else if !reflect.DeepEqual( 138 | payload, 139 | []*redis.Result{{ 140 | Kind: redis.ResultKindBinary, 141 | Val: []byte("hello world"), 142 | }}) { 143 | http.Error(w, "unexpected GET result", http.StatusInternalServerError) 144 | return 145 | } 146 | }) 147 | } 148 | 149 | func main() {} 150 | -------------------------------------------------------------------------------- /http/http.go: -------------------------------------------------------------------------------- 1 | // Package http contains the helper functions for writing Spin HTTP components 2 | // in TinyGo, as well as for sending outbound HTTP requests. 3 | package http 4 | 5 | import ( 6 | "fmt" 7 | "io" 8 | "net/http" 9 | "os" 10 | 11 | "github.com/julienschmidt/httprouter" 12 | ) 13 | 14 | const ( 15 | // The application base path. 16 | HeaderBasePath = "spin-base-path" 17 | // The component route pattern matched, _excluding_ any wildcard indicator. 18 | HeaderComponentRoot = "spin-component-route" 19 | // The full URL of the request. This includes full host and scheme information. 20 | HeaderFullUrl = "spin-full-url" 21 | // The part of the request path that was matched by the route (including 22 | // the base and wildcard indicator if present). 23 | HeaderMatchedRoute = "spin-matched-route" 24 | // The request path relative to the component route (including any base). 25 | HeaderPathInfo = "spin-path-info" 26 | // The component route pattern matched, as written in the component 27 | // manifest (that is, _excluding_ the base, but including the wildcard 28 | // indicator if present). 29 | HeaderRawComponentRoot = "spin-raw-component-route" 30 | // The client address for the request. 31 | HeaderClientAddr = "spin-client-addr" 32 | ) 33 | 34 | // Override the default HTTP client to be compatible with the Spin SDK. 35 | func init() { 36 | http.DefaultClient = NewClient() 37 | } 38 | 39 | // Router is a http.Handler which can be used to dispatch requests to different 40 | // handler functions via configurable routes 41 | type Router = httprouter.Router 42 | 43 | // Params is a Param-slice, as returned by the router. 44 | // The slice is ordered, the first URL parameter is also the first slice value. 45 | // It is therefore safe to read values by the index. 46 | type Params = httprouter.Params 47 | 48 | // Param is a single URL parameter, consisting of a key and a value. 49 | type Param = httprouter.Param 50 | 51 | // RouterHandle is a function that can be registered to a route to handle HTTP 52 | // requests. Like http.HandlerFunc, but has a third parameter for the values of 53 | // wildcards (variables). 54 | type RouterHandle = httprouter.Handle 55 | 56 | // New returns a new initialized Router. 57 | // Path auto-correction, including trailing slashes, is enabled by default. 58 | func NewRouter() *Router { 59 | return httprouter.New() 60 | } 61 | 62 | // NewTransport returns http.RoundTripper backed by Spin SDK 63 | func NewTransport() http.RoundTripper { 64 | return &Transport{} 65 | } 66 | 67 | // Transport implements http.RoundTripper 68 | type Transport struct{} 69 | 70 | // RoundTrip makes roundtrip using Spin SDK 71 | func (r *Transport) RoundTrip(req *http.Request) (*http.Response, error) { 72 | return Send(req) 73 | } 74 | 75 | // NewClient returns a new HTTP client compatible with the Spin SDK 76 | func NewClient() *http.Client { 77 | return &http.Client{ 78 | Transport: &Transport{}, 79 | } 80 | } 81 | 82 | // handler is the function that will be called by the http trigger in Spin. 83 | var handler = defaultHandler 84 | 85 | // defaultHandler is a placeholder for returning a useful error to stderr when 86 | // the handler is not set. 87 | var defaultHandler = func(http.ResponseWriter, *http.Request) { 88 | fmt.Fprintln(os.Stderr, "http handler undefined") 89 | } 90 | 91 | // Handle sets the handler function for the http trigger. 92 | // It must be set in an init() function. 93 | func Handle(fn func(http.ResponseWriter, *http.Request)) { 94 | handler = fn 95 | } 96 | 97 | // Get creates a GET HTTP request to a given URL and returns the HTTP response. 98 | // The destination of the request must be explicitly allowed in the Spin application 99 | // configuration, otherwise the request will not be sent. 100 | func Get(url string) (*http.Response, error) { 101 | return get(url) 102 | } 103 | 104 | // Post creates a POST HTTP request and returns the HTTP response. 105 | // The destination of the request must be explicitly allowed in the Spin application 106 | // configuration, otherwise the request will not be sent. 107 | func Post(url string, contentType string, body io.Reader) (*http.Response, error) { 108 | return post(url, contentType, body) 109 | } 110 | 111 | // Send sends an HTTP request and return the HTTP response. 112 | // The destination of the request must be explicitly allowed in the Spin application 113 | // configuration, otherwise the request will not be sent. 114 | func Send(req *http.Request) (*http.Response, error) { 115 | return send(req) 116 | } 117 | -------------------------------------------------------------------------------- /kv/kv.go: -------------------------------------------------------------------------------- 1 | // Package kv provides access to key value stores within Spin 2 | // components. 3 | package kv 4 | 5 | // #include "key-value.h" 6 | import "C" 7 | import ( 8 | "errors" 9 | "fmt" 10 | "unsafe" 11 | ) 12 | 13 | // Store is the Key/Value backend storage. 14 | type Store struct { 15 | name string 16 | active bool 17 | ptr C.key_value_store_t 18 | } 19 | 20 | // OpenStore creates a new instance of Store and opens a connection. 21 | func OpenStore(name string) (*Store, error) { 22 | s := &Store{name: name} 23 | if err := s.open(); err != nil { 24 | return nil, err 25 | } 26 | return s, nil 27 | } 28 | 29 | // Close terminates the connection to Store. 30 | func (s *Store) Close() { 31 | if s.active { 32 | C.key_value_close(C.uint32_t(s.ptr)) 33 | } 34 | s.active = false 35 | } 36 | 37 | // Get retrieves a value from Store. 38 | func (s *Store) Get(key string) ([]byte, error) { 39 | ckey := toCStr(key) 40 | var ret C.key_value_expected_list_u8_error_t 41 | C.key_value_get(C.uint32_t(s.ptr), &ckey, &ret) 42 | if ret.is_err { 43 | return nil, toErr((*C.key_value_error_t)(unsafe.Pointer(&ret.val))) 44 | } 45 | list := (*C.key_value_list_u8_t)(unsafe.Pointer(&ret.val)) 46 | return C.GoBytes(unsafe.Pointer(list.ptr), C.int(list.len)), nil 47 | } 48 | 49 | // Delete removes a value from Store. 50 | func (s *Store) Delete(key string) error { 51 | ckey := toCStr(key) 52 | var ret C.key_value_expected_unit_error_t 53 | C.key_value_delete(C.uint32_t(s.ptr), &ckey, &ret) 54 | if ret.is_err { 55 | return toErr((*C.key_value_error_t)(unsafe.Pointer(&ret.val))) 56 | } 57 | return nil 58 | } 59 | 60 | // Set creates a new key/value in Store. 61 | func (s *Store) Set(key string, value []byte) error { 62 | ckey := toCStr(key) 63 | cbytes := toCBytes(value) 64 | var ret C.key_value_expected_unit_error_t 65 | C.key_value_set(C.uint32_t(s.ptr), &ckey, &cbytes, &ret) 66 | if ret.is_err { 67 | return toErr((*C.key_value_error_t)(unsafe.Pointer(&ret.val))) 68 | } 69 | return nil 70 | } 71 | 72 | // Exists checks if a key exists within Store. 73 | func (s *Store) Exists(key string) (bool, error) { 74 | ckey := toCStr(key) 75 | var ret C.key_value_expected_bool_error_t 76 | C.key_value_exists(C.uint32_t(s.ptr), &ckey, &ret) 77 | if ret.is_err { 78 | return false, toErr((*C.key_value_error_t)(unsafe.Pointer(&ret.val))) 79 | } 80 | return *(*bool)(unsafe.Pointer(&ret.val)), nil 81 | } 82 | 83 | // GetKeys retrieves the list of keys from Store. 84 | func (s *Store) GetKeys() ([]string, error) { 85 | var ret C.key_value_expected_list_string_error_t 86 | C.key_value_get_keys(C.uint32_t(s.ptr), &ret) 87 | if ret.is_err { 88 | return nil, toErr((*C.key_value_error_t)(unsafe.Pointer(&ret.val))) 89 | } 90 | list := (*C.key_value_list_string_t)(unsafe.Pointer(&ret.val)) 91 | return fromCStrList(list), nil 92 | } 93 | 94 | func (s *Store) open() error { 95 | if s.active { 96 | return nil 97 | } 98 | cname := toCStr(s.name) 99 | var ret C.key_value_expected_store_error_t 100 | C.key_value_open(&cname, &ret) 101 | if ret.is_err { 102 | return toErr((*C.key_value_error_t)(unsafe.Pointer(&ret.val))) 103 | } 104 | s.ptr = *(*C.key_value_store_t)(unsafe.Pointer(&ret.val)) 105 | s.active = true 106 | return nil 107 | } 108 | 109 | func toCBytes(x []byte) C.key_value_list_u8_t { 110 | return C.key_value_list_u8_t{ptr: (*C.uint8_t)(unsafe.Pointer(&x[0])), len: C.size_t(len(x))} 111 | } 112 | 113 | func toCStr(x string) C.key_value_string_t { 114 | return C.key_value_string_t{ptr: C.CString(x), len: C.size_t(len(x))} 115 | } 116 | 117 | func fromCStrList(list *C.key_value_list_string_t) []string { 118 | var result []string 119 | 120 | listLen := int(list.len) 121 | slice := unsafe.Slice(list.ptr, listLen) 122 | for i := 0; i < listLen; i++ { 123 | str := slice[i] 124 | result = append(result, C.GoStringN(str.ptr, C.int(str.len))) 125 | } 126 | 127 | return result 128 | } 129 | 130 | func toErr(err *C.key_value_error_t) error { 131 | switch err.tag { 132 | case 0: 133 | return errors.New("store table full") 134 | case 1: 135 | return errors.New("no such store") 136 | case 2: 137 | return errors.New("access denied") 138 | case 3: 139 | return errors.New("invalid store") 140 | case 4: 141 | return errors.New("no such key") 142 | case 5: 143 | str := (*C.key_value_string_t)(unsafe.Pointer(&err.val)) 144 | return fmt.Errorf("io error: %s", C.GoStringN(str.ptr, C.int(str.len))) 145 | default: 146 | return fmt.Errorf("unrecognized error: %v", err.tag) 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /llm/internals.go: -------------------------------------------------------------------------------- 1 | package llm 2 | 3 | // #include "llm.h" 4 | import "C" 5 | import ( 6 | "errors" 7 | "fmt" 8 | "unsafe" 9 | ) 10 | 11 | func infer(model, prompt string, params *InferencingParams) (*InferencingResult, error) { 12 | llmModel := toLLMModel(model) 13 | llmPrompt := toLLMString(prompt) 14 | llmParams := toLLMInferencingParams(params) 15 | 16 | var ret C.llm_expected_inferencing_result_error_t 17 | defer C.llm_expected_inferencing_result_error_free(&ret) 18 | 19 | C.llm_infer(&llmModel, &llmPrompt, &llmParams, &ret) 20 | if ret.is_err { 21 | return nil, toErr((*C.llm_error_t)(unsafe.Pointer(&ret.val))) 22 | } 23 | 24 | result := (*C.llm_inferencing_result_t)(unsafe.Pointer(&ret.val)) 25 | 26 | r := &InferencingResult{ 27 | Text: C.GoStringN(result.text.ptr, C.int(result.text.len)), 28 | Usage: &InferencingUsage{ 29 | PromptTokenCount: int(result.usage.prompt_token_count), 30 | GeneratedTokenCount: int(result.usage.generated_token_count), 31 | }, 32 | } 33 | return r, nil 34 | } 35 | 36 | func toErr(err *C.llm_error_t) error { 37 | switch err.tag { 38 | case 0: 39 | return errors.New("model not supported") 40 | case 1: 41 | str := (*C.llm_string_t)(unsafe.Pointer(&err.val)) 42 | return fmt.Errorf("runtime error: %s", C.GoStringN(str.ptr, C.int(str.len))) 43 | case 2: 44 | str := (*C.llm_string_t)(unsafe.Pointer(&err.val)) 45 | return fmt.Errorf("invalid input error: %s", C.GoStringN(str.ptr, C.int(str.len))) 46 | default: 47 | return fmt.Errorf("unrecognized error: %v", err.tag) 48 | } 49 | } 50 | 51 | func toLLMModel(name string) C.llm_inferencing_model_t { 52 | llmString := toLLMString(name) 53 | return *(*C.llm_inferencing_model_t)(unsafe.Pointer(&llmString.ptr)) 54 | } 55 | 56 | func toLLMString(x string) C.llm_string_t { 57 | return C.llm_string_t{ptr: C.CString(x), len: C.size_t(len(x))} 58 | } 59 | 60 | func toLLMInferencingParams(p *InferencingParams) C.llm_option_inferencing_params_t { 61 | if p == nil { 62 | return C.llm_option_inferencing_params_t{is_some: false} 63 | } 64 | llmParams := C.llm_inferencing_params_t{ 65 | max_tokens: C.uint32_t(p.MaxTokens), 66 | repeat_penalty: C.float(p.RepeatPenalty), 67 | repeat_penalty_last_n_token_count: C.uint32_t(p.RepeatPenaltyLastNTokenCount), 68 | temperature: C.float(p.Temperature), 69 | top_k: C.uint32_t(p.TopK), 70 | top_p: C.float(p.TopP), 71 | } 72 | return C.llm_option_inferencing_params_t{is_some: true, val: llmParams} 73 | } 74 | 75 | func generateEmbeddings(model string, text []string) (*EmbeddingsResult, error) { 76 | llmModel := toLLMEmbeddingModel(model) 77 | llmListString := toLLMListString(text) 78 | 79 | var ret C.llm_expected_embeddings_result_error_t 80 | defer C.llm_expected_embeddings_result_error_free(&ret) 81 | 82 | C.llm_generate_embeddings(&llmModel, &llmListString, &ret) 83 | if ret.is_err { 84 | return nil, toErr((*C.llm_error_t)(unsafe.Pointer(&ret.val))) 85 | } 86 | 87 | result := (*C.llm_embeddings_result_t)(unsafe.Pointer(&ret.val)) 88 | 89 | r := &EmbeddingsResult{ 90 | Embeddings: fromLLMListListFloat32(result.embeddings), 91 | Usage: &EmbeddingsUsage{ 92 | PromptTokenCount: int(result.usage.prompt_token_count), 93 | }, 94 | } 95 | return r, nil 96 | } 97 | 98 | func toLLMEmbeddingModel(name string) C.llm_embedding_model_t { 99 | llmString := toLLMString(name) 100 | return *(*C.llm_embedding_model_t)(unsafe.Pointer(&llmString.ptr)) 101 | } 102 | 103 | func toLLMListString(xs []string) C.llm_list_string_t { 104 | cxs := make([]C.llm_string_t, len(xs)) 105 | for i := 0; i < len(xs); i++ { 106 | cxs[i] = toLLMString(xs[i]) 107 | } 108 | return C.llm_list_string_t{ptr: &cxs[0], len: C.size_t(len(cxs))} 109 | } 110 | 111 | func fromLLMListListFloat32(list C.llm_list_list_float32_t) [][]float32 { 112 | listLen := int(list.len) 113 | ret := make([][]float32, listLen) 114 | slice := unsafe.Slice(list.ptr, listLen) 115 | for i := 0; i < listLen; i++ { 116 | row := *((*C.llm_list_float32_t)(unsafe.Pointer(&slice[i]))) 117 | ret[i] = fromLLMListFloat32(row) 118 | } 119 | return ret 120 | } 121 | 122 | func fromLLMListFloat32(list C.llm_list_float32_t) []float32 { 123 | listLen := int(list.len) 124 | ret := make([]float32, listLen) 125 | slice := unsafe.Slice(list.ptr, listLen) 126 | for i := 0; i < listLen; i++ { 127 | v := *((*C.float)(unsafe.Pointer(&slice[i]))) 128 | ret[i] = float32(v) 129 | } 130 | return ret 131 | } 132 | -------------------------------------------------------------------------------- /http/spin-http.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | __attribute__((weak, export_name("canonical_abi_realloc"))) 5 | void *canonical_abi_realloc( 6 | void *ptr, 7 | size_t orig_size, 8 | size_t align, 9 | size_t new_size 10 | ) { 11 | if (new_size == 0) 12 | return (void*) align; 13 | void *ret = realloc(ptr, new_size); 14 | if (!ret) 15 | abort(); 16 | return ret; 17 | } 18 | 19 | __attribute__((weak, export_name("canonical_abi_free"))) 20 | void canonical_abi_free( 21 | void *ptr, 22 | size_t size, 23 | size_t align 24 | ) { 25 | if (size == 0) 26 | return; 27 | free(ptr); 28 | } 29 | #include 30 | 31 | void spin_http_string_set(spin_http_string_t *ret, const char *s) { 32 | ret->ptr = (char*) s; 33 | ret->len = strlen(s); 34 | } 35 | 36 | void spin_http_string_dup(spin_http_string_t *ret, const char *s) { 37 | ret->len = strlen(s); 38 | ret->ptr = canonical_abi_realloc(NULL, 0, 1, ret->len); 39 | memcpy(ret->ptr, s, ret->len); 40 | } 41 | 42 | void spin_http_string_free(spin_http_string_t *ret) { 43 | canonical_abi_free(ret->ptr, ret->len, 1); 44 | ret->ptr = NULL; 45 | ret->len = 0; 46 | } 47 | void spin_http_body_free(spin_http_body_t *ptr) { 48 | canonical_abi_free(ptr->ptr, ptr->len * 1, 1); 49 | } 50 | void spin_http_tuple2_string_string_free(spin_http_tuple2_string_string_t *ptr) { 51 | spin_http_string_free(&ptr->f0); 52 | spin_http_string_free(&ptr->f1); 53 | } 54 | void spin_http_headers_free(spin_http_headers_t *ptr) { 55 | for (size_t i = 0; i < ptr->len; i++) { 56 | spin_http_tuple2_string_string_free(&ptr->ptr[i]); 57 | } 58 | canonical_abi_free(ptr->ptr, ptr->len * 16, 4); 59 | } 60 | void spin_http_params_free(spin_http_params_t *ptr) { 61 | for (size_t i = 0; i < ptr->len; i++) { 62 | spin_http_tuple2_string_string_free(&ptr->ptr[i]); 63 | } 64 | canonical_abi_free(ptr->ptr, ptr->len * 16, 4); 65 | } 66 | void spin_http_uri_free(spin_http_uri_t *ptr) { 67 | spin_http_string_free(ptr); 68 | } 69 | void spin_http_option_body_free(spin_http_option_body_t *ptr) { 70 | if (ptr->is_some) { 71 | spin_http_body_free(&ptr->val); 72 | } 73 | } 74 | void spin_http_request_free(spin_http_request_t *ptr) { 75 | spin_http_uri_free(&ptr->uri); 76 | spin_http_headers_free(&ptr->headers); 77 | spin_http_params_free(&ptr->params); 78 | spin_http_option_body_free(&ptr->body); 79 | } 80 | void spin_http_option_headers_free(spin_http_option_headers_t *ptr) { 81 | if (ptr->is_some) { 82 | spin_http_headers_free(&ptr->val); 83 | } 84 | } 85 | void spin_http_response_free(spin_http_response_t *ptr) { 86 | spin_http_option_headers_free(&ptr->headers); 87 | spin_http_option_body_free(&ptr->body); 88 | } 89 | 90 | __attribute__((aligned(4))) 91 | static uint8_t RET_AREA[28]; 92 | __attribute__((export_name("handle-http-request"))) 93 | int32_t __wasm_export_spin_http_handle_http_request(int32_t arg, int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3, int32_t arg4, int32_t arg5, int32_t arg6, int32_t arg7, int32_t arg8) { 94 | spin_http_option_body_t option; 95 | switch (arg6) { 96 | case 0: { 97 | option.is_some = false; 98 | 99 | break; 100 | } 101 | case 1: { 102 | option.is_some = true; 103 | 104 | option.val = (spin_http_body_t) { (uint8_t*)(arg7), (size_t)(arg8) }; 105 | break; 106 | } 107 | }spin_http_request_t arg9 = (spin_http_request_t) { 108 | arg, 109 | (spin_http_string_t) { (char*)(arg0), (size_t)(arg1) }, 110 | (spin_http_headers_t) { (spin_http_tuple2_string_string_t*)(arg2), (size_t)(arg3) }, 111 | (spin_http_params_t) { (spin_http_tuple2_string_string_t*)(arg4), (size_t)(arg5) }, 112 | option, 113 | }; 114 | spin_http_response_t ret; 115 | spin_http_handle_http_request(&arg9, &ret); 116 | int32_t ptr = (int32_t) &RET_AREA; 117 | *((int16_t*)(ptr + 0)) = (int32_t) ((ret).status); 118 | 119 | if (((ret).headers).is_some) { 120 | const spin_http_headers_t *payload10 = &((ret).headers).val; 121 | *((int8_t*)(ptr + 4)) = 1; 122 | *((int32_t*)(ptr + 12)) = (int32_t) (*payload10).len; 123 | *((int32_t*)(ptr + 8)) = (int32_t) (*payload10).ptr; 124 | 125 | } else { 126 | *((int8_t*)(ptr + 4)) = 0; 127 | 128 | } 129 | 130 | if (((ret).body).is_some) { 131 | const spin_http_body_t *payload12 = &((ret).body).val; 132 | *((int8_t*)(ptr + 16)) = 1; 133 | *((int32_t*)(ptr + 24)) = (int32_t) (*payload12).len; 134 | *((int32_t*)(ptr + 20)) = (int32_t) (*payload12).ptr; 135 | 136 | } else { 137 | *((int8_t*)(ptr + 16)) = 0; 138 | 139 | } 140 | return ptr; 141 | } 142 | -------------------------------------------------------------------------------- /sqlite/internals.go: -------------------------------------------------------------------------------- 1 | package sqlite 2 | 3 | // #include "sqlite.h" 4 | import "C" 5 | import ( 6 | "errors" 7 | "fmt" 8 | "unsafe" 9 | ) 10 | 11 | func open(name string) (*conn, error) { 12 | var dbname C.sqlite_string_t 13 | var ret C.sqlite_expected_connection_error_t 14 | 15 | dbname = sqliteStr(name) 16 | C.sqlite_open(&dbname, &ret) 17 | 18 | if ret.is_err { 19 | return nil, toErr((*C.sqlite_error_t)(unsafe.Pointer(&ret.val))) 20 | } 21 | 22 | sqliteConn := *((*C.sqlite_connection_t)(unsafe.Pointer(&ret.val))) 23 | return &conn{_ptr: sqliteConn}, nil 24 | } 25 | 26 | func (db *conn) close() { 27 | C.sqlite_close(db._ptr) 28 | } 29 | 30 | func (db *conn) execute(statement string, args []any) (*rows, error) { 31 | var ret C.sqlite_expected_query_result_error_t 32 | defer C.sqlite_expected_query_result_error_free(&ret) 33 | 34 | sqliteStatement := sqliteStr(statement) 35 | params := toSqliteListValue(args) 36 | 37 | C.sqlite_execute(db._ptr, &sqliteStatement, ¶ms, &ret) 38 | 39 | if ret.is_err { 40 | spinErr := (*C.sqlite_error_t)(unsafe.Pointer(&ret.val)) 41 | return nil, toErr(spinErr) 42 | } 43 | 44 | qr := (*C.sqlite_query_result_t)(unsafe.Pointer(&ret.val)) 45 | 46 | result := &rows{ 47 | columns: fromSqliteListString(qr.columns), 48 | rows: fromSqliteListRowResult(qr.rows), 49 | len: int(qr.rows.len), 50 | } 51 | 52 | return result, nil 53 | } 54 | 55 | func fromSqliteListRowResult(list C.sqlite_list_row_result_t) [][]any { 56 | listLen := int(list.len) 57 | ret := make([][]any, listLen) 58 | slice := unsafe.Slice(list.ptr, listLen) 59 | for i := 0; i < listLen; i++ { 60 | row := *((*C.sqlite_list_value_t)(unsafe.Pointer(&slice[i]))) 61 | ret[i] = fromSqliteListValue(row) 62 | } 63 | return ret 64 | 65 | } 66 | 67 | func fromSqliteListString(list C.sqlite_list_string_t) []string { 68 | listLen := int(list.len) 69 | ret := make([]string, listLen) 70 | slice := unsafe.Slice(list.ptr, listLen) 71 | for i := 0; i < listLen; i++ { 72 | str := slice[i] 73 | ret[i] = C.GoStringN(str.ptr, C.int(str.len)) 74 | } 75 | return ret 76 | } 77 | 78 | func fromSqliteListValue(list C.sqlite_list_value_t) []any { 79 | listLen := int(list.len) 80 | ret := make([]any, listLen) 81 | slice := unsafe.Slice(list.ptr, listLen) 82 | for i := 0; i < listLen; i++ { 83 | ret[i] = fromSqliteValue(slice[i]) 84 | } 85 | return ret 86 | } 87 | 88 | func toSqliteListValue(xv []any) C.sqlite_list_value_t { 89 | if len(xv) == 0 { 90 | return C.sqlite_list_value_t{} 91 | } 92 | cxv := make([]C.sqlite_value_t, len(xv)) 93 | for i := 0; i < len(xv); i++ { 94 | cxv[i] = toSqliteValue(xv[i]) 95 | } 96 | return C.sqlite_list_value_t{ptr: &cxv[0], len: C.size_t(len(cxv))} 97 | } 98 | 99 | const ( 100 | valueInt uint8 = iota 101 | valueReal 102 | valueText 103 | valueBlob 104 | valueNull 105 | ) 106 | 107 | func toSqliteValue(x any) C.sqlite_value_t { 108 | var ret C.sqlite_value_t 109 | switch v := x.(type) { 110 | case int: 111 | *(*C.int64_t)(unsafe.Pointer(&ret.val)) = int64(v) 112 | ret.tag = valueInt 113 | case int64: 114 | *(*C.int64_t)(unsafe.Pointer(&ret.val)) = v 115 | ret.tag = valueInt 116 | case float64: 117 | *(*C.double)(unsafe.Pointer(&ret.val)) = v 118 | ret.tag = valueReal 119 | case string: 120 | str := sqliteStr(v) 121 | *(*C.sqlite_string_t)(unsafe.Pointer(&ret.val)) = str 122 | ret.tag = valueText 123 | case []byte: 124 | blob := C.sqlite_list_u8_t{ptr: &v[0], len: C.size_t(len(v))} 125 | *(*C.sqlite_list_u8_t)(unsafe.Pointer(&ret.val)) = blob 126 | ret.tag = valueBlob 127 | default: 128 | ret.tag = valueNull 129 | } 130 | return ret 131 | } 132 | 133 | func fromSqliteValue(x C.sqlite_value_t) any { 134 | switch x.tag { 135 | case valueInt: 136 | return int64(*(*C.int64_t)(unsafe.Pointer(&x.val))) 137 | case valueReal: 138 | return float64(*(*C.double)(unsafe.Pointer(&x.val))) 139 | case valueBlob: 140 | blob := (*C.sqlite_list_u8_t)(unsafe.Pointer(&x.val)) 141 | return C.GoBytes(unsafe.Pointer(blob.ptr), C.int(blob.len)) 142 | case valueText: 143 | str := (*C.sqlite_string_t)(unsafe.Pointer(&x.val)) 144 | return C.GoStringN(str.ptr, C.int(str.len)) 145 | } 146 | return nil 147 | } 148 | 149 | func sqliteStr(x string) C.sqlite_string_t { 150 | return C.sqlite_string_t{ptr: C.CString(x), len: C.size_t(len(x))} 151 | } 152 | 153 | func toErr(err *C.sqlite_error_t) error { 154 | switch err.tag { 155 | case 0: 156 | return errors.New("no such database") 157 | case 1: 158 | return errors.New("access denied") 159 | case 2: 160 | return errors.New("invalid connection") 161 | case 3: 162 | return errors.New("database full") 163 | case 4: 164 | str := (*C.sqlite_string_t)(unsafe.Pointer(&err.val)) 165 | return errors.New(fmt.Sprintf("io error: %s", C.GoStringN(str.ptr, C.int(str.len)))) 166 | default: 167 | return errors.New(fmt.Sprintf("unrecognized error: %v", err.tag)) 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /mysql/mysql.go: -------------------------------------------------------------------------------- 1 | package mysql 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "database/sql/driver" 7 | "errors" 8 | "io" 9 | "reflect" 10 | 11 | spindb "github.com/spinframework/spin-go-sdk/v2/internal/db" 12 | ) 13 | 14 | // Open returns a new connection to the database. 15 | func Open(address string) *sql.DB { 16 | return sql.OpenDB(&connector{address}) 17 | } 18 | 19 | // connector implements driver.Connector. 20 | type connector struct { 21 | address string 22 | } 23 | 24 | // Connect returns a connection to the database. 25 | func (d *connector) Connect(_ context.Context) (driver.Conn, error) { 26 | return d.Open(d.address) 27 | } 28 | 29 | // Driver returns the underlying Driver of the Connector. 30 | func (d *connector) Driver() driver.Driver { 31 | return d 32 | } 33 | 34 | // Open returns a new connection to the database. 35 | func (d *connector) Open(address string) (driver.Conn, error) { 36 | return &conn{address: address}, nil 37 | } 38 | 39 | // conn implements driver.Conn 40 | type conn struct { 41 | address string 42 | } 43 | 44 | var _ driver.Conn = (*conn)(nil) 45 | 46 | // Prepare returns a prepared statement, bound to this connection. 47 | func (c *conn) Prepare(query string) (driver.Stmt, error) { 48 | return &stmt{c: c, query: query}, nil 49 | } 50 | 51 | func (c *conn) Close() error { 52 | return nil 53 | } 54 | 55 | func (c *conn) Begin() (driver.Tx, error) { 56 | return nil, errors.New("transactions are unsupported by this driver") 57 | } 58 | 59 | type stmt struct { 60 | c *conn 61 | query string 62 | } 63 | 64 | var _ driver.Stmt = (*stmt)(nil) 65 | var _ driver.ColumnConverter = (*stmt)(nil) 66 | 67 | // Close closes the statement. 68 | func (s *stmt) Close() error { 69 | return nil 70 | } 71 | 72 | // NumInput returns the number of placeholder parameters. 73 | func (s *stmt) NumInput() int { 74 | // Golang sql won't sanity check argument counts before Query. 75 | return -1 76 | } 77 | 78 | // Query executes a query that may return rows, such as a SELECT. 79 | func (s *stmt) Query(args []driver.Value) (driver.Rows, error) { 80 | params := make([]any, len(args)) 81 | for i := range args { 82 | params[i] = args[i] 83 | } 84 | return query(s.c.address, s.query, params) 85 | } 86 | 87 | // Exec executes a query that doesn't return rows, such as an INSERT or 88 | // UPDATE. 89 | func (s *stmt) Exec(args []driver.Value) (driver.Result, error) { 90 | params := make([]any, len(args)) 91 | for i := range args { 92 | params[i] = args[i] 93 | } 94 | err := execute(s.c.address, s.query, params) 95 | return &result{}, err 96 | } 97 | 98 | // ColumnConverter returns GlobalParameterConverter to prevent using driver.DefaultParameterConverter. 99 | func (s *stmt) ColumnConverter(_ int) driver.ValueConverter { 100 | return spindb.GlobalParameterConverter 101 | } 102 | 103 | type result struct{} 104 | 105 | func (r result) LastInsertId() (int64, error) { 106 | return -1, errors.New("LastInsertId is unsupported by this driver") 107 | } 108 | 109 | func (r result) RowsAffected() (int64, error) { 110 | return -1, errors.New("RowsAffected is unsupported by this driver") 111 | } 112 | 113 | type rows struct { 114 | columns []string 115 | columnType []uint8 116 | pos int 117 | len int 118 | rows [][]any 119 | closed bool 120 | } 121 | 122 | var _ driver.Rows = (*rows)(nil) 123 | var _ driver.RowsColumnTypeScanType = (*rows)(nil) 124 | var _ driver.RowsNextResultSet = (*rows)(nil) 125 | 126 | // Columns return column names. 127 | func (r *rows) Columns() []string { 128 | return r.columns 129 | } 130 | 131 | // Close closes the rows iterator. 132 | func (r *rows) Close() error { 133 | r.rows = nil 134 | r.pos = 0 135 | r.len = 0 136 | r.closed = true 137 | return nil 138 | } 139 | 140 | // Next moves the cursor to the next row. 141 | func (r *rows) Next(dest []driver.Value) error { 142 | if !r.HasNextResultSet() { 143 | return io.EOF 144 | } 145 | for i := 0; i != len(r.columns); i++ { 146 | dest[i] = driver.Value(r.rows[r.pos][i]) 147 | } 148 | r.pos++ 149 | return nil 150 | } 151 | 152 | // HasNextResultSet is called at the end of the current result set and 153 | // reports whether there is another result set after the current one. 154 | func (r *rows) HasNextResultSet() bool { 155 | return r.pos < r.len 156 | } 157 | 158 | // NextResultSet advances the driver to the next result set even 159 | // if there are remaining rows in the current result set. 160 | // 161 | // NextResultSet should return io.EOF when there are no more result sets. 162 | func (r *rows) NextResultSet() error { 163 | if r.HasNextResultSet() { 164 | r.pos++ 165 | return nil 166 | } 167 | return io.EOF // Per interface spec. 168 | } 169 | 170 | // ColumnTypeScanType return the value type that can be used to scan types into. 171 | func (r *rows) ColumnTypeScanType(index int) reflect.Type { 172 | return colTypeToReflectType(r.columnType[index]) 173 | } 174 | -------------------------------------------------------------------------------- /pg/pg.go: -------------------------------------------------------------------------------- 1 | package pg 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "database/sql/driver" 7 | "errors" 8 | "io" 9 | "reflect" 10 | 11 | spindb "github.com/spinframework/spin-go-sdk/v2/internal/db" 12 | ) 13 | 14 | // Open returns a new connection to the database. 15 | func Open(address string) *sql.DB { 16 | return sql.OpenDB(&connector{address}) 17 | } 18 | 19 | // connector implements driver.Connector. 20 | type connector struct { 21 | address string 22 | } 23 | 24 | // Connect returns a connection to the database. 25 | func (d *connector) Connect(_ context.Context) (driver.Conn, error) { 26 | return d.Open(d.address) 27 | } 28 | 29 | // Driver returns the underlying Driver of the Connector. 30 | func (d *connector) Driver() driver.Driver { 31 | return d 32 | } 33 | 34 | // Open returns a new connection to the database. 35 | func (d *connector) Open(address string) (driver.Conn, error) { 36 | return &conn{address: address}, nil 37 | } 38 | 39 | // conn implements driver.Conn 40 | type conn struct { 41 | address string 42 | } 43 | 44 | var _ driver.Conn = (*conn)(nil) 45 | 46 | // Prepare returns a prepared statement, bound to this connection. 47 | func (c *conn) Prepare(query string) (driver.Stmt, error) { 48 | return &stmt{c: c, query: query}, nil 49 | } 50 | 51 | func (c *conn) Close() error { 52 | return nil 53 | } 54 | 55 | func (c *conn) Begin() (driver.Tx, error) { 56 | return nil, errors.New("transactions are unsupported by this driver") 57 | } 58 | 59 | type stmt struct { 60 | c *conn 61 | query string 62 | } 63 | 64 | var _ driver.Stmt = (*stmt)(nil) 65 | var _ driver.ColumnConverter = (*stmt)(nil) 66 | 67 | // Close closes the statement. 68 | func (s *stmt) Close() error { 69 | return nil 70 | } 71 | 72 | // NumInput returns the number of placeholder parameters. 73 | func (s *stmt) NumInput() int { 74 | // Golang sql won't sanity check argument counts before Query. 75 | return -1 76 | } 77 | 78 | // Query executes a query that may return rows, such as a SELECT. 79 | func (s *stmt) Query(args []driver.Value) (driver.Rows, error) { 80 | params := make([]any, len(args)) 81 | for i := range args { 82 | params[i] = args[i] 83 | } 84 | return query(s.c.address, s.query, params) 85 | } 86 | 87 | // Exec executes a query that doesn't return rows, such as an INSERT or 88 | // UPDATE. 89 | func (s *stmt) Exec(args []driver.Value) (driver.Result, error) { 90 | params := make([]any, len(args)) 91 | for i := range args { 92 | params[i] = args[i] 93 | } 94 | n, err := execute(s.c.address, s.query, params) 95 | return &result{rowsAffected: int64(n)}, err 96 | } 97 | 98 | // ColumnConverter returns GlobalParameterConverter to prevent using driver.DefaultParameterConverter. 99 | func (s *stmt) ColumnConverter(_ int) driver.ValueConverter { 100 | return spindb.GlobalParameterConverter 101 | } 102 | 103 | type result struct { 104 | rowsAffected int64 105 | } 106 | 107 | func (r result) LastInsertId() (int64, error) { 108 | return -1, errors.New("LastInsertId is unsupported by this driver") 109 | } 110 | 111 | func (r result) RowsAffected() (int64, error) { 112 | return r.rowsAffected, nil 113 | } 114 | 115 | type rows struct { 116 | columns []string 117 | columnType []uint8 118 | pos int 119 | len int 120 | rows [][]any 121 | closed bool 122 | } 123 | 124 | var _ driver.Rows = (*rows)(nil) 125 | var _ driver.RowsColumnTypeScanType = (*rows)(nil) 126 | var _ driver.RowsNextResultSet = (*rows)(nil) 127 | 128 | // Columns return column names. 129 | func (r *rows) Columns() []string { 130 | return r.columns 131 | } 132 | 133 | // Close closes the rows iterator. 134 | func (r *rows) Close() error { 135 | r.rows = nil 136 | r.pos = 0 137 | r.len = 0 138 | r.closed = true 139 | return nil 140 | } 141 | 142 | // Next moves the cursor to the next row. 143 | func (r *rows) Next(dest []driver.Value) error { 144 | if !r.HasNextResultSet() { 145 | return io.EOF 146 | } 147 | for i := 0; i != len(r.columns); i++ { 148 | dest[i] = driver.Value(r.rows[r.pos][i]) 149 | } 150 | r.pos++ 151 | return nil 152 | } 153 | 154 | // HasNextResultSet is called at the end of the current result set and 155 | // reports whether there is another result set after the current one. 156 | func (r *rows) HasNextResultSet() bool { 157 | return r.pos < r.len 158 | } 159 | 160 | // NextResultSet advances the driver to the next result set even 161 | // if there are remaining rows in the current result set. 162 | // 163 | // NextResultSet should return io.EOF when there are no more result sets. 164 | func (r *rows) NextResultSet() error { 165 | if r.HasNextResultSet() { 166 | r.pos++ 167 | return nil 168 | } 169 | return io.EOF // Per interface spec. 170 | } 171 | 172 | // ColumnTypeScanType return the value type that can be used to scan types into. 173 | func (r *rows) ColumnTypeScanType(index int) reflect.Type { 174 | return colTypeToReflectType(r.columnType[index]) 175 | } 176 | -------------------------------------------------------------------------------- /sqlite/sqlite.go: -------------------------------------------------------------------------------- 1 | package sqlite 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "database/sql/driver" 7 | "errors" 8 | "io" 9 | 10 | spindb "github.com/spinframework/spin-go-sdk/v2/internal/db" 11 | ) 12 | 13 | // Open returns a new connection to the database. 14 | func Open(name string) *sql.DB { 15 | return sql.OpenDB(&connector{name: name}) 16 | } 17 | 18 | // conn represents a database connection. 19 | type conn struct { 20 | _ptr uint32 21 | } 22 | 23 | // Close the connection. 24 | func (c *conn) Close() error { 25 | c.close() 26 | return nil 27 | } 28 | 29 | // Prepare returns a prepared statement, bound to this connection. 30 | func (c *conn) Prepare(query string) (driver.Stmt, error) { 31 | return &stmt{c: c, query: query}, nil 32 | } 33 | 34 | // Begin isn't supported. 35 | func (c *conn) Begin() (driver.Tx, error) { 36 | return nil, errors.New("transactions are unsupported by this driver") 37 | } 38 | 39 | // connector implements driver.Connector. 40 | type connector struct { 41 | conn *conn 42 | name string 43 | } 44 | 45 | // Connect returns a connection to the database. 46 | func (d *connector) Connect(_ context.Context) (driver.Conn, error) { 47 | if d.conn != nil { 48 | return d.conn, nil 49 | } 50 | 51 | return d.Open(d.name) 52 | } 53 | 54 | // Driver returns the underlying Driver of the Connector. 55 | func (d *connector) Driver() driver.Driver { 56 | return d 57 | } 58 | 59 | // Open returns a new connection to the database. 60 | func (d *connector) Open(name string) (driver.Conn, error) { 61 | conn, err := open(name) 62 | if err != nil { 63 | return nil, err 64 | } 65 | 66 | d.conn = conn 67 | 68 | return conn, err 69 | } 70 | 71 | // Close closes the connection to the database. 72 | func (d *connector) Close() error { 73 | if d.conn != nil { 74 | d.conn.Close() 75 | } 76 | 77 | return nil 78 | } 79 | 80 | type rows struct { 81 | columns []string 82 | pos int 83 | len int 84 | rows [][]any 85 | closed bool 86 | } 87 | 88 | var _ driver.Rows = (*rows)(nil) 89 | 90 | // Columns return column names. 91 | func (r *rows) Columns() []string { 92 | return r.columns 93 | } 94 | 95 | // Close closes the rows iterator. 96 | func (r *rows) Close() error { 97 | r.rows = nil 98 | r.pos = 0 99 | r.len = 0 100 | r.closed = true 101 | return nil 102 | } 103 | 104 | // Next moves the cursor to the next row. 105 | func (r *rows) Next(dest []driver.Value) error { 106 | if !r.HasNextResultSet() { 107 | return io.EOF 108 | } 109 | for i := 0; i != len(r.columns); i++ { 110 | dest[i] = driver.Value(r.rows[r.pos][i]) 111 | } 112 | r.pos++ 113 | return nil 114 | } 115 | 116 | // HasNextResultSet is called at the end of the current result set and 117 | // reports whether there is another result set after the current one. 118 | func (r *rows) HasNextResultSet() bool { 119 | return r.pos < r.len 120 | } 121 | 122 | // NextResultSet advances the driver to the next result set even 123 | // if there are remaining rows in the current result set. 124 | // 125 | // NextResultSet should return io.EOF when there are no more result sets. 126 | func (r *rows) NextResultSet() error { 127 | if r.HasNextResultSet() { 128 | r.pos++ 129 | return nil 130 | } 131 | return io.EOF // Per interface spec. 132 | } 133 | 134 | type stmt struct { 135 | c *conn 136 | query string 137 | } 138 | 139 | var _ driver.Stmt = (*stmt)(nil) 140 | var _ driver.ColumnConverter = (*stmt)(nil) 141 | 142 | // Close closes the statement. 143 | func (s *stmt) Close() error { 144 | return nil 145 | } 146 | 147 | // NumInput returns the number of placeholder parameters. 148 | func (s *stmt) NumInput() int { 149 | // Golang sql won't sanity check argument counts before Query. 150 | return -1 151 | } 152 | 153 | // Query executes a query that may return rows, such as a SELECT. 154 | func (s *stmt) Query(args []driver.Value) (driver.Rows, error) { 155 | params := make([]any, len(args)) 156 | for i := range args { 157 | params[i] = args[i] 158 | } 159 | return s.c.execute(s.query, params) 160 | } 161 | 162 | // Exec executes a query that doesn't return rows, such as an INSERT or 163 | // UPDATE. 164 | func (s *stmt) Exec(args []driver.Value) (driver.Result, error) { 165 | params := make([]any, len(args)) 166 | for i := range args { 167 | params[i] = args[i] 168 | } 169 | _, err := s.c.execute(s.query, params) 170 | return &result{}, err 171 | } 172 | 173 | // ColumnConverter returns GlobalParameterConverter to prevent using driver.DefaultParameterConverter. 174 | func (s *stmt) ColumnConverter(_ int) driver.ValueConverter { 175 | return spindb.GlobalParameterConverter 176 | } 177 | 178 | type result struct{} 179 | 180 | func (r result) LastInsertId() (int64, error) { 181 | return -1, errors.New("LastInsertId is unsupported by this driver") 182 | } 183 | 184 | func (r result) RowsAffected() (int64, error) { 185 | return -1, errors.New("RowsAffected is unsupported by this driver") 186 | } 187 | -------------------------------------------------------------------------------- /http/outbound_internals.go: -------------------------------------------------------------------------------- 1 | //nolint:staticcheck 2 | 3 | // This file contains the manual conversions between Go HTTP objects 4 | // and Spin HTTP objects, through the auto-generated C bindings for 5 | // the outbound HTTP API. 6 | 7 | package http 8 | 9 | // #cgo CFLAGS: -Wno-unused-parameter -Wno-switch-bool 10 | // #include "wasi-outbound-http.h" 11 | // #include 12 | import "C" 13 | 14 | import ( 15 | "bytes" 16 | "fmt" 17 | "io" 18 | "io/ioutil" 19 | "net/http" 20 | "strings" 21 | "unsafe" 22 | ) 23 | 24 | func get(url string) (*http.Response, error) { 25 | req, err := http.NewRequest("GET", url, nil) 26 | if err != nil { 27 | return nil, err 28 | } 29 | 30 | return send(req) 31 | } 32 | 33 | func post(url string, contentType string, body io.Reader) (*http.Response, error) { 34 | req, err := http.NewRequest("POST", url, body) 35 | if err != nil { 36 | return nil, err 37 | } 38 | req.Header.Set("Content-Type", contentType) 39 | 40 | return send(req) 41 | } 42 | 43 | func send(req *http.Request) (*http.Response, error) { 44 | var spinReq C.wasi_outbound_http_request_t 45 | var spinRes C.wasi_outbound_http_response_t 46 | 47 | m, err := method(req.Method) 48 | if err != nil { 49 | return nil, err 50 | } 51 | spinReq.method = uint8(m) 52 | spinReq.uri = C.wasi_outbound_http_uri_t{ 53 | ptr: C.CString(req.URL.String()), 54 | len: C.ulong(len(req.URL.String())), 55 | } 56 | spinReq.headers = toOutboundHeaders(req.Header) 57 | spinReq.body, err = toOutboundReqBody(req.Body) 58 | if err != nil { 59 | return nil, err 60 | } 61 | 62 | code := C.wasi_outbound_http_request(&spinReq, &spinRes) 63 | 64 | if err := toErr(code, req.URL.String()); err != nil { 65 | return nil, err 66 | } 67 | return toResponse(&spinRes) 68 | } 69 | 70 | func method(m string) (int, error) { 71 | switch strings.ToUpper(m) { 72 | case "GET": 73 | return 0, nil 74 | case "POST": 75 | return 1, nil 76 | case "PUT": 77 | return 2, nil 78 | case "DELETE": 79 | return 3, nil 80 | case "PATCH": 81 | return 4, nil 82 | case "HEAD": 83 | return 5, nil 84 | case "OPTIONS": 85 | return 6, nil 86 | default: 87 | return -1, fmt.Errorf("Unknown HTTP method %v", m) 88 | } 89 | } 90 | 91 | // Transform a C outbound HTTP response to a Go *http.Response. 92 | func toResponse(res *C.wasi_outbound_http_response_t) (*http.Response, error) { 93 | var body []byte 94 | if res.body.is_some { 95 | body = C.GoBytes(unsafe.Pointer(res.body.val.ptr), C.int(res.body.val.len)) 96 | } 97 | 98 | t := &http.Response{ 99 | Status: fmt.Sprintf("%v %v", res.status, http.StatusText(int(res.status))), 100 | StatusCode: int(res.status), 101 | Proto: "HTTP/1.1", 102 | ProtoMajor: 1, 103 | ProtoMinor: 1, 104 | Body: ioutil.NopCloser(bytes.NewBuffer(body)), 105 | ContentLength: int64(len(body)), 106 | Request: nil, // we don't really have a request to populate with here 107 | Header: toHeaders(&res.headers), 108 | } 109 | return t, nil 110 | } 111 | 112 | func toOutboundHeaders(hm http.Header) C.wasi_outbound_http_headers_t { 113 | var reqHeaders C.wasi_outbound_http_headers_t 114 | 115 | headersLen := len(hm) 116 | 117 | if headersLen > 0 { 118 | reqHeaders.len = C.ulong(headersLen) 119 | var x C.wasi_outbound_http_tuple2_string_string_t 120 | reqHeaders.ptr = (*C.wasi_outbound_http_tuple2_string_string_t)(C.malloc(C.size_t(headersLen) * C.size_t(unsafe.Sizeof(x)))) 121 | headers := unsafe.Slice(reqHeaders.ptr, headersLen) 122 | 123 | idx := 0 124 | for k, v := range hm { 125 | headers[idx] = newOutboundHeader(k, v[0]) 126 | idx++ 127 | } 128 | } 129 | return reqHeaders 130 | } 131 | 132 | func toOutboundReqBody(body io.Reader) (C.wasi_outbound_http_option_body_t, error) { 133 | var spinBody C.wasi_outbound_http_option_body_t 134 | spinBody.is_some = false 135 | 136 | if body != nil { 137 | buf := new(bytes.Buffer) 138 | len, err := buf.ReadFrom(body) 139 | if err != nil { 140 | return spinBody, err 141 | } 142 | 143 | if len > 0 { 144 | spinBody.is_some = true 145 | spinBody.val = C.wasi_outbound_http_body_t{ 146 | ptr: &buf.Bytes()[0], 147 | len: C.size_t(len), 148 | } 149 | } 150 | } 151 | 152 | return spinBody, nil 153 | } 154 | 155 | func toHeaders(hm *C.wasi_outbound_http_option_headers_t) http.Header { 156 | if !hm.is_some { 157 | return make(map[string][]string, 0) 158 | } 159 | headersLen := int(hm.val.len) 160 | headers := make(http.Header, headersLen) 161 | 162 | var headersArr *C.wasi_outbound_http_tuple2_string_string_t = hm.val.ptr 163 | headersSlice := unsafe.Slice(headersArr, headersLen) 164 | for i := 0; i < headersLen; i++ { 165 | tuple := headersSlice[i] 166 | k := C.GoStringN(tuple.f0.ptr, C.int(tuple.f0.len)) 167 | v := C.GoStringN(tuple.f1.ptr, C.int(tuple.f1.len)) 168 | 169 | headers.Add(k, v) 170 | } 171 | 172 | return headers 173 | } 174 | 175 | func toErr(code C.uint8_t, url string) error { 176 | switch code { 177 | case 1: 178 | return fmt.Errorf("Destination not allowed: %v", url) 179 | case 2: 180 | return fmt.Errorf("Invalid URL: %v", url) 181 | case 3: 182 | return fmt.Errorf("Error sending request to URL: %v", url) 183 | case 4: 184 | return fmt.Errorf("Runtime error") 185 | default: 186 | return nil 187 | } 188 | } 189 | 190 | // newOutboundHeader creates a new outboundHeader with the given key/value. 191 | func newOutboundHeader(k, v string) C.wasi_outbound_http_tuple2_string_string_t { 192 | return C.wasi_outbound_http_tuple2_string_string_t{ 193 | f0: C.wasi_outbound_http_string_t{ptr: C.CString(k), len: C.size_t(len(k))}, 194 | f1: C.wasi_outbound_http_string_t{ptr: C.CString(v), len: C.size_t(len(v))}, 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /http/wasi-outbound-http.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | __attribute__((weak, export_name("canonical_abi_realloc"))) 5 | void *canonical_abi_realloc( 6 | void *ptr, 7 | size_t orig_size, 8 | size_t align, 9 | size_t new_size 10 | ) { 11 | if (new_size == 0) 12 | return (void*) align; 13 | void *ret = realloc(ptr, new_size); 14 | if (!ret) 15 | abort(); 16 | return ret; 17 | } 18 | 19 | __attribute__((weak, export_name("canonical_abi_free"))) 20 | void canonical_abi_free( 21 | void *ptr, 22 | size_t size, 23 | size_t align 24 | ) { 25 | if (size == 0) 26 | return; 27 | free(ptr); 28 | } 29 | #include 30 | 31 | void wasi_outbound_http_string_set(wasi_outbound_http_string_t *ret, const char *s) { 32 | ret->ptr = (char*) s; 33 | ret->len = strlen(s); 34 | } 35 | 36 | void wasi_outbound_http_string_dup(wasi_outbound_http_string_t *ret, const char *s) { 37 | ret->len = strlen(s); 38 | ret->ptr = canonical_abi_realloc(NULL, 0, 1, ret->len); 39 | memcpy(ret->ptr, s, ret->len); 40 | } 41 | 42 | void wasi_outbound_http_string_free(wasi_outbound_http_string_t *ret) { 43 | canonical_abi_free(ret->ptr, ret->len, 1); 44 | ret->ptr = NULL; 45 | ret->len = 0; 46 | } 47 | void wasi_outbound_http_body_free(wasi_outbound_http_body_t *ptr) { 48 | canonical_abi_free(ptr->ptr, ptr->len * 1, 1); 49 | } 50 | void wasi_outbound_http_tuple2_string_string_free(wasi_outbound_http_tuple2_string_string_t *ptr) { 51 | wasi_outbound_http_string_free(&ptr->f0); 52 | wasi_outbound_http_string_free(&ptr->f1); 53 | } 54 | void wasi_outbound_http_headers_free(wasi_outbound_http_headers_t *ptr) { 55 | for (size_t i = 0; i < ptr->len; i++) { 56 | wasi_outbound_http_tuple2_string_string_free(&ptr->ptr[i]); 57 | } 58 | canonical_abi_free(ptr->ptr, ptr->len * 16, 4); 59 | } 60 | void wasi_outbound_http_params_free(wasi_outbound_http_params_t *ptr) { 61 | for (size_t i = 0; i < ptr->len; i++) { 62 | wasi_outbound_http_tuple2_string_string_free(&ptr->ptr[i]); 63 | } 64 | canonical_abi_free(ptr->ptr, ptr->len * 16, 4); 65 | } 66 | void wasi_outbound_http_uri_free(wasi_outbound_http_uri_t *ptr) { 67 | wasi_outbound_http_string_free(ptr); 68 | } 69 | void wasi_outbound_http_option_body_free(wasi_outbound_http_option_body_t *ptr) { 70 | if (ptr->is_some) { 71 | wasi_outbound_http_body_free(&ptr->val); 72 | } 73 | } 74 | void wasi_outbound_http_request_free(wasi_outbound_http_request_t *ptr) { 75 | wasi_outbound_http_uri_free(&ptr->uri); 76 | wasi_outbound_http_headers_free(&ptr->headers); 77 | wasi_outbound_http_params_free(&ptr->params); 78 | wasi_outbound_http_option_body_free(&ptr->body); 79 | } 80 | void wasi_outbound_http_option_headers_free(wasi_outbound_http_option_headers_t *ptr) { 81 | if (ptr->is_some) { 82 | wasi_outbound_http_headers_free(&ptr->val); 83 | } 84 | } 85 | void wasi_outbound_http_response_free(wasi_outbound_http_response_t *ptr) { 86 | wasi_outbound_http_option_headers_free(&ptr->headers); 87 | wasi_outbound_http_option_body_free(&ptr->body); 88 | } 89 | typedef struct { 90 | bool is_err; 91 | union { 92 | wasi_outbound_http_response_t ok; 93 | wasi_outbound_http_http_error_t err; 94 | } val; 95 | } wasi_outbound_http_expected_response_http_error_t; 96 | 97 | __attribute__((aligned(4))) 98 | static uint8_t RET_AREA[32]; 99 | __attribute__((import_module("wasi-outbound-http"), import_name("request"))) 100 | void __wasm_import_wasi_outbound_http_request(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t); 101 | wasi_outbound_http_http_error_t wasi_outbound_http_request(wasi_outbound_http_request_t *req, wasi_outbound_http_response_t *ret0) { 102 | int32_t option; 103 | int32_t option1; 104 | int32_t option2; 105 | 106 | if (((*req).body).is_some) { 107 | const wasi_outbound_http_body_t *payload0 = &((*req).body).val; 108 | option = 1; 109 | option1 = (int32_t) (*payload0).ptr; 110 | option2 = (int32_t) (*payload0).len; 111 | 112 | } else { 113 | option = 0; 114 | option1 = 0; 115 | option2 = 0; 116 | 117 | } 118 | int32_t ptr = (int32_t) &RET_AREA; 119 | __wasm_import_wasi_outbound_http_request((int32_t) (*req).method, (int32_t) ((*req).uri).ptr, (int32_t) ((*req).uri).len, (int32_t) ((*req).headers).ptr, (int32_t) ((*req).headers).len, (int32_t) ((*req).params).ptr, (int32_t) ((*req).params).len, option, option1, option2, ptr); 120 | wasi_outbound_http_expected_response_http_error_t expected; 121 | switch ((int32_t) (*((uint8_t*) (ptr + 0)))) { 122 | case 0: { 123 | expected.is_err = false; 124 | wasi_outbound_http_option_headers_t option3; 125 | switch ((int32_t) (*((uint8_t*) (ptr + 8)))) { 126 | case 0: { 127 | option3.is_some = false; 128 | 129 | break; 130 | } 131 | case 1: { 132 | option3.is_some = true; 133 | 134 | option3.val = (wasi_outbound_http_headers_t) { (wasi_outbound_http_tuple2_string_string_t*)(*((int32_t*) (ptr + 12))), (size_t)(*((int32_t*) (ptr + 16))) }; 135 | break; 136 | } 137 | }wasi_outbound_http_option_body_t option4; 138 | switch ((int32_t) (*((uint8_t*) (ptr + 20)))) { 139 | case 0: { 140 | option4.is_some = false; 141 | 142 | break; 143 | } 144 | case 1: { 145 | option4.is_some = true; 146 | 147 | option4.val = (wasi_outbound_http_body_t) { (uint8_t*)(*((int32_t*) (ptr + 24))), (size_t)(*((int32_t*) (ptr + 28))) }; 148 | break; 149 | } 150 | } 151 | expected.val.ok = (wasi_outbound_http_response_t) { 152 | (uint16_t) ((int32_t) (*((uint16_t*) (ptr + 4)))), 153 | option3, 154 | option4, 155 | }; 156 | break; 157 | } 158 | case 1: { 159 | expected.is_err = true; 160 | 161 | expected.val.err = (int32_t) (*((uint8_t*) (ptr + 4))); 162 | break; 163 | } 164 | }*ret0 = expected.val.ok; 165 | return expected.is_err ? expected.val.err : -1; 166 | } 167 | -------------------------------------------------------------------------------- /sqlite/sqlite.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | __attribute__((weak, export_name("canonical_abi_realloc"))) 5 | void *canonical_abi_realloc( 6 | void *ptr, 7 | size_t orig_size, 8 | size_t align, 9 | size_t new_size 10 | ) { 11 | if (new_size == 0) 12 | return (void*) align; 13 | void *ret = realloc(ptr, new_size); 14 | if (!ret) 15 | abort(); 16 | return ret; 17 | } 18 | 19 | __attribute__((weak, export_name("canonical_abi_free"))) 20 | void canonical_abi_free( 21 | void *ptr, 22 | size_t size, 23 | size_t align 24 | ) { 25 | if (size == 0) 26 | return; 27 | free(ptr); 28 | } 29 | #include 30 | 31 | void sqlite_string_set(sqlite_string_t *ret, const char *s) { 32 | ret->ptr = (char*) s; 33 | ret->len = strlen(s); 34 | } 35 | 36 | void sqlite_string_dup(sqlite_string_t *ret, const char *s) { 37 | ret->len = strlen(s); 38 | ret->ptr = canonical_abi_realloc(NULL, 0, 1, ret->len); 39 | memcpy(ret->ptr, s, ret->len); 40 | } 41 | 42 | void sqlite_string_free(sqlite_string_t *ret) { 43 | canonical_abi_free(ret->ptr, ret->len, 1); 44 | ret->ptr = NULL; 45 | ret->len = 0; 46 | } 47 | void sqlite_error_free(sqlite_error_t *ptr) { 48 | switch ((int32_t) ptr->tag) { 49 | case 4: { 50 | sqlite_string_free(&ptr->val.io); 51 | break; 52 | } 53 | } 54 | } 55 | void sqlite_list_string_free(sqlite_list_string_t *ptr) { 56 | for (size_t i = 0; i < ptr->len; i++) { 57 | sqlite_string_free(&ptr->ptr[i]); 58 | } 59 | canonical_abi_free(ptr->ptr, ptr->len * 8, 4); 60 | } 61 | void sqlite_list_u8_free(sqlite_list_u8_t *ptr) { 62 | canonical_abi_free(ptr->ptr, ptr->len * 1, 1); 63 | } 64 | void sqlite_value_free(sqlite_value_t *ptr) { 65 | switch ((int32_t) ptr->tag) { 66 | case 2: { 67 | sqlite_string_free(&ptr->val.text); 68 | break; 69 | } 70 | case 3: { 71 | sqlite_list_u8_free(&ptr->val.blob); 72 | break; 73 | } 74 | } 75 | } 76 | void sqlite_list_value_free(sqlite_list_value_t *ptr) { 77 | for (size_t i = 0; i < ptr->len; i++) { 78 | sqlite_value_free(&ptr->ptr[i]); 79 | } 80 | canonical_abi_free(ptr->ptr, ptr->len * 16, 8); 81 | } 82 | void sqlite_row_result_free(sqlite_row_result_t *ptr) { 83 | sqlite_list_value_free(&ptr->values); 84 | } 85 | void sqlite_list_row_result_free(sqlite_list_row_result_t *ptr) { 86 | for (size_t i = 0; i < ptr->len; i++) { 87 | sqlite_row_result_free(&ptr->ptr[i]); 88 | } 89 | canonical_abi_free(ptr->ptr, ptr->len * 8, 4); 90 | } 91 | void sqlite_query_result_free(sqlite_query_result_t *ptr) { 92 | sqlite_list_string_free(&ptr->columns); 93 | sqlite_list_row_result_free(&ptr->rows); 94 | } 95 | void sqlite_expected_connection_error_free(sqlite_expected_connection_error_t *ptr) { 96 | if (!ptr->is_err) { 97 | } else { 98 | sqlite_error_free(&ptr->val.err); 99 | } 100 | } 101 | void sqlite_expected_query_result_error_free(sqlite_expected_query_result_error_t *ptr) { 102 | if (!ptr->is_err) { 103 | sqlite_query_result_free(&ptr->val.ok); 104 | } else { 105 | sqlite_error_free(&ptr->val.err); 106 | } 107 | } 108 | 109 | __attribute__((aligned(4))) 110 | static uint8_t RET_AREA[20]; 111 | __attribute__((import_module("sqlite"), import_name("open"))) 112 | void __wasm_import_sqlite_open(int32_t, int32_t, int32_t); 113 | void sqlite_open(sqlite_string_t *name, sqlite_expected_connection_error_t *ret0) { 114 | int32_t ptr = (int32_t) &RET_AREA; 115 | __wasm_import_sqlite_open((int32_t) (*name).ptr, (int32_t) (*name).len, ptr); 116 | sqlite_expected_connection_error_t expected; 117 | switch ((int32_t) (*((uint8_t*) (ptr + 0)))) { 118 | case 0: { 119 | expected.is_err = false; 120 | 121 | expected.val.ok = (uint32_t) (*((int32_t*) (ptr + 4))); 122 | break; 123 | } 124 | case 1: { 125 | expected.is_err = true; 126 | sqlite_error_t variant; 127 | variant.tag = (int32_t) (*((uint8_t*) (ptr + 4))); 128 | switch ((int32_t) variant.tag) { 129 | case 0: { 130 | break; 131 | } 132 | case 1: { 133 | break; 134 | } 135 | case 2: { 136 | break; 137 | } 138 | case 3: { 139 | break; 140 | } 141 | case 4: { 142 | variant.val.io = (sqlite_string_t) { (char*)(*((int32_t*) (ptr + 8))), (size_t)(*((int32_t*) (ptr + 12))) }; 143 | break; 144 | } 145 | } 146 | 147 | expected.val.err = variant; 148 | break; 149 | } 150 | }*ret0 = expected; 151 | } 152 | __attribute__((import_module("sqlite"), import_name("execute"))) 153 | void __wasm_import_sqlite_execute(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t); 154 | void sqlite_execute(sqlite_connection_t conn, sqlite_string_t *statement, sqlite_list_value_t *parameters, sqlite_expected_query_result_error_t *ret0) { 155 | int32_t ptr = (int32_t) &RET_AREA; 156 | __wasm_import_sqlite_execute((int32_t) (conn), (int32_t) (*statement).ptr, (int32_t) (*statement).len, (int32_t) (*parameters).ptr, (int32_t) (*parameters).len, ptr); 157 | sqlite_expected_query_result_error_t expected; 158 | switch ((int32_t) (*((uint8_t*) (ptr + 0)))) { 159 | case 0: { 160 | expected.is_err = false; 161 | 162 | expected.val.ok = (sqlite_query_result_t) { 163 | (sqlite_list_string_t) { (sqlite_string_t*)(*((int32_t*) (ptr + 4))), (size_t)(*((int32_t*) (ptr + 8))) }, 164 | (sqlite_list_row_result_t) { (sqlite_row_result_t*)(*((int32_t*) (ptr + 12))), (size_t)(*((int32_t*) (ptr + 16))) }, 165 | }; 166 | break; 167 | } 168 | case 1: { 169 | expected.is_err = true; 170 | sqlite_error_t variant4; 171 | variant4.tag = (int32_t) (*((uint8_t*) (ptr + 4))); 172 | switch ((int32_t) variant4.tag) { 173 | case 0: { 174 | break; 175 | } 176 | case 1: { 177 | break; 178 | } 179 | case 2: { 180 | break; 181 | } 182 | case 3: { 183 | break; 184 | } 185 | case 4: { 186 | variant4.val.io = (sqlite_string_t) { (char*)(*((int32_t*) (ptr + 8))), (size_t)(*((int32_t*) (ptr + 12))) }; 187 | break; 188 | } 189 | } 190 | 191 | expected.val.err = variant4; 192 | break; 193 | } 194 | }*ret0 = expected; 195 | } 196 | __attribute__((import_module("sqlite"), import_name("close"))) 197 | void __wasm_import_sqlite_close(int32_t); 198 | void sqlite_close(sqlite_connection_t conn) { 199 | __wasm_import_sqlite_close((int32_t) (conn)); 200 | } 201 | -------------------------------------------------------------------------------- /pg/outbound-pg.h: -------------------------------------------------------------------------------- 1 | #ifndef __BINDINGS_OUTBOUND_PG_H 2 | #define __BINDINGS_OUTBOUND_PG_H 3 | #ifdef __cplusplus 4 | extern "C" 5 | { 6 | #endif 7 | 8 | #include 9 | #include 10 | 11 | typedef struct { 12 | char *ptr; 13 | size_t len; 14 | } outbound_pg_string_t; 15 | 16 | void outbound_pg_string_set(outbound_pg_string_t *ret, const char *s); 17 | void outbound_pg_string_dup(outbound_pg_string_t *ret, const char *s); 18 | void outbound_pg_string_free(outbound_pg_string_t *ret); 19 | typedef struct { 20 | uint8_t tag; 21 | union { 22 | outbound_pg_string_t connection_failed; 23 | outbound_pg_string_t bad_parameter; 24 | outbound_pg_string_t query_failed; 25 | outbound_pg_string_t value_conversion_failed; 26 | outbound_pg_string_t other_error; 27 | } val; 28 | } outbound_pg_pg_error_t; 29 | #define OUTBOUND_PG_PG_ERROR_SUCCESS 0 30 | #define OUTBOUND_PG_PG_ERROR_CONNECTION_FAILED 1 31 | #define OUTBOUND_PG_PG_ERROR_BAD_PARAMETER 2 32 | #define OUTBOUND_PG_PG_ERROR_QUERY_FAILED 3 33 | #define OUTBOUND_PG_PG_ERROR_VALUE_CONVERSION_FAILED 4 34 | #define OUTBOUND_PG_PG_ERROR_OTHER_ERROR 5 35 | void outbound_pg_pg_error_free(outbound_pg_pg_error_t *ptr); 36 | typedef uint8_t outbound_pg_db_data_type_t; 37 | #define OUTBOUND_PG_DB_DATA_TYPE_BOOLEAN 0 38 | #define OUTBOUND_PG_DB_DATA_TYPE_INT8 1 39 | #define OUTBOUND_PG_DB_DATA_TYPE_INT16 2 40 | #define OUTBOUND_PG_DB_DATA_TYPE_INT32 3 41 | #define OUTBOUND_PG_DB_DATA_TYPE_INT64 4 42 | #define OUTBOUND_PG_DB_DATA_TYPE_UINT8 5 43 | #define OUTBOUND_PG_DB_DATA_TYPE_UINT16 6 44 | #define OUTBOUND_PG_DB_DATA_TYPE_UINT32 7 45 | #define OUTBOUND_PG_DB_DATA_TYPE_UINT64 8 46 | #define OUTBOUND_PG_DB_DATA_TYPE_FLOATING32 9 47 | #define OUTBOUND_PG_DB_DATA_TYPE_FLOATING64 10 48 | #define OUTBOUND_PG_DB_DATA_TYPE_STR 11 49 | #define OUTBOUND_PG_DB_DATA_TYPE_BINARY 12 50 | #define OUTBOUND_PG_DB_DATA_TYPE_OTHER 13 51 | typedef struct { 52 | outbound_pg_string_t name; 53 | outbound_pg_db_data_type_t data_type; 54 | } outbound_pg_column_t; 55 | void outbound_pg_column_free(outbound_pg_column_t *ptr); 56 | typedef struct { 57 | uint8_t *ptr; 58 | size_t len; 59 | } outbound_pg_list_u8_t; 60 | void outbound_pg_list_u8_free(outbound_pg_list_u8_t *ptr); 61 | typedef struct { 62 | uint8_t tag; 63 | union { 64 | bool boolean; 65 | int8_t int8; 66 | int16_t int16; 67 | int32_t int32; 68 | int64_t int64; 69 | uint8_t uint8; 70 | uint16_t uint16; 71 | uint32_t uint32; 72 | uint64_t uint64; 73 | float floating32; 74 | double floating64; 75 | outbound_pg_string_t str; 76 | outbound_pg_list_u8_t binary; 77 | } val; 78 | } outbound_pg_db_value_t; 79 | #define OUTBOUND_PG_DB_VALUE_BOOLEAN 0 80 | #define OUTBOUND_PG_DB_VALUE_INT8 1 81 | #define OUTBOUND_PG_DB_VALUE_INT16 2 82 | #define OUTBOUND_PG_DB_VALUE_INT32 3 83 | #define OUTBOUND_PG_DB_VALUE_INT64 4 84 | #define OUTBOUND_PG_DB_VALUE_UINT8 5 85 | #define OUTBOUND_PG_DB_VALUE_UINT16 6 86 | #define OUTBOUND_PG_DB_VALUE_UINT32 7 87 | #define OUTBOUND_PG_DB_VALUE_UINT64 8 88 | #define OUTBOUND_PG_DB_VALUE_FLOATING32 9 89 | #define OUTBOUND_PG_DB_VALUE_FLOATING64 10 90 | #define OUTBOUND_PG_DB_VALUE_STR 11 91 | #define OUTBOUND_PG_DB_VALUE_BINARY 12 92 | #define OUTBOUND_PG_DB_VALUE_DB_NULL 13 93 | #define OUTBOUND_PG_DB_VALUE_UNSUPPORTED 14 94 | void outbound_pg_db_value_free(outbound_pg_db_value_t *ptr); 95 | typedef struct { 96 | uint8_t tag; 97 | union { 98 | bool boolean; 99 | int8_t int8; 100 | int16_t int16; 101 | int32_t int32; 102 | int64_t int64; 103 | uint8_t uint8; 104 | uint16_t uint16; 105 | uint32_t uint32; 106 | uint64_t uint64; 107 | float floating32; 108 | double floating64; 109 | outbound_pg_string_t str; 110 | outbound_pg_list_u8_t binary; 111 | } val; 112 | } outbound_pg_parameter_value_t; 113 | #define OUTBOUND_PG_PARAMETER_VALUE_BOOLEAN 0 114 | #define OUTBOUND_PG_PARAMETER_VALUE_INT8 1 115 | #define OUTBOUND_PG_PARAMETER_VALUE_INT16 2 116 | #define OUTBOUND_PG_PARAMETER_VALUE_INT32 3 117 | #define OUTBOUND_PG_PARAMETER_VALUE_INT64 4 118 | #define OUTBOUND_PG_PARAMETER_VALUE_UINT8 5 119 | #define OUTBOUND_PG_PARAMETER_VALUE_UINT16 6 120 | #define OUTBOUND_PG_PARAMETER_VALUE_UINT32 7 121 | #define OUTBOUND_PG_PARAMETER_VALUE_UINT64 8 122 | #define OUTBOUND_PG_PARAMETER_VALUE_FLOATING32 9 123 | #define OUTBOUND_PG_PARAMETER_VALUE_FLOATING64 10 124 | #define OUTBOUND_PG_PARAMETER_VALUE_STR 11 125 | #define OUTBOUND_PG_PARAMETER_VALUE_BINARY 12 126 | #define OUTBOUND_PG_PARAMETER_VALUE_DB_NULL 13 127 | void outbound_pg_parameter_value_free(outbound_pg_parameter_value_t *ptr); 128 | typedef struct { 129 | outbound_pg_db_value_t *ptr; 130 | size_t len; 131 | } outbound_pg_row_t; 132 | void outbound_pg_row_free(outbound_pg_row_t *ptr); 133 | typedef struct { 134 | outbound_pg_column_t *ptr; 135 | size_t len; 136 | } outbound_pg_list_column_t; 137 | void outbound_pg_list_column_free(outbound_pg_list_column_t *ptr); 138 | typedef struct { 139 | outbound_pg_row_t *ptr; 140 | size_t len; 141 | } outbound_pg_list_row_t; 142 | void outbound_pg_list_row_free(outbound_pg_list_row_t *ptr); 143 | typedef struct { 144 | outbound_pg_list_column_t columns; 145 | outbound_pg_list_row_t rows; 146 | } outbound_pg_row_set_t; 147 | void outbound_pg_row_set_free(outbound_pg_row_set_t *ptr); 148 | typedef struct { 149 | outbound_pg_parameter_value_t *ptr; 150 | size_t len; 151 | } outbound_pg_list_parameter_value_t; 152 | void outbound_pg_list_parameter_value_free(outbound_pg_list_parameter_value_t *ptr); 153 | typedef struct { 154 | bool is_err; 155 | union { 156 | outbound_pg_row_set_t ok; 157 | outbound_pg_pg_error_t err; 158 | } val; 159 | } outbound_pg_expected_row_set_pg_error_t; 160 | void outbound_pg_expected_row_set_pg_error_free(outbound_pg_expected_row_set_pg_error_t *ptr); 161 | typedef struct { 162 | bool is_err; 163 | union { 164 | uint64_t ok; 165 | outbound_pg_pg_error_t err; 166 | } val; 167 | } outbound_pg_expected_u64_pg_error_t; 168 | void outbound_pg_expected_u64_pg_error_free(outbound_pg_expected_u64_pg_error_t *ptr); 169 | void outbound_pg_query(outbound_pg_string_t *address, outbound_pg_string_t *statement, outbound_pg_list_parameter_value_t *params, outbound_pg_expected_row_set_pg_error_t *ret0); 170 | void outbound_pg_execute(outbound_pg_string_t *address, outbound_pg_string_t *statement, outbound_pg_list_parameter_value_t *params, outbound_pg_expected_u64_pg_error_t *ret0); 171 | #ifdef __cplusplus 172 | } 173 | #endif 174 | #endif 175 | -------------------------------------------------------------------------------- /mysql/outbound-mysql.h: -------------------------------------------------------------------------------- 1 | #ifndef __BINDINGS_OUTBOUND_MYSQL_H 2 | #define __BINDINGS_OUTBOUND_MYSQL_H 3 | #ifdef __cplusplus 4 | extern "C" { 5 | #endif 6 | 7 | #include 8 | #include 9 | 10 | typedef struct { 11 | char *ptr; 12 | size_t len; 13 | } outbound_mysql_string_t; 14 | 15 | void outbound_mysql_string_set(outbound_mysql_string_t *ret, const char *s); 16 | void outbound_mysql_string_dup(outbound_mysql_string_t *ret, const char *s); 17 | void outbound_mysql_string_free(outbound_mysql_string_t *ret); 18 | typedef struct { 19 | uint8_t tag; 20 | union { 21 | outbound_mysql_string_t connection_failed; 22 | outbound_mysql_string_t bad_parameter; 23 | outbound_mysql_string_t query_failed; 24 | outbound_mysql_string_t value_conversion_failed; 25 | outbound_mysql_string_t other_error; 26 | } val; 27 | } outbound_mysql_mysql_error_t; 28 | #define OUTBOUND_MYSQL_MYSQL_ERROR_SUCCESS 0 29 | #define OUTBOUND_MYSQL_MYSQL_ERROR_CONNECTION_FAILED 1 30 | #define OUTBOUND_MYSQL_MYSQL_ERROR_BAD_PARAMETER 2 31 | #define OUTBOUND_MYSQL_MYSQL_ERROR_QUERY_FAILED 3 32 | #define OUTBOUND_MYSQL_MYSQL_ERROR_VALUE_CONVERSION_FAILED 4 33 | #define OUTBOUND_MYSQL_MYSQL_ERROR_OTHER_ERROR 5 34 | void outbound_mysql_mysql_error_free(outbound_mysql_mysql_error_t *ptr); 35 | typedef uint8_t outbound_mysql_db_data_type_t; 36 | #define OUTBOUND_MYSQL_DB_DATA_TYPE_BOOLEAN 0 37 | #define OUTBOUND_MYSQL_DB_DATA_TYPE_INT8 1 38 | #define OUTBOUND_MYSQL_DB_DATA_TYPE_INT16 2 39 | #define OUTBOUND_MYSQL_DB_DATA_TYPE_INT32 3 40 | #define OUTBOUND_MYSQL_DB_DATA_TYPE_INT64 4 41 | #define OUTBOUND_MYSQL_DB_DATA_TYPE_UINT8 5 42 | #define OUTBOUND_MYSQL_DB_DATA_TYPE_UINT16 6 43 | #define OUTBOUND_MYSQL_DB_DATA_TYPE_UINT32 7 44 | #define OUTBOUND_MYSQL_DB_DATA_TYPE_UINT64 8 45 | #define OUTBOUND_MYSQL_DB_DATA_TYPE_FLOATING32 9 46 | #define OUTBOUND_MYSQL_DB_DATA_TYPE_FLOATING64 10 47 | #define OUTBOUND_MYSQL_DB_DATA_TYPE_STR 11 48 | #define OUTBOUND_MYSQL_DB_DATA_TYPE_BINARY 12 49 | #define OUTBOUND_MYSQL_DB_DATA_TYPE_OTHER 13 50 | typedef struct { 51 | outbound_mysql_string_t name; 52 | outbound_mysql_db_data_type_t data_type; 53 | } outbound_mysql_column_t; 54 | void outbound_mysql_column_free(outbound_mysql_column_t *ptr); 55 | typedef struct { 56 | uint8_t *ptr; 57 | size_t len; 58 | } outbound_mysql_list_u8_t; 59 | void outbound_mysql_list_u8_free(outbound_mysql_list_u8_t *ptr); 60 | typedef struct { 61 | uint8_t tag; 62 | union { 63 | bool boolean; 64 | int8_t int8; 65 | int16_t int16; 66 | int32_t int32; 67 | int64_t int64; 68 | uint8_t uint8; 69 | uint16_t uint16; 70 | uint32_t uint32; 71 | uint64_t uint64; 72 | float floating32; 73 | double floating64; 74 | outbound_mysql_string_t str; 75 | outbound_mysql_list_u8_t binary; 76 | } val; 77 | } outbound_mysql_db_value_t; 78 | #define OUTBOUND_MYSQL_DB_VALUE_BOOLEAN 0 79 | #define OUTBOUND_MYSQL_DB_VALUE_INT8 1 80 | #define OUTBOUND_MYSQL_DB_VALUE_INT16 2 81 | #define OUTBOUND_MYSQL_DB_VALUE_INT32 3 82 | #define OUTBOUND_MYSQL_DB_VALUE_INT64 4 83 | #define OUTBOUND_MYSQL_DB_VALUE_UINT8 5 84 | #define OUTBOUND_MYSQL_DB_VALUE_UINT16 6 85 | #define OUTBOUND_MYSQL_DB_VALUE_UINT32 7 86 | #define OUTBOUND_MYSQL_DB_VALUE_UINT64 8 87 | #define OUTBOUND_MYSQL_DB_VALUE_FLOATING32 9 88 | #define OUTBOUND_MYSQL_DB_VALUE_FLOATING64 10 89 | #define OUTBOUND_MYSQL_DB_VALUE_STR 11 90 | #define OUTBOUND_MYSQL_DB_VALUE_BINARY 12 91 | #define OUTBOUND_MYSQL_DB_VALUE_DB_NULL 13 92 | #define OUTBOUND_MYSQL_DB_VALUE_UNSUPPORTED 14 93 | void outbound_mysql_db_value_free(outbound_mysql_db_value_t *ptr); 94 | typedef struct { 95 | uint8_t tag; 96 | union { 97 | bool boolean; 98 | int8_t int8; 99 | int16_t int16; 100 | int32_t int32; 101 | int64_t int64; 102 | uint8_t uint8; 103 | uint16_t uint16; 104 | uint32_t uint32; 105 | uint64_t uint64; 106 | float floating32; 107 | double floating64; 108 | outbound_mysql_string_t str; 109 | outbound_mysql_list_u8_t binary; 110 | } val; 111 | } outbound_mysql_parameter_value_t; 112 | #define OUTBOUND_MYSQL_PARAMETER_VALUE_BOOLEAN 0 113 | #define OUTBOUND_MYSQL_PARAMETER_VALUE_INT8 1 114 | #define OUTBOUND_MYSQL_PARAMETER_VALUE_INT16 2 115 | #define OUTBOUND_MYSQL_PARAMETER_VALUE_INT32 3 116 | #define OUTBOUND_MYSQL_PARAMETER_VALUE_INT64 4 117 | #define OUTBOUND_MYSQL_PARAMETER_VALUE_UINT8 5 118 | #define OUTBOUND_MYSQL_PARAMETER_VALUE_UINT16 6 119 | #define OUTBOUND_MYSQL_PARAMETER_VALUE_UINT32 7 120 | #define OUTBOUND_MYSQL_PARAMETER_VALUE_UINT64 8 121 | #define OUTBOUND_MYSQL_PARAMETER_VALUE_FLOATING32 9 122 | #define OUTBOUND_MYSQL_PARAMETER_VALUE_FLOATING64 10 123 | #define OUTBOUND_MYSQL_PARAMETER_VALUE_STR 11 124 | #define OUTBOUND_MYSQL_PARAMETER_VALUE_BINARY 12 125 | #define OUTBOUND_MYSQL_PARAMETER_VALUE_DB_NULL 13 126 | void outbound_mysql_parameter_value_free(outbound_mysql_parameter_value_t *ptr); 127 | typedef struct { 128 | outbound_mysql_db_value_t *ptr; 129 | size_t len; 130 | } outbound_mysql_row_t; 131 | void outbound_mysql_row_free(outbound_mysql_row_t *ptr); 132 | typedef struct { 133 | outbound_mysql_column_t *ptr; 134 | size_t len; 135 | } outbound_mysql_list_column_t; 136 | void outbound_mysql_list_column_free(outbound_mysql_list_column_t *ptr); 137 | typedef struct { 138 | outbound_mysql_row_t *ptr; 139 | size_t len; 140 | } outbound_mysql_list_row_t; 141 | void outbound_mysql_list_row_free(outbound_mysql_list_row_t *ptr); 142 | typedef struct { 143 | outbound_mysql_list_column_t columns; 144 | outbound_mysql_list_row_t rows; 145 | } outbound_mysql_row_set_t; 146 | void outbound_mysql_row_set_free(outbound_mysql_row_set_t *ptr); 147 | typedef struct { 148 | outbound_mysql_parameter_value_t *ptr; 149 | size_t len; 150 | } outbound_mysql_list_parameter_value_t; 151 | void outbound_mysql_list_parameter_value_free( 152 | outbound_mysql_list_parameter_value_t *ptr); 153 | typedef struct { 154 | bool is_err; 155 | union { 156 | outbound_mysql_row_set_t ok; 157 | outbound_mysql_mysql_error_t err; 158 | } val; 159 | } outbound_mysql_expected_row_set_mysql_error_t; 160 | void outbound_mysql_expected_row_set_mysql_error_free( 161 | outbound_mysql_expected_row_set_mysql_error_t *ptr); 162 | typedef struct { 163 | bool is_err; 164 | union { 165 | outbound_mysql_mysql_error_t err; 166 | } val; 167 | } outbound_mysql_expected_unit_mysql_error_t; 168 | void outbound_mysql_expected_unit_mysql_error_free( 169 | outbound_mysql_expected_unit_mysql_error_t *ptr); 170 | void outbound_mysql_query(outbound_mysql_string_t *address, 171 | outbound_mysql_string_t *statement, 172 | outbound_mysql_list_parameter_value_t *params, 173 | outbound_mysql_expected_row_set_mysql_error_t *ret0); 174 | void outbound_mysql_execute(outbound_mysql_string_t *address, 175 | outbound_mysql_string_t *statement, 176 | outbound_mysql_list_parameter_value_t *params, 177 | outbound_mysql_expected_unit_mysql_error_t *ret0); 178 | #ifdef __cplusplus 179 | } 180 | #endif 181 | #endif 182 | -------------------------------------------------------------------------------- /llm/llm.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | __attribute__((weak, export_name("canonical_abi_realloc"))) 5 | void *canonical_abi_realloc( 6 | void *ptr, 7 | size_t orig_size, 8 | size_t align, 9 | size_t new_size 10 | ) { 11 | if (new_size == 0) 12 | return (void*) align; 13 | void *ret = realloc(ptr, new_size); 14 | if (!ret) 15 | abort(); 16 | return ret; 17 | } 18 | 19 | __attribute__((weak, export_name("canonical_abi_free"))) 20 | void canonical_abi_free( 21 | void *ptr, 22 | size_t size, 23 | size_t align 24 | ) { 25 | if (size == 0) 26 | return; 27 | free(ptr); 28 | } 29 | #include 30 | 31 | void llm_string_set(llm_string_t *ret, const char *s) { 32 | ret->ptr = (char*) s; 33 | ret->len = strlen(s); 34 | } 35 | 36 | void llm_string_dup(llm_string_t *ret, const char *s) { 37 | ret->len = strlen(s); 38 | ret->ptr = canonical_abi_realloc(NULL, 0, 1, ret->len); 39 | memcpy(ret->ptr, s, ret->len); 40 | } 41 | 42 | void llm_string_free(llm_string_t *ret) { 43 | canonical_abi_free(ret->ptr, ret->len, 1); 44 | ret->ptr = NULL; 45 | ret->len = 0; 46 | } 47 | void llm_inferencing_model_free(llm_inferencing_model_t *ptr) { 48 | llm_string_free(ptr); 49 | } 50 | void llm_error_free(llm_error_t *ptr) { 51 | switch ((int32_t) ptr->tag) { 52 | case 1: { 53 | llm_string_free(&ptr->val.runtime_error); 54 | break; 55 | } 56 | case 2: { 57 | llm_string_free(&ptr->val.invalid_input); 58 | break; 59 | } 60 | } 61 | } 62 | void llm_inferencing_result_free(llm_inferencing_result_t *ptr) { 63 | llm_string_free(&ptr->text); 64 | } 65 | void llm_embedding_model_free(llm_embedding_model_t *ptr) { 66 | llm_string_free(ptr); 67 | } 68 | void llm_list_float32_free(llm_list_float32_t *ptr) { 69 | canonical_abi_free(ptr->ptr, ptr->len * 4, 4); 70 | } 71 | void llm_list_list_float32_free(llm_list_list_float32_t *ptr) { 72 | for (size_t i = 0; i < ptr->len; i++) { 73 | llm_list_float32_free(&ptr->ptr[i]); 74 | } 75 | canonical_abi_free(ptr->ptr, ptr->len * 8, 4); 76 | } 77 | void llm_embeddings_result_free(llm_embeddings_result_t *ptr) { 78 | llm_list_list_float32_free(&ptr->embeddings); 79 | } 80 | void llm_expected_inferencing_result_error_free(llm_expected_inferencing_result_error_t *ptr) { 81 | if (!ptr->is_err) { 82 | llm_inferencing_result_free(&ptr->val.ok); 83 | } else { 84 | llm_error_free(&ptr->val.err); 85 | } 86 | } 87 | void llm_list_string_free(llm_list_string_t *ptr) { 88 | for (size_t i = 0; i < ptr->len; i++) { 89 | llm_string_free(&ptr->ptr[i]); 90 | } 91 | canonical_abi_free(ptr->ptr, ptr->len * 8, 4); 92 | } 93 | void llm_expected_embeddings_result_error_free(llm_expected_embeddings_result_error_t *ptr) { 94 | if (!ptr->is_err) { 95 | llm_embeddings_result_free(&ptr->val.ok); 96 | } else { 97 | llm_error_free(&ptr->val.err); 98 | } 99 | } 100 | 101 | __attribute__((aligned(4))) 102 | static uint8_t RET_AREA[20]; 103 | __attribute__((import_module("llm"), import_name("infer"))) 104 | void __wasm_import_llm_infer(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, float, int32_t, float, int32_t, float, int32_t); 105 | void llm_infer(llm_inferencing_model_t *model, llm_string_t *prompt, llm_option_inferencing_params_t *params, llm_expected_inferencing_result_error_t *ret0) { 106 | int32_t option; 107 | int32_t option1; 108 | float option2; 109 | int32_t option3; 110 | float option4; 111 | int32_t option5; 112 | float option6; 113 | 114 | if ((*params).is_some) { 115 | const llm_inferencing_params_t *payload0 = &(*params).val; 116 | option = 1; 117 | option1 = (int32_t) ((*payload0).max_tokens); 118 | option2 = (*payload0).repeat_penalty; 119 | option3 = (int32_t) ((*payload0).repeat_penalty_last_n_token_count); 120 | option4 = (*payload0).temperature; 121 | option5 = (int32_t) ((*payload0).top_k); 122 | option6 = (*payload0).top_p; 123 | 124 | } else { 125 | option = 0; 126 | option1 = 0; 127 | option2 = 0; 128 | option3 = 0; 129 | option4 = 0; 130 | option5 = 0; 131 | option6 = 0; 132 | 133 | } 134 | int32_t ptr = (int32_t) &RET_AREA; 135 | __wasm_import_llm_infer((int32_t) (*model).ptr, (int32_t) (*model).len, (int32_t) (*prompt).ptr, (int32_t) (*prompt).len, option, option1, option2, option3, option4, option5, option6, ptr); 136 | llm_expected_inferencing_result_error_t expected; 137 | switch ((int32_t) (*((uint8_t*) (ptr + 0)))) { 138 | case 0: { 139 | expected.is_err = false; 140 | 141 | expected.val.ok = (llm_inferencing_result_t) { 142 | (llm_string_t) { (char*)(*((int32_t*) (ptr + 4))), (size_t)(*((int32_t*) (ptr + 8))) }, 143 | (llm_inferencing_usage_t) { 144 | (uint32_t) (*((int32_t*) (ptr + 12))), 145 | (uint32_t) (*((int32_t*) (ptr + 16))), 146 | }, 147 | }; 148 | break; 149 | } 150 | case 1: { 151 | expected.is_err = true; 152 | llm_error_t variant; 153 | variant.tag = (int32_t) (*((uint8_t*) (ptr + 4))); 154 | switch ((int32_t) variant.tag) { 155 | case 0: { 156 | break; 157 | } 158 | case 1: { 159 | variant.val.runtime_error = (llm_string_t) { (char*)(*((int32_t*) (ptr + 8))), (size_t)(*((int32_t*) (ptr + 12))) }; 160 | break; 161 | } 162 | case 2: { 163 | variant.val.invalid_input = (llm_string_t) { (char*)(*((int32_t*) (ptr + 8))), (size_t)(*((int32_t*) (ptr + 12))) }; 164 | break; 165 | } 166 | } 167 | 168 | expected.val.err = variant; 169 | break; 170 | } 171 | }*ret0 = expected; 172 | } 173 | __attribute__((import_module("llm"), import_name("generate-embeddings"))) 174 | void __wasm_import_llm_generate_embeddings(int32_t, int32_t, int32_t, int32_t, int32_t); 175 | void llm_generate_embeddings(llm_embedding_model_t *model, llm_list_string_t *text, llm_expected_embeddings_result_error_t *ret0) { 176 | int32_t ptr = (int32_t) &RET_AREA; 177 | __wasm_import_llm_generate_embeddings((int32_t) (*model).ptr, (int32_t) (*model).len, (int32_t) (*text).ptr, (int32_t) (*text).len, ptr); 178 | llm_expected_embeddings_result_error_t expected; 179 | switch ((int32_t) (*((uint8_t*) (ptr + 0)))) { 180 | case 0: { 181 | expected.is_err = false; 182 | 183 | expected.val.ok = (llm_embeddings_result_t) { 184 | (llm_list_list_float32_t) { (llm_list_float32_t*)(*((int32_t*) (ptr + 4))), (size_t)(*((int32_t*) (ptr + 8))) }, 185 | (llm_embeddings_usage_t) { 186 | (uint32_t) (*((int32_t*) (ptr + 12))), 187 | }, 188 | }; 189 | break; 190 | } 191 | case 1: { 192 | expected.is_err = true; 193 | llm_error_t variant; 194 | variant.tag = (int32_t) (*((uint8_t*) (ptr + 4))); 195 | switch ((int32_t) variant.tag) { 196 | case 0: { 197 | break; 198 | } 199 | case 1: { 200 | variant.val.runtime_error = (llm_string_t) { (char*)(*((int32_t*) (ptr + 8))), (size_t)(*((int32_t*) (ptr + 12))) }; 201 | break; 202 | } 203 | case 2: { 204 | variant.val.invalid_input = (llm_string_t) { (char*)(*((int32_t*) (ptr + 8))), (size_t)(*((int32_t*) (ptr + 12))) }; 205 | break; 206 | } 207 | } 208 | 209 | expected.val.err = variant; 210 | break; 211 | } 212 | }*ret0 = expected; 213 | } 214 | -------------------------------------------------------------------------------- /redis/internals.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | // #include "outbound-redis.h" 4 | // #include "spin-redis.h" 5 | // #include 6 | import "C" 7 | import ( 8 | "errors" 9 | "fmt" 10 | "unsafe" 11 | ) 12 | 13 | // argumentKind represents a type of a argument for executing a Redis command. 14 | type argumentKind uint8 15 | 16 | const ( 17 | argumentKindInt argumentKind = iota 18 | argumentKindBinary 19 | ) 20 | 21 | // argument represents an argument for a Redis command. 22 | type argument struct { 23 | kind argumentKind 24 | val any 25 | } 26 | 27 | func createParameter(x any) (*argument, error) { 28 | var p argument 29 | switch v := x.(type) { 30 | case int: 31 | p.kind = argumentKindInt 32 | p.val = int64(v) 33 | case int32: 34 | p.kind = argumentKindInt 35 | p.val = int64(v) 36 | case int64: 37 | p.kind = argumentKindInt 38 | p.val = v 39 | case string: 40 | p.kind = argumentKindBinary 41 | p.val = []byte(v) 42 | case []byte: 43 | p.kind = argumentKindBinary 44 | p.val = v 45 | default: 46 | return &p, fmt.Errorf("unsupported parameter type: %T", x) 47 | } 48 | return &p, nil 49 | } 50 | 51 | //export spin_redis_handle_redis_message 52 | func handleRedisMessage(payload *C.spin_redis_payload_t) C.spin_redis_error_t { 53 | bytes := C.GoBytes(unsafe.Pointer(payload.ptr), C.int(payload.len)) 54 | if err := handler(bytes); err != nil { 55 | return C.uint8_t(1) 56 | 57 | } 58 | return C.uint8_t(0) 59 | } 60 | 61 | func publish(addr, channel string, payload []byte) error { 62 | caddr := redisStr(addr) 63 | cchannel := redisStr(channel) 64 | cpayload := C.outbound_redis_payload_t{ptr: &payload[0], len: C.size_t(len(payload))} 65 | 66 | err := C.outbound_redis_publish(&caddr, &cchannel, &cpayload) 67 | return toErr(err) 68 | } 69 | 70 | func get(addr, key string) ([]byte, error) { 71 | caddr := redisStr(addr) 72 | ckey := redisStr(key) 73 | 74 | var cpayload C.outbound_redis_payload_t 75 | 76 | err := C.outbound_redis_get(&caddr, &ckey, &cpayload) 77 | payload := C.GoBytes(unsafe.Pointer(cpayload.ptr), C.int(cpayload.len)) 78 | return payload, toErr(err) 79 | } 80 | 81 | func set(addr, key string, payload []byte) error { 82 | caddr := redisStr(addr) 83 | ckey := redisStr(key) 84 | cpayload := C.outbound_redis_payload_t{ptr: &payload[0], len: C.size_t(len(payload))} 85 | 86 | err := C.outbound_redis_set(&caddr, &ckey, &cpayload) 87 | return toErr(err) 88 | } 89 | 90 | func incr(addr, key string) (int64, error) { 91 | caddr := redisStr(addr) 92 | ckey := redisStr(key) 93 | 94 | var cpayload C.int64_t 95 | 96 | err := C.outbound_redis_incr(&caddr, &ckey, &cpayload) 97 | return int64(cpayload), toErr(err) 98 | } 99 | 100 | func del(addr string, keys []string) (int64, error) { 101 | caddr := redisStr(addr) 102 | ckeys := redisListStr(keys) 103 | 104 | var cpayload C.int64_t 105 | 106 | err := C.outbound_redis_del(&caddr, &ckeys, &cpayload) 107 | return int64(cpayload), toErr(err) 108 | } 109 | 110 | func sadd(addr, key string, values []string) (int64, error) { 111 | caddr := redisStr(addr) 112 | ckey := redisStr(key) 113 | cvalues := redisListStr(values) 114 | 115 | var cpayload C.int64_t 116 | 117 | err := C.outbound_redis_sadd(&caddr, &ckey, &cvalues, &cpayload) 118 | return int64(cpayload), toErr(err) 119 | } 120 | 121 | func smembers(addr, key string) ([]string, error) { 122 | caddr := redisStr(addr) 123 | ckey := redisStr(key) 124 | 125 | var cpayload C.outbound_redis_list_string_t 126 | 127 | err := C.outbound_redis_smembers(&caddr, &ckey, &cpayload) 128 | return fromRedisListStr(&cpayload), toErr(err) 129 | } 130 | 131 | func srem(addr, key string, values []string) (int64, error) { 132 | caddr := redisStr(addr) 133 | ckey := redisStr(key) 134 | cvalues := redisListStr(values) 135 | 136 | var cpayload C.int64_t 137 | 138 | err := C.outbound_redis_srem(&caddr, &ckey, &cvalues, &cpayload) 139 | return int64(cpayload), toErr(err) 140 | } 141 | 142 | func execute(addr, command string, arguments []*argument) ([]*Result, error) { 143 | caddr := redisStr(addr) 144 | ccommand := redisStr(command) 145 | carguments := redisListParameter(arguments) 146 | 147 | var cpayload C.outbound_redis_list_redis_result_t 148 | 149 | err := C.outbound_redis_execute(&caddr, &ccommand, &carguments, &cpayload) 150 | return fromRedisListResult(&cpayload), toErr(err) 151 | } 152 | 153 | func redisStr(x string) C.outbound_redis_string_t { 154 | return C.outbound_redis_string_t{ptr: C.CString(x), len: C.size_t(len(x))} 155 | } 156 | 157 | func redisListStr(xs []string) C.outbound_redis_list_string_t { 158 | if len(xs) == 0 { 159 | return C.outbound_redis_list_string_t{} 160 | } 161 | cxs := make([]C.outbound_redis_string_t, 0, len(xs)) 162 | for i := 0; i < len(xs); i++ { 163 | cxs = append(cxs, redisStr(xs[i])) 164 | } 165 | return C.outbound_redis_list_string_t{ptr: &cxs[0], len: C.size_t(len(cxs))} 166 | } 167 | 168 | func fromRedisListStr(list *C.outbound_redis_list_string_t) []string { 169 | listLen := int(list.len) 170 | result := make([]string, 0, listLen) 171 | 172 | slice := unsafe.Slice(list.ptr, listLen) 173 | for i := 0; i < listLen; i++ { 174 | str := slice[i] 175 | result = append(result, C.GoStringN(str.ptr, C.int(str.len))) 176 | } 177 | return result 178 | } 179 | 180 | func redisParameter(x *argument) C.outbound_redis_redis_parameter_t { 181 | var ret C.outbound_redis_redis_parameter_t 182 | switch x.kind { 183 | case argumentKindInt: 184 | *(*C.int64_t)(unsafe.Pointer(&ret.val)) = x.val.(int64) 185 | case argumentKindBinary: 186 | value := x.val.([]byte) 187 | payload := C.outbound_redis_payload_t{ptr: &value[0], len: C.size_t(len(value))} 188 | *(*C.outbound_redis_payload_t)(unsafe.Pointer(&ret.val)) = payload 189 | } 190 | ret.tag = C.uint8_t(x.kind) 191 | return ret 192 | } 193 | 194 | func redisListParameter(xs []*argument) C.outbound_redis_list_redis_parameter_t { 195 | if len(xs) == 0 { 196 | return C.outbound_redis_list_redis_parameter_t{} 197 | } 198 | 199 | cxs := make([]C.outbound_redis_redis_parameter_t, 0, len(xs)) 200 | for i := 0; i < len(xs); i++ { 201 | cxs = append(cxs, redisParameter(xs[i])) 202 | } 203 | return C.outbound_redis_list_redis_parameter_t{ptr: &cxs[0], len: C.size_t(len(cxs))} 204 | } 205 | 206 | func fromRedisResult(result *C.outbound_redis_redis_result_t) *Result { 207 | var val any 208 | switch ResultKind(result.tag) { 209 | case ResultKindNil: 210 | val = nil 211 | case ResultKindStatus: 212 | str := (*C.outbound_redis_string_t)(unsafe.Pointer(&result.val)) 213 | val = C.GoStringN(str.ptr, C.int(str.len)) 214 | case ResultKindInt64: 215 | val = int64(*(*C.int64_t)(unsafe.Pointer(&result.val))) 216 | case ResultKindBinary: 217 | payload := (*C.outbound_redis_payload_t)(unsafe.Pointer(&result.val)) 218 | val = C.GoBytes(unsafe.Pointer(payload.ptr), C.int(payload.len)) 219 | } 220 | 221 | return &Result{Kind: ResultKind(result.tag), Val: val} 222 | } 223 | 224 | func fromRedisListResult(list *C.outbound_redis_list_redis_result_t) []*Result { 225 | listLen := int(list.len) 226 | result := make([]*Result, 0, listLen) 227 | 228 | slice := unsafe.Slice(list.ptr, listLen) 229 | for i := 0; i < listLen; i++ { 230 | result = append(result, fromRedisResult(&slice[i])) 231 | } 232 | 233 | return result 234 | } 235 | 236 | func toErr(code C.uint8_t) error { 237 | if code == 1 { 238 | return errors.New("internal server error") 239 | } 240 | return nil 241 | } 242 | -------------------------------------------------------------------------------- /pg/outbound-pg.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | __attribute__((weak, export_name("canonical_abi_realloc"))) 5 | void *canonical_abi_realloc( 6 | void *ptr, 7 | size_t orig_size, 8 | size_t align, 9 | size_t new_size 10 | ) { 11 | if (new_size == 0) 12 | return (void*) align; 13 | void *ret = realloc(ptr, new_size); 14 | if (!ret) 15 | abort(); 16 | return ret; 17 | } 18 | 19 | __attribute__((weak, export_name("canonical_abi_free"))) 20 | void canonical_abi_free( 21 | void *ptr, 22 | size_t size, 23 | size_t align 24 | ) { 25 | if (size == 0) 26 | return; 27 | free(ptr); 28 | } 29 | #include 30 | 31 | void outbound_pg_string_set(outbound_pg_string_t *ret, const char *s) { 32 | ret->ptr = (char*) s; 33 | ret->len = strlen(s); 34 | } 35 | 36 | void outbound_pg_string_dup(outbound_pg_string_t *ret, const char *s) { 37 | ret->len = strlen(s); 38 | ret->ptr = canonical_abi_realloc(NULL, 0, 1, ret->len); 39 | memcpy(ret->ptr, s, ret->len); 40 | } 41 | 42 | void outbound_pg_string_free(outbound_pg_string_t *ret) { 43 | canonical_abi_free(ret->ptr, ret->len, 1); 44 | ret->ptr = NULL; 45 | ret->len = 0; 46 | } 47 | void outbound_pg_pg_error_free(outbound_pg_pg_error_t *ptr) { 48 | switch ((int32_t) ptr->tag) { 49 | case 1: { 50 | outbound_pg_string_free(&ptr->val.connection_failed); 51 | break; 52 | } 53 | case 2: { 54 | outbound_pg_string_free(&ptr->val.bad_parameter); 55 | break; 56 | } 57 | case 3: { 58 | outbound_pg_string_free(&ptr->val.query_failed); 59 | break; 60 | } 61 | case 4: { 62 | outbound_pg_string_free(&ptr->val.value_conversion_failed); 63 | break; 64 | } 65 | case 5: { 66 | outbound_pg_string_free(&ptr->val.other_error); 67 | break; 68 | } 69 | } 70 | } 71 | void outbound_pg_column_free(outbound_pg_column_t *ptr) { 72 | outbound_pg_string_free(&ptr->name); 73 | } 74 | void outbound_pg_list_u8_free(outbound_pg_list_u8_t *ptr) { 75 | canonical_abi_free(ptr->ptr, ptr->len * 1, 1); 76 | } 77 | void outbound_pg_db_value_free(outbound_pg_db_value_t *ptr) { 78 | switch ((int32_t) ptr->tag) { 79 | case 11: { 80 | outbound_pg_string_free(&ptr->val.str); 81 | break; 82 | } 83 | case 12: { 84 | outbound_pg_list_u8_free(&ptr->val.binary); 85 | break; 86 | } 87 | } 88 | } 89 | void outbound_pg_parameter_value_free(outbound_pg_parameter_value_t *ptr) { 90 | switch ((int32_t) ptr->tag) { 91 | case 11: { 92 | outbound_pg_string_free(&ptr->val.str); 93 | break; 94 | } 95 | case 12: { 96 | outbound_pg_list_u8_free(&ptr->val.binary); 97 | break; 98 | } 99 | } 100 | } 101 | void outbound_pg_row_free(outbound_pg_row_t *ptr) { 102 | for (size_t i = 0; i < ptr->len; i++) { 103 | outbound_pg_db_value_free(&ptr->ptr[i]); 104 | } 105 | canonical_abi_free(ptr->ptr, ptr->len * 16, 8); 106 | } 107 | void outbound_pg_list_column_free(outbound_pg_list_column_t *ptr) { 108 | for (size_t i = 0; i < ptr->len; i++) { 109 | outbound_pg_column_free(&ptr->ptr[i]); 110 | } 111 | canonical_abi_free(ptr->ptr, ptr->len * 12, 4); 112 | } 113 | void outbound_pg_list_row_free(outbound_pg_list_row_t *ptr) { 114 | for (size_t i = 0; i < ptr->len; i++) { 115 | outbound_pg_row_free(&ptr->ptr[i]); 116 | } 117 | canonical_abi_free(ptr->ptr, ptr->len * 8, 4); 118 | } 119 | void outbound_pg_row_set_free(outbound_pg_row_set_t *ptr) { 120 | outbound_pg_list_column_free(&ptr->columns); 121 | outbound_pg_list_row_free(&ptr->rows); 122 | } 123 | void outbound_pg_list_parameter_value_free(outbound_pg_list_parameter_value_t *ptr) { 124 | for (size_t i = 0; i < ptr->len; i++) { 125 | outbound_pg_parameter_value_free(&ptr->ptr[i]); 126 | } 127 | canonical_abi_free(ptr->ptr, ptr->len * 16, 8); 128 | } 129 | void outbound_pg_expected_row_set_pg_error_free(outbound_pg_expected_row_set_pg_error_t *ptr) { 130 | if (!ptr->is_err) { 131 | outbound_pg_row_set_free(&ptr->val.ok); 132 | } else { 133 | outbound_pg_pg_error_free(&ptr->val.err); 134 | } 135 | } 136 | void outbound_pg_expected_u64_pg_error_free(outbound_pg_expected_u64_pg_error_t *ptr) { 137 | if (!ptr->is_err) { 138 | } else { 139 | outbound_pg_pg_error_free(&ptr->val.err); 140 | } 141 | } 142 | 143 | __attribute__((aligned(8))) 144 | static uint8_t RET_AREA[20]; 145 | __attribute__((import_module("outbound-pg"), import_name("query"))) 146 | void __wasm_import_outbound_pg_query(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t); 147 | void outbound_pg_query(outbound_pg_string_t *address, outbound_pg_string_t *statement, outbound_pg_list_parameter_value_t *params, outbound_pg_expected_row_set_pg_error_t *ret0) { 148 | int32_t ptr = (int32_t) &RET_AREA; 149 | __wasm_import_outbound_pg_query((int32_t) (*address).ptr, (int32_t) (*address).len, (int32_t) (*statement).ptr, (int32_t) (*statement).len, (int32_t) (*params).ptr, (int32_t) (*params).len, ptr); 150 | outbound_pg_expected_row_set_pg_error_t expected; 151 | switch ((int32_t) (*((uint8_t*) (ptr + 0)))) { 152 | case 0: { 153 | expected.is_err = false; 154 | 155 | expected.val.ok = (outbound_pg_row_set_t) { 156 | (outbound_pg_list_column_t) { (outbound_pg_column_t*)(*((int32_t*) (ptr + 4))), (size_t)(*((int32_t*) (ptr + 8))) }, 157 | (outbound_pg_list_row_t) { (outbound_pg_row_t*)(*((int32_t*) (ptr + 12))), (size_t)(*((int32_t*) (ptr + 16))) }, 158 | }; 159 | break; 160 | } 161 | case 1: { 162 | expected.is_err = true; 163 | outbound_pg_pg_error_t variant13; 164 | variant13.tag = (int32_t) (*((uint8_t*) (ptr + 4))); 165 | switch ((int32_t) variant13.tag) { 166 | case 0: { 167 | break; 168 | } 169 | case 1: { 170 | variant13.val.connection_failed = (outbound_pg_string_t) { (char*)(*((int32_t*) (ptr + 8))), (size_t)(*((int32_t*) (ptr + 12))) }; 171 | break; 172 | } 173 | case 2: { 174 | variant13.val.bad_parameter = (outbound_pg_string_t) { (char*)(*((int32_t*) (ptr + 8))), (size_t)(*((int32_t*) (ptr + 12))) }; 175 | break; 176 | } 177 | case 3: { 178 | variant13.val.query_failed = (outbound_pg_string_t) { (char*)(*((int32_t*) (ptr + 8))), (size_t)(*((int32_t*) (ptr + 12))) }; 179 | break; 180 | } 181 | case 4: { 182 | variant13.val.value_conversion_failed = (outbound_pg_string_t) { (char*)(*((int32_t*) (ptr + 8))), (size_t)(*((int32_t*) (ptr + 12))) }; 183 | break; 184 | } 185 | case 5: { 186 | variant13.val.other_error = (outbound_pg_string_t) { (char*)(*((int32_t*) (ptr + 8))), (size_t)(*((int32_t*) (ptr + 12))) }; 187 | break; 188 | } 189 | } 190 | 191 | expected.val.err = variant13; 192 | break; 193 | } 194 | }*ret0 = expected; 195 | } 196 | __attribute__((import_module("outbound-pg"), import_name("execute"))) 197 | void __wasm_import_outbound_pg_execute(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t); 198 | void outbound_pg_execute(outbound_pg_string_t *address, outbound_pg_string_t *statement, outbound_pg_list_parameter_value_t *params, outbound_pg_expected_u64_pg_error_t *ret0) { 199 | int32_t ptr = (int32_t) &RET_AREA; 200 | __wasm_import_outbound_pg_execute((int32_t) (*address).ptr, (int32_t) (*address).len, (int32_t) (*statement).ptr, (int32_t) (*statement).len, (int32_t) (*params).ptr, (int32_t) (*params).len, ptr); 201 | outbound_pg_expected_u64_pg_error_t expected; 202 | switch ((int32_t) (*((uint8_t*) (ptr + 0)))) { 203 | case 0: { 204 | expected.is_err = false; 205 | 206 | expected.val.ok = (uint64_t) (*((int64_t*) (ptr + 8))); 207 | break; 208 | } 209 | case 1: { 210 | expected.is_err = true; 211 | outbound_pg_pg_error_t variant; 212 | variant.tag = (int32_t) (*((uint8_t*) (ptr + 8))); 213 | switch ((int32_t) variant.tag) { 214 | case 0: { 215 | break; 216 | } 217 | case 1: { 218 | variant.val.connection_failed = (outbound_pg_string_t) { (char*)(*((int32_t*) (ptr + 12))), (size_t)(*((int32_t*) (ptr + 16))) }; 219 | break; 220 | } 221 | case 2: { 222 | variant.val.bad_parameter = (outbound_pg_string_t) { (char*)(*((int32_t*) (ptr + 12))), (size_t)(*((int32_t*) (ptr + 16))) }; 223 | break; 224 | } 225 | case 3: { 226 | variant.val.query_failed = (outbound_pg_string_t) { (char*)(*((int32_t*) (ptr + 12))), (size_t)(*((int32_t*) (ptr + 16))) }; 227 | break; 228 | } 229 | case 4: { 230 | variant.val.value_conversion_failed = (outbound_pg_string_t) { (char*)(*((int32_t*) (ptr + 12))), (size_t)(*((int32_t*) (ptr + 16))) }; 231 | break; 232 | } 233 | case 5: { 234 | variant.val.other_error = (outbound_pg_string_t) { (char*)(*((int32_t*) (ptr + 12))), (size_t)(*((int32_t*) (ptr + 16))) }; 235 | break; 236 | } 237 | } 238 | 239 | expected.val.err = variant; 240 | break; 241 | } 242 | }*ret0 = expected; 243 | } 244 | -------------------------------------------------------------------------------- /mysql/outbound-mysql.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | __attribute__((weak, export_name("canonical_abi_realloc"))) 5 | void *canonical_abi_realloc( 6 | void *ptr, 7 | size_t orig_size, 8 | size_t align, 9 | size_t new_size 10 | ) { 11 | if (new_size == 0) 12 | return (void*) align; 13 | void *ret = realloc(ptr, new_size); 14 | if (!ret) 15 | abort(); 16 | return ret; 17 | } 18 | 19 | __attribute__((weak, export_name("canonical_abi_free"))) 20 | void canonical_abi_free( 21 | void *ptr, 22 | size_t size, 23 | size_t align 24 | ) { 25 | if (size == 0) 26 | return; 27 | free(ptr); 28 | } 29 | #include 30 | 31 | void outbound_mysql_string_set(outbound_mysql_string_t *ret, const char *s) { 32 | ret->ptr = (char*) s; 33 | ret->len = strlen(s); 34 | } 35 | 36 | void outbound_mysql_string_dup(outbound_mysql_string_t *ret, const char *s) { 37 | ret->len = strlen(s); 38 | ret->ptr = canonical_abi_realloc(NULL, 0, 1, ret->len); 39 | memcpy(ret->ptr, s, ret->len); 40 | } 41 | 42 | void outbound_mysql_string_free(outbound_mysql_string_t *ret) { 43 | canonical_abi_free(ret->ptr, ret->len, 1); 44 | ret->ptr = NULL; 45 | ret->len = 0; 46 | } 47 | void outbound_mysql_mysql_error_free(outbound_mysql_mysql_error_t *ptr) { 48 | switch ((int32_t) ptr->tag) { 49 | case 1: { 50 | outbound_mysql_string_free(&ptr->val.connection_failed); 51 | break; 52 | } 53 | case 2: { 54 | outbound_mysql_string_free(&ptr->val.bad_parameter); 55 | break; 56 | } 57 | case 3: { 58 | outbound_mysql_string_free(&ptr->val.query_failed); 59 | break; 60 | } 61 | case 4: { 62 | outbound_mysql_string_free(&ptr->val.value_conversion_failed); 63 | break; 64 | } 65 | case 5: { 66 | outbound_mysql_string_free(&ptr->val.other_error); 67 | break; 68 | } 69 | } 70 | } 71 | void outbound_mysql_column_free(outbound_mysql_column_t *ptr) { 72 | outbound_mysql_string_free(&ptr->name); 73 | } 74 | void outbound_mysql_list_u8_free(outbound_mysql_list_u8_t *ptr) { 75 | canonical_abi_free(ptr->ptr, ptr->len * 1, 1); 76 | } 77 | void outbound_mysql_db_value_free(outbound_mysql_db_value_t *ptr) { 78 | switch ((int32_t) ptr->tag) { 79 | case 11: { 80 | outbound_mysql_string_free(&ptr->val.str); 81 | break; 82 | } 83 | case 12: { 84 | outbound_mysql_list_u8_free(&ptr->val.binary); 85 | break; 86 | } 87 | } 88 | } 89 | void outbound_mysql_parameter_value_free(outbound_mysql_parameter_value_t *ptr) { 90 | switch ((int32_t) ptr->tag) { 91 | case 11: { 92 | outbound_mysql_string_free(&ptr->val.str); 93 | break; 94 | } 95 | case 12: { 96 | outbound_mysql_list_u8_free(&ptr->val.binary); 97 | break; 98 | } 99 | } 100 | } 101 | void outbound_mysql_row_free(outbound_mysql_row_t *ptr) { 102 | for (size_t i = 0; i < ptr->len; i++) { 103 | outbound_mysql_db_value_free(&ptr->ptr[i]); 104 | } 105 | canonical_abi_free(ptr->ptr, ptr->len * 16, 8); 106 | } 107 | void outbound_mysql_list_column_free(outbound_mysql_list_column_t *ptr) { 108 | for (size_t i = 0; i < ptr->len; i++) { 109 | outbound_mysql_column_free(&ptr->ptr[i]); 110 | } 111 | canonical_abi_free(ptr->ptr, ptr->len * 12, 4); 112 | } 113 | void outbound_mysql_list_row_free(outbound_mysql_list_row_t *ptr) { 114 | for (size_t i = 0; i < ptr->len; i++) { 115 | outbound_mysql_row_free(&ptr->ptr[i]); 116 | } 117 | canonical_abi_free(ptr->ptr, ptr->len * 8, 4); 118 | } 119 | void outbound_mysql_row_set_free(outbound_mysql_row_set_t *ptr) { 120 | outbound_mysql_list_column_free(&ptr->columns); 121 | outbound_mysql_list_row_free(&ptr->rows); 122 | } 123 | void outbound_mysql_list_parameter_value_free(outbound_mysql_list_parameter_value_t *ptr) { 124 | for (size_t i = 0; i < ptr->len; i++) { 125 | outbound_mysql_parameter_value_free(&ptr->ptr[i]); 126 | } 127 | canonical_abi_free(ptr->ptr, ptr->len * 16, 8); 128 | } 129 | void outbound_mysql_expected_row_set_mysql_error_free(outbound_mysql_expected_row_set_mysql_error_t *ptr) { 130 | if (!ptr->is_err) { 131 | outbound_mysql_row_set_free(&ptr->val.ok); 132 | } else { 133 | outbound_mysql_mysql_error_free(&ptr->val.err); 134 | } 135 | } 136 | void outbound_mysql_expected_unit_mysql_error_free(outbound_mysql_expected_unit_mysql_error_t *ptr) { 137 | if (!ptr->is_err) { 138 | } else { 139 | outbound_mysql_mysql_error_free(&ptr->val.err); 140 | } 141 | } 142 | 143 | __attribute__((aligned(4))) 144 | static uint8_t RET_AREA[20]; 145 | __attribute__((import_module("outbound-mysql"), import_name("query"))) 146 | void __wasm_import_outbound_mysql_query(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t); 147 | void outbound_mysql_query(outbound_mysql_string_t *address, outbound_mysql_string_t *statement, outbound_mysql_list_parameter_value_t *params, outbound_mysql_expected_row_set_mysql_error_t *ret0) { 148 | int32_t ptr = (int32_t) &RET_AREA; 149 | __wasm_import_outbound_mysql_query((int32_t) (*address).ptr, (int32_t) (*address).len, (int32_t) (*statement).ptr, (int32_t) (*statement).len, (int32_t) (*params).ptr, (int32_t) (*params).len, ptr); 150 | outbound_mysql_expected_row_set_mysql_error_t expected; 151 | switch ((int32_t) (*((uint8_t*) (ptr + 0)))) { 152 | case 0: { 153 | expected.is_err = false; 154 | 155 | expected.val.ok = (outbound_mysql_row_set_t) { 156 | (outbound_mysql_list_column_t) { (outbound_mysql_column_t*)(*((int32_t*) (ptr + 4))), (size_t)(*((int32_t*) (ptr + 8))) }, 157 | (outbound_mysql_list_row_t) { (outbound_mysql_row_t*)(*((int32_t*) (ptr + 12))), (size_t)(*((int32_t*) (ptr + 16))) }, 158 | }; 159 | break; 160 | } 161 | case 1: { 162 | expected.is_err = true; 163 | outbound_mysql_mysql_error_t variant13; 164 | variant13.tag = (int32_t) (*((uint8_t*) (ptr + 4))); 165 | switch ((int32_t) variant13.tag) { 166 | case 0: { 167 | break; 168 | } 169 | case 1: { 170 | variant13.val.connection_failed = (outbound_mysql_string_t) { (char*)(*((int32_t*) (ptr + 8))), (size_t)(*((int32_t*) (ptr + 12))) }; 171 | break; 172 | } 173 | case 2: { 174 | variant13.val.bad_parameter = (outbound_mysql_string_t) { (char*)(*((int32_t*) (ptr + 8))), (size_t)(*((int32_t*) (ptr + 12))) }; 175 | break; 176 | } 177 | case 3: { 178 | variant13.val.query_failed = (outbound_mysql_string_t) { (char*)(*((int32_t*) (ptr + 8))), (size_t)(*((int32_t*) (ptr + 12))) }; 179 | break; 180 | } 181 | case 4: { 182 | variant13.val.value_conversion_failed = (outbound_mysql_string_t) { (char*)(*((int32_t*) (ptr + 8))), (size_t)(*((int32_t*) (ptr + 12))) }; 183 | break; 184 | } 185 | case 5: { 186 | variant13.val.other_error = (outbound_mysql_string_t) { (char*)(*((int32_t*) (ptr + 8))), (size_t)(*((int32_t*) (ptr + 12))) }; 187 | break; 188 | } 189 | } 190 | 191 | expected.val.err = variant13; 192 | break; 193 | } 194 | }*ret0 = expected; 195 | } 196 | __attribute__((import_module("outbound-mysql"), import_name("execute"))) 197 | void __wasm_import_outbound_mysql_execute(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t); 198 | void outbound_mysql_execute(outbound_mysql_string_t *address, outbound_mysql_string_t *statement, outbound_mysql_list_parameter_value_t *params, outbound_mysql_expected_unit_mysql_error_t *ret0) { 199 | int32_t ptr = (int32_t) &RET_AREA; 200 | __wasm_import_outbound_mysql_execute((int32_t) (*address).ptr, (int32_t) (*address).len, (int32_t) (*statement).ptr, (int32_t) (*statement).len, (int32_t) (*params).ptr, (int32_t) (*params).len, ptr); 201 | outbound_mysql_expected_unit_mysql_error_t expected; 202 | switch ((int32_t) (*((uint8_t*) (ptr + 0)))) { 203 | case 0: { 204 | expected.is_err = false; 205 | 206 | 207 | break; 208 | } 209 | case 1: { 210 | expected.is_err = true; 211 | outbound_mysql_mysql_error_t variant; 212 | variant.tag = (int32_t) (*((uint8_t*) (ptr + 4))); 213 | switch ((int32_t) variant.tag) { 214 | case 0: { 215 | break; 216 | } 217 | case 1: { 218 | variant.val.connection_failed = (outbound_mysql_string_t) { (char*)(*((int32_t*) (ptr + 8))), (size_t)(*((int32_t*) (ptr + 12))) }; 219 | break; 220 | } 221 | case 2: { 222 | variant.val.bad_parameter = (outbound_mysql_string_t) { (char*)(*((int32_t*) (ptr + 8))), (size_t)(*((int32_t*) (ptr + 12))) }; 223 | break; 224 | } 225 | case 3: { 226 | variant.val.query_failed = (outbound_mysql_string_t) { (char*)(*((int32_t*) (ptr + 8))), (size_t)(*((int32_t*) (ptr + 12))) }; 227 | break; 228 | } 229 | case 4: { 230 | variant.val.value_conversion_failed = (outbound_mysql_string_t) { (char*)(*((int32_t*) (ptr + 8))), (size_t)(*((int32_t*) (ptr + 12))) }; 231 | break; 232 | } 233 | case 5: { 234 | variant.val.other_error = (outbound_mysql_string_t) { (char*)(*((int32_t*) (ptr + 8))), (size_t)(*((int32_t*) (ptr + 12))) }; 235 | break; 236 | } 237 | } 238 | 239 | expected.val.err = variant; 240 | break; 241 | } 242 | }*ret0 = expected; 243 | } 244 | --------------------------------------------------------------------------------