From 215243ac8bb52e0bb3ef741b1ee08626d0b4ee83 Mon Sep 17 00:00:00 2001 From: Scott Andrews Date: Thu, 8 Oct 2026 23:54:45 -0400 Subject: [PATCH 1/2] Adopt upstream project layout Signed-off-by: Scott Andrews --- .config/wasm-pkg/config.toml | 3 + .devcontainer/Dockerfile | 8 - .devcontainer/devcontainer.json | 4 +- .devcontainer/docker-compose.yml | 4 +- .github/dependabot.yml | 4 + .github/workflows/bump-version.yaml | 161 +++ .github/workflows/ci.yaml | 64 +- .gitignore | 5 +- Cargo.lock | 14 +- Cargo.toml | 17 +- Makefile | 128 +-- README.md | 10 +- components/cli/cli.wac | 10 + components/client/README.md | 5 + components/{ => client}/client.wac | 0 components/{cli => dep-cli}/Cargo.toml | 2 +- components/dep-cli/README.md | 3 + components/{cli => dep-cli}/src/main.rs | 2 +- .../package.wit | 102 -- .../wit/deps/wasi-cli-0.2.6/package.wit | 261 ----- .../wit/deps/wasi-clocks-0.2.6/package.wit | 43 - .../deps/wasi-config-0.2.0-rc.1/package.wit | 33 - .../deps/wasi-filesystem-0.2.6/package.wit | 158 --- .../wit/deps/wasi-http-0.2.6/package.wit | 733 -------------- components/wit/deps/wasi-io-0.2.6/package.wit | 48 - .../wasi-keyvalue-0.2.0-draft2/package.wit | 276 ----- .../wit/deps/wasi-random-0.2.6/package.wit | 18 - .../wit/deps/wasi-sockets-0.2.6/package.wit | 949 ------------------ lib/.gitignore | 2 - make/components.mk | 91 ++ make/pinned.jq | 6 + make/publish.mk | 15 + make/tools.mk | 53 + make/wit.mk | 29 + scripts/bump-version.sh | 105 ++ scripts/init-devcontainer.sh | 15 + scripts/publish.sh | 106 ++ tools/Cargo.toml | 30 + tools/lib.rs | 1 + 39 files changed, 737 insertions(+), 2781 deletions(-) create mode 100644 .config/wasm-pkg/config.toml delete mode 100644 .devcontainer/Dockerfile create mode 100644 .github/workflows/bump-version.yaml create mode 100644 components/cli/cli.wac create mode 100644 components/client/README.md rename components/{ => client}/client.wac (100%) rename components/{cli => dep-cli}/Cargo.toml (90%) create mode 100644 components/dep-cli/README.md rename components/{cli => dep-cli}/src/main.rs (99%) delete mode 100644 components/wit/deps/componentized-valkey-0.3.0-dev/package.wit delete mode 100644 components/wit/deps/wasi-cli-0.2.6/package.wit delete mode 100644 components/wit/deps/wasi-clocks-0.2.6/package.wit delete mode 100644 components/wit/deps/wasi-config-0.2.0-rc.1/package.wit delete mode 100644 components/wit/deps/wasi-filesystem-0.2.6/package.wit delete mode 100644 components/wit/deps/wasi-http-0.2.6/package.wit delete mode 100644 components/wit/deps/wasi-io-0.2.6/package.wit delete mode 100644 components/wit/deps/wasi-keyvalue-0.2.0-draft2/package.wit delete mode 100644 components/wit/deps/wasi-random-0.2.6/package.wit delete mode 100644 components/wit/deps/wasi-sockets-0.2.6/package.wit delete mode 100644 lib/.gitignore create mode 100644 make/components.mk create mode 100644 make/pinned.jq create mode 100644 make/publish.mk create mode 100644 make/tools.mk create mode 100644 make/wit.mk create mode 100755 scripts/bump-version.sh create mode 100755 scripts/init-devcontainer.sh create mode 100755 scripts/publish.sh create mode 100644 tools/Cargo.toml create mode 100644 tools/lib.rs diff --git a/.config/wasm-pkg/config.toml b/.config/wasm-pkg/config.toml new file mode 100644 index 0000000..ec58b31 --- /dev/null +++ b/.config/wasm-pkg/config.toml @@ -0,0 +1,3 @@ +[namespace_registries] +wasi = "wasi.dev" +componentized = "componentized.dev" diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile deleted file mode 100644 index 2e54e59..0000000 --- a/.devcontainer/Dockerfile +++ /dev/null @@ -1,8 +0,0 @@ -FROM mcr.microsoft.com/devcontainers/rust:2-1-trixie -RUN sudo apt update && sudo apt upgrade -y -RUN curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash -RUN cargo binstall -y wasm-tools -RUN cargo binstall -y wkg -RUN cargo binstall -y wac-cli -RUN cargo binstall -y wasmtime-cli -RUN sudo apt install valkey-tools -y diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index be00903..1f59ab4 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,7 +1,7 @@ // For format details, see https://aka.ms/devcontainer.json. For config options, see the // README at: https://github.com/devcontainers/templates/tree/main/src/rust { - "name": "valkey", + "name": "componentized-valkey", "dockerComposeFile": "docker-compose.yml", "service": "devcontainer", "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", @@ -26,7 +26,7 @@ }, // Use 'postCreateCommand' to run commands after the container is created. - "postCreateCommand": "cargo check", + "postCreateCommand": "./scripts/init-devcontainer.sh", // Configure tool-specific properties. "customizations": { diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index ed507f3..91c0c43 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -1,9 +1,7 @@ version: '3.8' services: devcontainer: - build: - context: . - dockerfile: Dockerfile + image: mcr.microsoft.com/devcontainers/rust volumes: - ../..:/workspaces:cached command: sleep infinity diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 5c1266c..8ec1844 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -8,6 +8,10 @@ updates: directory: "/" schedule: interval: daily +- package-ecosystem: cargo + directory: "/tools" + schedule: + interval: daily - package-ecosystem: rust-toolchain directory: "/" schedule: diff --git a/.github/workflows/bump-version.yaml b/.github/workflows/bump-version.yaml new file mode 100644 index 0000000..b139c64 --- /dev/null +++ b/.github/workflows/bump-version.yaml @@ -0,0 +1,161 @@ +name: Bump version + +on: + workflow_dispatch: + inputs: + version: + description: The new version of the interface package and crates, e.g. 0.1.0 or 0.2.0-dev + required: true + type: string + default: "0.3.0-dev" # the current version, kept current by scripts/bump-version.sh + +jobs: + # bumps the version with read only access, the changes are handed to the pull-request job as a + # patch so the third party actions used to build never run with write access + bump: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v7 + with: + persist-credentials: false + - uses: actions-rust-lang/setup-rust-toolchain@v2 + - name: Install cargo binstall + uses: cargo-bins/cargo-binstall@main + - name: Install tools + run: | + make tools + make -s tools-path >> "${GITHUB_PATH}" + - name: Start Valkey service + run: docker run --rm --name valkey -d -p 6379:6379 valkey/valkey:8 + - name: Bump version + # also fetches the wit dependencies for the new version, and builds and tests the components + run: scripts/bump-version.sh "${VERSION}" + env: + VERSION: ${{ inputs.version }} + - name: Collect changes + run: | + git add --all + git diff --cached --binary > bump-version.patch + - name: Upload changes + uses: actions/upload-artifact@v7 + with: + name: bump-version.patch + path: bump-version.patch + if-no-files-found: error + retention-days: 1 + + # opens the pull request using only first party actions and the gh cli + pull-request: + needs: + - bump + runs-on: ubuntu-latest + # the branch and pull request are created with a token for the custodian GitHub App rather than + # the GITHUB_TOKEN, which can't change workflow files and doesn't trigger the CI workflow + permissions: + contents: read + env: + VERSION: ${{ inputs.version }} + steps: + - name: Check custodian app credentials + run: | + if [ -z "${CLIENT_ID}" ] || [ -z "${PRIVATE_KEY}" ] ; then + echo "::error::the CUSTODIAN_CLIENT_ID and CUSTODIAN_PRIVATE_KEY secrets must be available to this repository, the private key of the custodian GitHub App is needed to create a token" + exit 1 + fi + env: + CLIENT_ID: ${{ secrets.CUSTODIAN_CLIENT_ID }} + PRIVATE_KEY: ${{ secrets.CUSTODIAN_PRIVATE_KEY }} + - name: Create custodian app token + id: app-token + uses: actions/create-github-app-token@v3 + with: + client-id: ${{ secrets.CUSTODIAN_CLIENT_ID }} + private-key: ${{ secrets.CUSTODIAN_PRIVATE_KEY }} + # only this repository, with only the permissions the bump needs + repositories: ${{ github.event.repository.name }} + permission-contents: write + permission-pull-requests: write + # the bump changes the default version in this workflow + permission-workflows: write + - uses: actions/checkout@v7 + with: + persist-credentials: false + - name: Download changes + uses: actions/download-artifact@v8 + with: + name: bump-version.patch + path: ${{ runner.temp }} + - name: Read current version + # the checkout is before the bump, the crates' workspace version is the current version + run: | + current=$( sed -n '/^\[workspace.package\]/,/^\[/s/^version = "\(.*\)"$/\1/p' Cargo.toml ) + echo "CURRENT_VERSION=${current}" >> "${GITHUB_ENV}" + - name: Commit changes + # the commit is created with the REST API, as the app's token can't push. The patch is applied + # locally only to find the changed files and their modes. + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + APP_SLUG: ${{ steps.app-token.outputs.app-slug }} + run: | + branch="bump-version/${VERSION}" + api="repos/${GITHUB_REPOSITORY}" + base=$( git rev-parse HEAD ) + git apply --index "${RUNNER_TEMP}/bump-version.patch" + + # a blob for each changed file, or a null sha for a deleted file + entries="${RUNNER_TEMP}/tree-entries.json" + echo '[]' > "${entries}" + git diff --cached --no-renames --name-status "${base}" | while IFS=$'\t' read -r status path ; do + if [ "${status}" = "D" ] ; then + entry=$( jq -n --arg path "${path}" '{path: $path, mode: "100644", type: "blob", sha: null}' ) + else + mode=$( git ls-files --stage -- "${path}" | cut -d' ' -f1 ) + sha=$( base64 < "${path}" | tr -d '\n' | jq -Rs '{encoding: "base64", content: .}' | gh api --method POST "${api}/git/blobs" --input - --jq .sha ) + entry=$( jq -n --arg path "${path}" --arg mode "${mode}" --arg sha "${sha}" '{path: $path, mode: $mode, type: "blob", sha: $sha}' ) + fi + jq --argjson entry "${entry}" '. + [$entry]' "${entries}" > "${entries}.tmp" && mv "${entries}.tmp" "${entries}" + echo "${status} ${path}" + done + tree=$( jq --arg base "$( git rev-parse "${base}^{tree}" )" '{base_tree: $base, tree: .}' "${entries}" | gh api --method POST "${api}/git/trees" --input - --jq .sha ) + + # authored and signed off (DCO) by the user who triggered the workflow, with their GitHub + # noreply email so the commit is attributed to them without exposing their email address. + # Committed by the custodian app's bot, which made the commit on their behalf. The commit is + # unsigned, GitHub only signs commits it attributes entirely to the app. + name=$( gh api "users/${GITHUB_ACTOR}" --jq '.name // .login' ) + name="${name:-${GITHUB_ACTOR}}" + email="${GITHUB_ACTOR_ID}+${GITHUB_ACTOR}@users.noreply.github.com" + bot="${APP_SLUG}[bot]" + bot_email="$( gh api "users/${bot}" --jq .id )+${bot}@users.noreply.github.com" + commit=$( jq -n \ + --arg message "$( printf 'Bump version from %s to %s\n\nSigned-off-by: %s <%s>' "${CURRENT_VERSION}" "${VERSION}" "${name}" "${email}" )" \ + --arg tree "${tree}" --arg parent "${base}" --arg name "${name}" --arg email "${email}" \ + --arg bot "${bot}" --arg bot_email "${bot_email}" \ + '{message: $message, tree: $tree, parents: [$parent], author: {name: $name, email: $email}, committer: {name: $bot, email: $bot_email}}' \ + | gh api --method POST "${api}/git/commits" --input - --jq .sha ) + echo "created commit ${commit}" + + # points the branch at the commit, replacing the branch left by an earlier run for the same version + if gh api "${api}/git/ref/heads/${branch}" --silent 2> /dev/null ; then + gh api --method PATCH "${api}/git/refs/heads/${branch}" -f sha="${commit}" -F force=true --silent + else + gh api --method POST "${api}/git/refs" -f ref="refs/heads/${branch}" -f sha="${commit}" --silent + fi + - name: Open pull request + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + run: | + branch="bump-version/${VERSION}" + if [ -n "$( gh pr list --head "${branch}" --state open --json number --jq '.[].number' )" ] ; then + echo "A pull request for ${branch} is already open, updated by the new commit" + exit 0 + fi + gh pr create \ + --base "${GITHUB_REF_NAME}" \ + --head "${branch}" \ + --title "Bump version from \`${CURRENT_VERSION}\` to \`${VERSION}\`" \ + --body "Bumps the wit package and crates from \`${CURRENT_VERSION}\` to \`${VERSION}\`. + + Triggered by @${GITHUB_ACTOR} from the [Bump version](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}) workflow." diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f2528c6..21bf394 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -4,6 +4,7 @@ on: push: branches: - "**" + - "!bump-version/**" - "!dependabot/**" tags: - 'v[0-9]+\.[0-9]+\.[0-9]+-?**' @@ -17,14 +18,11 @@ jobs: - uses: actions-rust-lang/setup-rust-toolchain@v2 - name: Install cargo binstall uses: cargo-bins/cargo-binstall@main - - name: Install wac - run: cargo binstall --force wac-cli - - name: Install wasmtime - run: cargo binstall --force wasmtime-cli - - name: Install wkg - run: cargo binstall --force wkg - - name: Install wasm-tools - run: cargo binstall --force wasm-tools + - name: Install tools + # the versions pinned in tools/Cargo.toml, on the path for later steps + run: | + make tools + make -s tools-path >> "${GITHUB_PATH}" - name: Sync wit run: make wit - name: Check for drift in generated wit @@ -32,8 +30,8 @@ jobs: - name: Build components run: make components - name: Collect components.tar - run: tar -cvf ../components.tar *.wasm* - working-directory: ./lib + run: tar -cvf ../../components.tar . + working-directory: ./target/components - name: Upload components.tar uses: actions/upload-artifact@v7 with: @@ -45,12 +43,20 @@ jobs: - name: Test run: make test - name: Capture WIT - working-directory: ./lib + working-directory: ./target/components run: | - for component in *.wasm ; do - echo "::group::${component} ($(du -h ${component} | cut -f1 ))" - wasm-tools component wit "${component}" - echo "::endgroup::" + dump_wit() { + echo "::group::$(basename "$1") ($(du -h "$1" | awk '{print $1}' ))" + wasm-tools component wit "$1" + echo "::endgroup::" + } + + # print interface.wasm first + if [ -f interface.wasm ] ; then + dump_wit interface.wasm + fi + for component in $(find . -name '*.wasm' -not -name '*.debug.wasm' -not -name 'interface.wasm' | sort) ; do + dump_wit "${component}" done publish: @@ -67,10 +73,10 @@ jobs: - uses: actions-rust-lang/setup-rust-toolchain@v2 - name: Install cargo binstall uses: cargo-bins/cargo-binstall@main - - name: Install wkg - run: cargo binstall --force wkg - - name: Install wasm-tools - run: cargo binstall --force wasm-tools + - name: Install tools + run: | + make tools + make -s tools-path >> "${GITHUB_PATH}" - name: Install cosign uses: sigstore/cosign-installer@v4.1.2 - name: Download components.tar @@ -78,10 +84,10 @@ jobs: with: name: components.tar - name: Extract components - run: tar -xvf components.tar -C lib + run: mkdir -p target/components && tar -xvf components.tar -C target/components - name: Get interface version id: interface_version - run: echo "VERSION=$( wasm-tools component wit lib/interface.wasm --json | jq -r "[.packages[] | select(.name | contains(\"${GITHUB_REPOSITORY/\//:}@\"))][0].name" | cut -d'@' -f2 )" >> $GITHUB_OUTPUT + run: echo "VERSION=$( wasm-tools component wit target/components/interface.wasm --json | jq -r "[.packages[] | select(.name | contains(\"${GITHUB_REPOSITORY/\//:}@\"))][0].name" | cut -d'@' -f2 )" >> $GITHUB_OUTPUT - name: Get tag version if: startsWith(github.ref, 'refs/tags/') id: tag_version @@ -97,15 +103,29 @@ jobs: if: startsWith(github.ref, 'refs/tags/') || (github.ref == 'refs/heads/main' && contains(steps.interface_version.outputs.VERSION, '-')) run: make publish env: + PUBLISH_LOG: "${{ runner.temp }}/published.txt" REPOSITORY: "ghcr.io/${{ github.repository }}" VERSION: "${{ case(github.ref == 'refs/heads/main', steps.interface_version.outputs.VERSION, steps.tag_version.outputs.VERSION) }}" + - name: Draft release notes + run: | + { + echo "## Published components" + echo + echo "| File | Size | Image |" + echo "| --- | --: | --- |" + while read -r file bytes image ; do + size=$( numfmt --to=iec-i --suffix=B --format=%.1f "${bytes}" ) + echo "| \`${file}\` | ${size} | \`${image}\` |" + done < "${RUNNER_TEMP}/published.txt" + } > "${RUNNER_TEMP}/release-notes.md" + cat "${RUNNER_TEMP}/release-notes.md" - name: Draft GitHub Release if: startsWith(github.ref, 'refs/tags/') uses: softprops/action-gh-release@v3 with: draft: true + body_path: ${{ runner.temp }}/release-notes.md files: | - lib/*.wasm components.tar fail_on_unmatched_files: true token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 3546b49..ca3cf2e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,7 @@ -/lib/*.wasm +/components.tar /target .DS_Store +/tools/Cargo.lock +# wit dependencies, fetched by `make wit` from the wkg.toml and wkg.lock files +**/wit/deps/ *.rdb diff --git a/Cargo.lock b/Cargo.lock index 3a09d6c..448b476 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -112,19 +112,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" [[package]] -name = "cli" +name = "colorchoice" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + +[[package]] +name = "dep-cli" version = "0.1.0" dependencies = [ "clap", "wit-bindgen", ] -[[package]] -name = "colorchoice" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" - [[package]] name = "equivalent" version = "1.0.2" diff --git a/Cargo.toml b/Cargo.toml index 8380caa..56cd6f2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,20 @@ [workspace] resolver = "2" members = [ - "components/*", -] -exclude = [ - "components/wit", + "components/as-keyvalue", + "components/dep-cli", + "components/ops", + "components/sample-http-incrementor", ] +[workspace.package] +version = "0.3.0-dev" + [workspace.dependencies] wit-bindgen = "0.61.0" + +[profile.release] +opt-level = "s" +lto = true +codegen-units = 1 +strip = "debuginfo" diff --git a/Makefile b/Makefile index 2e13e4b..17b8b96 100644 --- a/Makefile +++ b/Makefile @@ -1,119 +1,37 @@ +SHELL := /bin/bash + export RUST_BACKTRACE ?= 1 export WASMTIME_BACKTRACE_DETAILS ?= 1 +export WKG_CONFIG_FILE := $(abspath .config/wasm-pkg/config.toml) WASMTIME_RUN_FLAGS ?= -Sinherit-network -Sallow-ip-name-lookup -.PHONY: all -all: components - -.PHONY: clean -clean: - cargo clean - rm -rf lib/*.wasm - rm -rf lib/*.wasm.md - -.PHONY: run -run: lib/cli.debug.wasm - @wasmtime run $(WASMTIME_RUN_FLAGS) lib/cli.debug.wasm $(cmd) - -.PHONY: components -components: lib/interface.wasm lib/cli.wasm lib/cli.debug.wasm lib/as-keyvalue.wasm lib/as-keyvalue.debug.wasm lib/client.wasm lib/client.debug.wasm lib/ops.wasm lib/ops.debug.wasm lib/sample-http-incrementor.wasm lib/sample-http-incrementor.debug.wasm - -lib/interface.wasm: wit/deps README.md - wkg build -o lib/interface.wasm - cp README.md lib/interface.wasm.md - -define BUILD_COMPONENT -# $1 - component -# $2 - target -# $3 - release target deps -# $4 - debug target deps +# `make` without a goal runs all, otherwise the first target of an included file would run +.DEFAULT_GOAL := all -lib/$1.wasm: $3 Cargo.toml Cargo.lock components/wit/deps $(shell find components/wit -type f) $(shell find components/$1 -type f) - cargo build -p $1 --target $2 --release - $(if $(findstring $1,cli), - wac plug target/$2/release/$(subst -,_,$1).wasm --plug lib/ops.wasm -o lib/$1.wasm, - wasm-tools component new target/$2/release/$(subst -,_,$1).wasm -o lib/$1.wasm) - cp components/$1/README.md lib/$1.wasm.md +# tools first, the others use the variables it defines for each tool when their rules are read +include make/tools.mk +include make/wit.mk +include make/components.mk +include make/publish.mk -lib/$1.debug.wasm: $4 Cargo.toml Cargo.lock wit/deps $(shell find components/$1 -type f) - cargo build -p $1 --target $2 - $(if $(findstring $1,cli), - wac plug target/$2/debug/$(subst -,_,$1).wasm --plug lib/ops.debug.wasm -o lib/$1.debug.wasm, - wasm-tools component new target/$2/debug/$(subst -,_,$1).wasm -o lib/$1.debug.wasm) - cp components/$1/README.md lib/$1.debug.wasm.md -endef - -$(eval $(call BUILD_COMPONENT,ops,wasm32-unknown-unknown)) -$(eval $(call BUILD_COMPONENT,as-keyvalue,wasm32-unknown-unknown)) -$(eval $(call BUILD_COMPONENT,cli,wasm32-wasip2,lib/ops.wasm,lib/ops.debug.wasm)) -$(eval $(call BUILD_COMPONENT,sample-http-incrementor,wasm32-unknown-unknown)) - -lib/client.wasm: components/client.wac lib/ops.wasm lib/as-keyvalue.wasm - wac compose -o lib/client.wasm \ - -d local:ops=./lib/ops.wasm \ - -d local:as-keyvalue=./lib/as-keyvalue.wasm \ - components/client.wac - cp README.md lib/client.wasm.md +.PHONY: all +all: tools wit components test -lib/client.debug.wasm: components/client.wac lib/ops.debug.wasm lib/as-keyvalue.debug.wasm - wac compose -o lib/client.debug.wasm \ - -d local:ops=./lib/ops.debug.wasm \ - -d local:as-keyvalue=./lib/as-keyvalue.debug.wasm \ - components/client.wac - cp README.md lib/client.debug.wasm.md +.PHONY: clean +clean: clean-components clean-wit + @: -.PHONY: wit -wit: wit/deps components/wit/deps +.PHONY: clean-all +clean-all: clean-components clean-tools clean-wit + cargo clean -wit/deps: wkg.toml $(shell find wit -type f -name "*.wit" -not -path "deps") - wkg fetch -components/wit/deps: wit/deps components/wkg.toml $(shell find components/wit -type f -name "*.wit" -not -path "deps") - (cd components && wkg fetch) +.PHONY: run ## Run the cli against a Valkey server, e.g. `make run cmd="keys '*'"` +run: ${COMPONENTS_DIR}/cli/cli.debug.wasm | $(call tool,wasmtime-cli) + @$(WASMTIME) run $(WASMTIME_RUN_FLAGS) ${COMPONENTS_DIR}/cli/cli.debug.wasm $(cmd) +# requires a running Valkey server .PHONY: test -test: +test: components @$(MAKE) run cmd="hello" - -.PHONY: publish ## Publish each component in the lib directory -publish: $(shell find lib -maxdepth 1 -type f -name "*.wasm" | sed -e 's:^lib/:publish-:g') - -.PHONY: publish-% -publish-%: -ifndef VERSION - $(error VERSION is undefined) -endif -ifndef REPOSITORY - $(error REPOSITORY is undefined) -endif - @$(eval FILE := $(@:publish-%=%)) - @$(eval COMPONENT := $(if $(filter %.debug.wasm,$(FILE)),$(FILE:%.debug.wasm=%),$(FILE:%.wasm=%))) - @$(eval TITLE := $(subst /,:,$(GITHUB_REPOSITORY))$(if $(filter interface,$(COMPONENT)),,-$(COMPONENT))$(if $(filter %.debug.wasm,$(FILE)), (debug))) - @$(eval DESCRIPTION := $(shell head -n 3 "lib/${FILE}.md" | tail -n 1)) - @$(eval COMMIT := $(shell git rev-parse HEAD)) - @$(eval README_DIR := $(if $(wildcard components/$(COMPONENT)/README.md),/components/$(COMPONENT))) - @$(eval URL := https://github.com/${GITHUB_REPOSITORY}/tree/${COMMIT}${README_DIR}) - @$(eval REVISION := ${COMMIT}$(shell git diff --quiet HEAD || echo "+dirty")) - @$(eval COMPONENT_VERSION := $(if $(filter %.debug.wasm,$(FILE)),${VERSION}+debug,${VERSION})) - @$(eval TAG := $(patsubst v%,%,$(subst +,_,$(COMPONENT_VERSION)))) - @$(eval IMAGE := $(if $(filter interface.wasm,$(FILE)),${REPOSITORY}:${TAG},${REPOSITORY}/${COMPONENT}:${TAG})) - - @echo "::group::${FILE} -> ${IMAGE}" - @DIGEST=$$( \ - wkg oci push \ - --annotation "org.opencontainers.image.title=${TITLE}" \ - --annotation "org.opencontainers.image.description=${DESCRIPTION}" \ - --annotation "org.opencontainers.image.version=${COMPONENT_VERSION}" \ - --annotation "org.opencontainers.image.url=${URL}" \ - --annotation "org.opencontainers.image.source=https://github.com/${GITHUB_REPOSITORY}.git" \ - --annotation "org.opencontainers.image.revision=${REVISION}" \ - --annotation "org.opencontainers.image.licenses=Apache-2.0" \ - "${IMAGE}" \ - "lib/${FILE}" \ - 2>&1 \ - | tee /dev/stderr \ - | grep -o 'sha256:[a-f0-9]\{64\}' \ - ) ; \ - cosign sign --yes "${IMAGE}@$${DIGEST}" - @echo "::endgroup::" diff --git a/README.md b/README.md index f237553..fb42323 100644 --- a/README.md +++ b/README.md @@ -19,14 +19,16 @@ A [dev container](https://containers.dev) is available that contains the necessa Prereqs: - a rust toolchain -- [`wasm-tools`](https://github.com/bytecodealliance/wasm-tools) -- [`wac`](https://github.com/bytecodealliance/wac) -- [`wkg`](https://github.com/bytecodealliance/wasm-pkg-tools) +- [`cargo-binstall`](https://github.com/cargo-bins/cargo-binstall), optional, to download prebuilt tools instead of building them ```sh make components ``` +The build creates each component in [`components`](./components) into `target/components`, e.g. the cli at `target/components/cli/cli.wasm`, along with `target/components/interface.wasm`, the `componentized:valkey` WIT package. Each component is also built with debug info, e.g. `target/components/cli/cli.debug.wasm`. + +The cli tools the build uses, [`wasm-tools`](https://github.com/bytecodealliance/wasm-tools), [`wac`](https://github.com/bytecodealliance/wac), [`wasmtime`](https://github.com/bytecodealliance/wasmtime) and [`wkg`](https://github.com/bytecodealliance/wasm-pkg-tools), are pinned in [`tools/Cargo.toml`](./tools/Cargo.toml) and installed into `target/tools/`, e.g. `target/tools/aarch64-apple-darwin`, as needed, or ahead of time with `make tools`. Dependabot bumps the pinned versions. + ## Run Prereqs: @@ -35,7 +37,7 @@ Prereqs: - access to a running [Valkey](https://valkey.io) server ```sh -wasmtime run -Sinherit-network -Sallow-ip-name-lookup lib/cli.wasm keys '*' +wasmtime run -Sinherit-network -Sallow-ip-name-lookup target/components/cli/cli.wasm keys '*' ``` - use `--host` to specify a host other than `127.0.0.1` diff --git a/components/cli/cli.wac b/components/cli/cli.wac new file mode 100644 index 0000000..c39d6a9 --- /dev/null +++ b/components/cli/cli.wac @@ -0,0 +1,10 @@ +package componentized:valkey; + +let ops = new local:ops { ... }; +let cli = new local:dep-cli { + "componentized:valkey/store@0.3.0-dev": ops.store, + "componentized:valkey/resp@0.3.0-dev": ops.resp, + ... +}; + +export cli...; diff --git a/components/client/README.md b/components/client/README.md new file mode 100644 index 0000000..3ec0938 --- /dev/null +++ b/components/client/README.md @@ -0,0 +1,5 @@ +# client + +Client for interacting with a Valkey server, as componentized:valkey and wasi:keyvalue. + +Composes [`ops`](../ops/) with [`as-keyvalue`](../as-keyvalue/), exporting both. diff --git a/components/client.wac b/components/client/client.wac similarity index 100% rename from components/client.wac rename to components/client/client.wac diff --git a/components/cli/Cargo.toml b/components/dep-cli/Cargo.toml similarity index 90% rename from components/cli/Cargo.toml rename to components/dep-cli/Cargo.toml index 2b41b5d..e22fb64 100644 --- a/components/cli/Cargo.toml +++ b/components/dep-cli/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "cli" +name = "dep-cli" version = "0.1.0" edition = "2021" license = "Apache-2.0" diff --git a/components/dep-cli/README.md b/components/dep-cli/README.md new file mode 100644 index 0000000..f31f01b --- /dev/null +++ b/components/dep-cli/README.md @@ -0,0 +1,3 @@ +# dep-cli + +The Valkey CLI before the ops client is composed in, see [`cli`](../cli/). diff --git a/components/cli/src/main.rs b/components/dep-cli/src/main.rs similarity index 99% rename from components/cli/src/main.rs rename to components/dep-cli/src/main.rs index 30d19d2..1aa7dd7 100644 --- a/components/cli/src/main.rs +++ b/components/dep-cli/src/main.rs @@ -6,7 +6,7 @@ use componentized::valkey::{ use std::{fmt, process}; #[derive(Parser)] -#[command(version, about, long_about = None)] +#[command(name = "cli", version, about, long_about = None)] struct Cli { /// Host or ip address hosting the Valkey service #[arg(long, default_value = "127.0.0.1")] diff --git a/components/wit/deps/componentized-valkey-0.3.0-dev/package.wit b/components/wit/deps/componentized-valkey-0.3.0-dev/package.wit deleted file mode 100644 index b5bed7e..0000000 --- a/components/wit/deps/componentized-valkey-0.3.0-dev/package.wit +++ /dev/null @@ -1,102 +0,0 @@ -package componentized:valkey@0.3.0-dev; - -interface resp { - type error = string; - - /// Values nested within an aggregate type. Since WIT types are not allowed to depend on - /// themselves, nested values are stored as encoded RESP. - type nested-value = list; - - variant value { - %string(string), - error(string), - integer(s64), - bulk-string(string), - array(list), - null, - boolean(bool), - double(f64), - big-number(string), - bulk-error(string), - verbatim-string(tuple), - %map(list>), - set(list), - push(list), - } - - decode: func(data: list) -> result; - - encode: func(value: value) -> result, error>; -} - -interface store { - use resp.{error as resp-error, value}; - - variant error { - /// Client errors can represent semantic issues with input/output values, or connection errors communicating with the server - client(string), - /// RESP errors are encoding issues converting values to/from bytes - resp(resp-error), - /// Valkey errors are Error or BulkError values returned directly from the server - valkey(string), - } - - record hello-opts { - proto-ver: option, - auth: option>, - client-name: option, - } - - record hrandfield-opts { - count: option, - with-values: option, - } - - record hscan-opts { - match: option, - count: option, - no-values: option, - } - - resource connection { - send: func(command: list) -> result; - acl-deluser: func(username: string) -> result<_, error>; - acl-genpass: func() -> result; - acl-setuser: func(username: string, rules: list) -> result<_, error>; - auth: func(username: string, password: string) -> result<_, error>; - del: func(key: string) -> result<_, error>; - exists: func(key: string) -> result; - get: func(key: string) -> result, error>; - hdel: func(key: string, field: string) -> result<_, error>; - hello: func(opts: option) -> result>, error>; - hexists: func(key: string, field: string) -> result; - hget: func(key: string, field: string) -> result, error>; - hgetall: func(key: string) -> result>, error>; - hincrby: func(key: string, field: string, increment: s64) -> result; - hincrbyfloat: func(key: string, field: string, increment: f64) -> result; - hkeys: func(key: string) -> result, error>; - hlen: func(key: string) -> result; - hmget: func(key: string, fields: list) -> result>, error>; - hmset: func(key: string, fields: list>) -> result<_, error>; - hrandfield: func(key: string, opts: option) -> result>>>, error>; - hscan: func(key: string, cursor: option, opts: option) -> result, list>>>, error>; - hset: func(key: string, field: string, value: string) -> result<_, error>; - hsetnx: func(key: string, field: string, value: string) -> result; - hstrlen: func(key: string, field: string) -> result; - hvals: func(key: string) -> result, error>; - incr: func(key: string) -> result; - incrby: func(key: string, increment: s64) -> result; - keys: func(pattern: string) -> result, error>; - ping: func() -> result<_, error>; - publish: func(channel: string, message: string) -> result; - quit: func() -> result<_, error>; - set: func(key: string, value: string) -> result<_, error>; - } - - connect: func(host: string, port: u16, opts: option) -> result; -} - -world imports { - import resp; - import store; -} diff --git a/components/wit/deps/wasi-cli-0.2.6/package.wit b/components/wit/deps/wasi-cli-0.2.6/package.wit deleted file mode 100644 index d7a3ca4..0000000 --- a/components/wit/deps/wasi-cli-0.2.6/package.wit +++ /dev/null @@ -1,261 +0,0 @@ -package wasi:cli@0.2.6; - -@since(version = 0.2.0) -interface environment { - /// Get the POSIX-style environment variables. - /// - /// Each environment variable is provided as a pair of string variable names - /// and string value. - /// - /// Morally, these are a value import, but until value imports are available - /// in the component model, this import function should return the same - /// values each time it is called. - @since(version = 0.2.0) - get-environment: func() -> list>; - - /// Get the POSIX-style arguments to the program. - @since(version = 0.2.0) - get-arguments: func() -> list; - - /// Return a path that programs should use as their initial current working - /// directory, interpreting `.` as shorthand for this. - @since(version = 0.2.0) - initial-cwd: func() -> option; -} - -@since(version = 0.2.0) -interface exit { - /// Exit the current instance and any linked instances. - @since(version = 0.2.0) - exit: func(status: result); - - /// Exit the current instance and any linked instances, reporting the - /// specified status code to the host. - /// - /// The meaning of the code depends on the context, with 0 usually meaning - /// "success", and other values indicating various types of failure. - /// - /// This function does not return; the effect is analogous to a trap, but - /// without the connotation that something bad has happened. - @unstable(feature = cli-exit-with-code) - exit-with-code: func(status-code: u8); -} - -@since(version = 0.2.0) -interface run { - /// Run the program. - @since(version = 0.2.0) - run: func() -> result; -} - -@since(version = 0.2.0) -interface stdin { - @since(version = 0.2.0) - use wasi:io/streams@0.2.6.{input-stream}; - - @since(version = 0.2.0) - get-stdin: func() -> input-stream; -} - -@since(version = 0.2.0) -interface stdout { - @since(version = 0.2.0) - use wasi:io/streams@0.2.6.{output-stream}; - - @since(version = 0.2.0) - get-stdout: func() -> output-stream; -} - -@since(version = 0.2.0) -interface stderr { - @since(version = 0.2.0) - use wasi:io/streams@0.2.6.{output-stream}; - - @since(version = 0.2.0) - get-stderr: func() -> output-stream; -} - -/// Terminal input. -/// -/// In the future, this may include functions for disabling echoing, -/// disabling input buffering so that keyboard events are sent through -/// immediately, querying supported features, and so on. -@since(version = 0.2.0) -interface terminal-input { - /// The input side of a terminal. - @since(version = 0.2.0) - resource terminal-input; -} - -/// Terminal output. -/// -/// In the future, this may include functions for querying the terminal -/// size, being notified of terminal size changes, querying supported -/// features, and so on. -@since(version = 0.2.0) -interface terminal-output { - /// The output side of a terminal. - @since(version = 0.2.0) - resource terminal-output; -} - -/// An interface providing an optional `terminal-input` for stdin as a -/// link-time authority. -@since(version = 0.2.0) -interface terminal-stdin { - @since(version = 0.2.0) - use terminal-input.{terminal-input}; - - /// If stdin is connected to a terminal, return a `terminal-input` handle - /// allowing further interaction with it. - @since(version = 0.2.0) - get-terminal-stdin: func() -> option; -} - -/// An interface providing an optional `terminal-output` for stdout as a -/// link-time authority. -@since(version = 0.2.0) -interface terminal-stdout { - @since(version = 0.2.0) - use terminal-output.{terminal-output}; - - /// If stdout is connected to a terminal, return a `terminal-output` handle - /// allowing further interaction with it. - @since(version = 0.2.0) - get-terminal-stdout: func() -> option; -} - -/// An interface providing an optional `terminal-output` for stderr as a -/// link-time authority. -@since(version = 0.2.0) -interface terminal-stderr { - @since(version = 0.2.0) - use terminal-output.{terminal-output}; - - /// If stderr is connected to a terminal, return a `terminal-output` handle - /// allowing further interaction with it. - @since(version = 0.2.0) - get-terminal-stderr: func() -> option; -} - -@since(version = 0.2.0) -world imports { - @since(version = 0.2.0) - import environment; - @since(version = 0.2.0) - import exit; - @since(version = 0.2.0) - import wasi:io/error@0.2.6; - @since(version = 0.2.0) - import wasi:io/poll@0.2.6; - @since(version = 0.2.0) - import wasi:io/streams@0.2.6; - @since(version = 0.2.0) - import stdin; - @since(version = 0.2.0) - import stdout; - @since(version = 0.2.0) - import stderr; - @since(version = 0.2.0) - import terminal-input; - @since(version = 0.2.0) - import terminal-output; - @since(version = 0.2.0) - import terminal-stdin; - @since(version = 0.2.0) - import terminal-stdout; - @since(version = 0.2.0) - import terminal-stderr; - @since(version = 0.2.0) - import wasi:clocks/monotonic-clock@0.2.6; - @since(version = 0.2.0) - import wasi:clocks/wall-clock@0.2.6; - @unstable(feature = clocks-timezone) - import wasi:clocks/timezone@0.2.6; - @since(version = 0.2.0) - import wasi:filesystem/types@0.2.6; - @since(version = 0.2.0) - import wasi:filesystem/preopens@0.2.6; - @since(version = 0.2.0) - import wasi:sockets/network@0.2.6; - @since(version = 0.2.0) - import wasi:sockets/instance-network@0.2.6; - @since(version = 0.2.0) - import wasi:sockets/udp@0.2.6; - @since(version = 0.2.0) - import wasi:sockets/udp-create-socket@0.2.6; - @since(version = 0.2.0) - import wasi:sockets/tcp@0.2.6; - @since(version = 0.2.0) - import wasi:sockets/tcp-create-socket@0.2.6; - @since(version = 0.2.0) - import wasi:sockets/ip-name-lookup@0.2.6; - @since(version = 0.2.0) - import wasi:random/random@0.2.6; - @since(version = 0.2.0) - import wasi:random/insecure@0.2.6; - @since(version = 0.2.0) - import wasi:random/insecure-seed@0.2.6; -} -@since(version = 0.2.0) -world command { - @since(version = 0.2.0) - import environment; - @since(version = 0.2.0) - import exit; - @since(version = 0.2.0) - import wasi:io/error@0.2.6; - @since(version = 0.2.0) - import wasi:io/poll@0.2.6; - @since(version = 0.2.0) - import wasi:io/streams@0.2.6; - @since(version = 0.2.0) - import stdin; - @since(version = 0.2.0) - import stdout; - @since(version = 0.2.0) - import stderr; - @since(version = 0.2.0) - import terminal-input; - @since(version = 0.2.0) - import terminal-output; - @since(version = 0.2.0) - import terminal-stdin; - @since(version = 0.2.0) - import terminal-stdout; - @since(version = 0.2.0) - import terminal-stderr; - @since(version = 0.2.0) - import wasi:clocks/monotonic-clock@0.2.6; - @since(version = 0.2.0) - import wasi:clocks/wall-clock@0.2.6; - @unstable(feature = clocks-timezone) - import wasi:clocks/timezone@0.2.6; - @since(version = 0.2.0) - import wasi:filesystem/types@0.2.6; - @since(version = 0.2.0) - import wasi:filesystem/preopens@0.2.6; - @since(version = 0.2.0) - import wasi:sockets/network@0.2.6; - @since(version = 0.2.0) - import wasi:sockets/instance-network@0.2.6; - @since(version = 0.2.0) - import wasi:sockets/udp@0.2.6; - @since(version = 0.2.0) - import wasi:sockets/udp-create-socket@0.2.6; - @since(version = 0.2.0) - import wasi:sockets/tcp@0.2.6; - @since(version = 0.2.0) - import wasi:sockets/tcp-create-socket@0.2.6; - @since(version = 0.2.0) - import wasi:sockets/ip-name-lookup@0.2.6; - @since(version = 0.2.0) - import wasi:random/random@0.2.6; - @since(version = 0.2.0) - import wasi:random/insecure@0.2.6; - @since(version = 0.2.0) - import wasi:random/insecure-seed@0.2.6; - - @since(version = 0.2.0) - export run; -} diff --git a/components/wit/deps/wasi-clocks-0.2.6/package.wit b/components/wit/deps/wasi-clocks-0.2.6/package.wit deleted file mode 100644 index 7004aa8..0000000 --- a/components/wit/deps/wasi-clocks-0.2.6/package.wit +++ /dev/null @@ -1,43 +0,0 @@ -package wasi:clocks@0.2.6; - -interface monotonic-clock { - use wasi:io/poll@0.2.6.{pollable}; - - type instant = u64; - - type duration = u64; - - now: func() -> instant; - - resolution: func() -> duration; - - subscribe-instant: func(when: instant) -> pollable; - - subscribe-duration: func(when: duration) -> pollable; -} - -interface wall-clock { - record datetime { - seconds: u64, - nanoseconds: u32, - } - - now: func() -> datetime; - - resolution: func() -> datetime; -} - -interface timezone { - use wall-clock.{datetime}; - - record timezone-display { - utc-offset: s32, - name: string, - in-daylight-saving-time: bool, - } - - display: func(when: datetime) -> timezone-display; - - utc-offset: func(when: datetime) -> s32; -} - diff --git a/components/wit/deps/wasi-config-0.2.0-rc.1/package.wit b/components/wit/deps/wasi-config-0.2.0-rc.1/package.wit deleted file mode 100644 index d8950ee..0000000 --- a/components/wit/deps/wasi-config-0.2.0-rc.1/package.wit +++ /dev/null @@ -1,33 +0,0 @@ -package wasi:config@0.2.0-rc.1; - -interface store { - /// An error type that encapsulates the different errors that can occur fetching configuration values. - variant error { - /// This indicates an error from an "upstream" config source. - /// As this could be almost _anything_ (such as Vault, Kubernetes ConfigMaps, KeyValue buckets, etc), - /// the error message is a string. - upstream(string), - /// This indicates an error from an I/O operation. - /// As this could be almost _anything_ (such as a file read, network connection, etc), - /// the error message is a string. - /// Depending on how this ends up being consumed, - /// we may consider moving this to use the `wasi:io/error` type instead. - /// For simplicity right now in supporting multiple implementations, it is being left as a string. - io(string), - } - - /// Gets a configuration value of type `string` associated with the `key`. - /// - /// The value is returned as an `option`. If the key is not found, - /// `Ok(none)` is returned. If an error occurs, an `Err(error)` is returned. - get: func(key: string) -> result, error>; - - /// Gets a list of configuration key-value pairs of type `string`. - /// - /// If an error occurs, an `Err(error)` is returned. - get-all: func() -> result>, error>; -} - -world imports { - import store; -} diff --git a/components/wit/deps/wasi-filesystem-0.2.6/package.wit b/components/wit/deps/wasi-filesystem-0.2.6/package.wit deleted file mode 100644 index 78bbb1e..0000000 --- a/components/wit/deps/wasi-filesystem-0.2.6/package.wit +++ /dev/null @@ -1,158 +0,0 @@ -package wasi:filesystem@0.2.6; - -interface types { - use wasi:io/streams@0.2.6.{input-stream, output-stream, error}; - use wasi:clocks/wall-clock@0.2.6.{datetime}; - - type filesize = u64; - - enum descriptor-type { - unknown, - block-device, - character-device, - directory, - fifo, - symbolic-link, - regular-file, - socket, - } - - flags descriptor-flags { - read, - write, - file-integrity-sync, - data-integrity-sync, - requested-write-sync, - mutate-directory, - } - - flags path-flags { - symlink-follow, - } - - flags open-flags { - create, - directory, - exclusive, - truncate, - } - - type link-count = u64; - - record descriptor-stat { - %type: descriptor-type, - link-count: link-count, - size: filesize, - data-access-timestamp: option, - data-modification-timestamp: option, - status-change-timestamp: option, - } - - variant new-timestamp { - no-change, - now, - timestamp(datetime), - } - - record directory-entry { - %type: descriptor-type, - name: string, - } - - enum error-code { - access, - would-block, - already, - bad-descriptor, - busy, - deadlock, - quota, - exist, - file-too-large, - illegal-byte-sequence, - in-progress, - interrupted, - invalid, - io, - is-directory, - loop, - too-many-links, - message-size, - name-too-long, - no-device, - no-entry, - no-lock, - insufficient-memory, - insufficient-space, - not-directory, - not-empty, - not-recoverable, - unsupported, - no-tty, - no-such-device, - overflow, - not-permitted, - pipe, - read-only, - invalid-seek, - text-file-busy, - cross-device, - } - - enum advice { - normal, - sequential, - random, - will-need, - dont-need, - no-reuse, - } - - record metadata-hash-value { - lower: u64, - upper: u64, - } - - resource descriptor { - read-via-stream: func(offset: filesize) -> result; - write-via-stream: func(offset: filesize) -> result; - append-via-stream: func() -> result; - advise: func(offset: filesize, length: filesize, advice: advice) -> result<_, error-code>; - sync-data: func() -> result<_, error-code>; - get-flags: func() -> result; - get-type: func() -> result; - set-size: func(size: filesize) -> result<_, error-code>; - set-times: func(data-access-timestamp: new-timestamp, data-modification-timestamp: new-timestamp) -> result<_, error-code>; - read: func(length: filesize, offset: filesize) -> result, bool>, error-code>; - write: func(buffer: list, offset: filesize) -> result; - read-directory: func() -> result; - sync: func() -> result<_, error-code>; - create-directory-at: func(path: string) -> result<_, error-code>; - stat: func() -> result; - stat-at: func(path-flags: path-flags, path: string) -> result; - set-times-at: func(path-flags: path-flags, path: string, data-access-timestamp: new-timestamp, data-modification-timestamp: new-timestamp) -> result<_, error-code>; - link-at: func(old-path-flags: path-flags, old-path: string, new-descriptor: borrow, new-path: string) -> result<_, error-code>; - open-at: func(path-flags: path-flags, path: string, open-flags: open-flags, %flags: descriptor-flags) -> result; - readlink-at: func(path: string) -> result; - remove-directory-at: func(path: string) -> result<_, error-code>; - rename-at: func(old-path: string, new-descriptor: borrow, new-path: string) -> result<_, error-code>; - symlink-at: func(old-path: string, new-path: string) -> result<_, error-code>; - unlink-file-at: func(path: string) -> result<_, error-code>; - is-same-object: func(other: borrow) -> bool; - metadata-hash: func() -> result; - metadata-hash-at: func(path-flags: path-flags, path: string) -> result; - } - - resource directory-entry-stream { - read-directory-entry: func() -> result, error-code>; - } - - filesystem-error-code: func(err: borrow) -> option; -} - -interface preopens { - use types.{descriptor}; - - get-directories: func() -> list>; -} - diff --git a/components/wit/deps/wasi-http-0.2.6/package.wit b/components/wit/deps/wasi-http-0.2.6/package.wit deleted file mode 100644 index eb1b25f..0000000 --- a/components/wit/deps/wasi-http-0.2.6/package.wit +++ /dev/null @@ -1,733 +0,0 @@ -package wasi:http@0.2.6; - -/// This interface defines all of the types and methods for implementing -/// HTTP Requests and Responses, both incoming and outgoing, as well as -/// their headers, trailers, and bodies. -@since(version = 0.2.0) -interface types { - @since(version = 0.2.0) - use wasi:clocks/monotonic-clock@0.2.6.{duration}; - @since(version = 0.2.0) - use wasi:io/streams@0.2.6.{input-stream, output-stream}; - @since(version = 0.2.0) - use wasi:io/error@0.2.6.{error as io-error}; - @since(version = 0.2.0) - use wasi:io/poll@0.2.6.{pollable}; - - /// This type corresponds to HTTP standard Methods. - @since(version = 0.2.0) - variant method { - get, - head, - post, - put, - delete, - connect, - options, - trace, - patch, - other(string), - } - - /// This type corresponds to HTTP standard Related Schemes. - @since(version = 0.2.0) - variant scheme { - HTTP, - HTTPS, - other(string), - } - - /// Defines the case payload type for `DNS-error` above: - @since(version = 0.2.0) - record DNS-error-payload { - rcode: option, - info-code: option, - } - - /// Defines the case payload type for `TLS-alert-received` above: - @since(version = 0.2.0) - record TLS-alert-received-payload { - alert-id: option, - alert-message: option, - } - - /// Defines the case payload type for `HTTP-response-{header,trailer}-size` above: - @since(version = 0.2.0) - record field-size-payload { - field-name: option, - field-size: option, - } - - /// These cases are inspired by the IANA HTTP Proxy Error Types: - /// - @since(version = 0.2.0) - variant error-code { - DNS-timeout, - DNS-error(DNS-error-payload), - destination-not-found, - destination-unavailable, - destination-IP-prohibited, - destination-IP-unroutable, - connection-refused, - connection-terminated, - connection-timeout, - connection-read-timeout, - connection-write-timeout, - connection-limit-reached, - TLS-protocol-error, - TLS-certificate-error, - TLS-alert-received(TLS-alert-received-payload), - HTTP-request-denied, - HTTP-request-length-required, - HTTP-request-body-size(option), - HTTP-request-method-invalid, - HTTP-request-URI-invalid, - HTTP-request-URI-too-long, - HTTP-request-header-section-size(option), - HTTP-request-header-size(option), - HTTP-request-trailer-section-size(option), - HTTP-request-trailer-size(field-size-payload), - HTTP-response-incomplete, - HTTP-response-header-section-size(option), - HTTP-response-header-size(field-size-payload), - HTTP-response-body-size(option), - HTTP-response-trailer-section-size(option), - HTTP-response-trailer-size(field-size-payload), - HTTP-response-transfer-coding(option), - HTTP-response-content-coding(option), - HTTP-response-timeout, - HTTP-upgrade-failed, - HTTP-protocol-error, - loop-detected, - configuration-error, - /// This is a catch-all error for anything that doesn't fit cleanly into a - /// more specific case. It also includes an optional string for an - /// unstructured description of the error. Users should not depend on the - /// string for diagnosing errors, as it's not required to be consistent - /// between implementations. - internal-error(option), - } - - /// This type enumerates the different kinds of errors that may occur when - /// setting or appending to a `fields` resource. - @since(version = 0.2.0) - variant header-error { - /// This error indicates that a `field-name` or `field-value` was - /// syntactically invalid when used with an operation that sets headers in a - /// `fields`. - invalid-syntax, - /// This error indicates that a forbidden `field-name` was used when trying - /// to set a header in a `fields`. - forbidden, - /// This error indicates that the operation on the `fields` was not - /// permitted because the fields are immutable. - immutable, - } - - /// Field keys are always strings. - /// - /// Field keys should always be treated as case insensitive by the `fields` - /// resource for the purposes of equality checking. - /// - /// # Deprecation - /// - /// This type has been deprecated in favor of the `field-name` type. - @since(version = 0.2.0) - @deprecated(version = 0.2.2) - type field-key = string; - - /// Field names are always strings. - /// - /// Field names should always be treated as case insensitive by the `fields` - /// resource for the purposes of equality checking. - @since(version = 0.2.1) - type field-name = field-key; - - /// Field values should always be ASCII strings. However, in - /// reality, HTTP implementations often have to interpret malformed values, - /// so they are provided as a list of bytes. - @since(version = 0.2.0) - type field-value = list; - - /// This following block defines the `fields` resource which corresponds to - /// HTTP standard Fields. Fields are a common representation used for both - /// Headers and Trailers. - /// - /// A `fields` may be mutable or immutable. A `fields` created using the - /// constructor, `from-list`, or `clone` will be mutable, but a `fields` - /// resource given by other means (including, but not limited to, - /// `incoming-request.headers`, `outgoing-request.headers`) might be - /// immutable. In an immutable fields, the `set`, `append`, and `delete` - /// operations will fail with `header-error.immutable`. - @since(version = 0.2.0) - resource fields { - /// Construct an empty HTTP Fields. - /// - /// The resulting `fields` is mutable. - @since(version = 0.2.0) - constructor(); - /// Construct an HTTP Fields. - /// - /// The resulting `fields` is mutable. - /// - /// The list represents each name-value pair in the Fields. Names - /// which have multiple values are represented by multiple entries in this - /// list with the same name. - /// - /// The tuple is a pair of the field name, represented as a string, and - /// Value, represented as a list of bytes. - /// - /// An error result will be returned if any `field-name` or `field-value` is - /// syntactically invalid, or if a field is forbidden. - @since(version = 0.2.0) - from-list: static func(entries: list>) -> result; - /// Get all of the values corresponding to a name. If the name is not present - /// in this `fields` or is syntactically invalid, an empty list is returned. - /// However, if the name is present but empty, this is represented by a list - /// with one or more empty field-values present. - @since(version = 0.2.0) - get: func(name: field-name) -> list; - /// Returns `true` when the name is present in this `fields`. If the name is - /// syntactically invalid, `false` is returned. - @since(version = 0.2.0) - has: func(name: field-name) -> bool; - /// Set all of the values for a name. Clears any existing values for that - /// name, if they have been set. - /// - /// Fails with `header-error.immutable` if the `fields` are immutable. - /// - /// Fails with `header-error.invalid-syntax` if the `field-name` or any of - /// the `field-value`s are syntactically invalid. - @since(version = 0.2.0) - set: func(name: field-name, value: list) -> result<_, header-error>; - /// Delete all values for a name. Does nothing if no values for the name - /// exist. - /// - /// Fails with `header-error.immutable` if the `fields` are immutable. - /// - /// Fails with `header-error.invalid-syntax` if the `field-name` is - /// syntactically invalid. - @since(version = 0.2.0) - delete: func(name: field-name) -> result<_, header-error>; - /// Append a value for a name. Does not change or delete any existing - /// values for that name. - /// - /// Fails with `header-error.immutable` if the `fields` are immutable. - /// - /// Fails with `header-error.invalid-syntax` if the `field-name` or - /// `field-value` are syntactically invalid. - @since(version = 0.2.0) - append: func(name: field-name, value: field-value) -> result<_, header-error>; - /// Retrieve the full set of names and values in the Fields. Like the - /// constructor, the list represents each name-value pair. - /// - /// The outer list represents each name-value pair in the Fields. Names - /// which have multiple values are represented by multiple entries in this - /// list with the same name. - /// - /// The names and values are always returned in the original casing and in - /// the order in which they will be serialized for transport. - @since(version = 0.2.0) - entries: func() -> list>; - /// Make a deep copy of the Fields. Equivalent in behavior to calling the - /// `fields` constructor on the return value of `entries`. The resulting - /// `fields` is mutable. - @since(version = 0.2.0) - clone: func() -> fields; - } - - /// Headers is an alias for Fields. - @since(version = 0.2.0) - type headers = fields; - - /// Trailers is an alias for Fields. - @since(version = 0.2.0) - type trailers = fields; - - /// Represents an incoming HTTP Request. - @since(version = 0.2.0) - resource incoming-request { - /// Returns the method of the incoming request. - @since(version = 0.2.0) - method: func() -> method; - /// Returns the path with query parameters from the request, as a string. - @since(version = 0.2.0) - path-with-query: func() -> option; - /// Returns the protocol scheme from the request. - @since(version = 0.2.0) - scheme: func() -> option; - /// Returns the authority of the Request's target URI, if present. - @since(version = 0.2.0) - authority: func() -> option; - /// Get the `headers` associated with the request. - /// - /// The returned `headers` resource is immutable: `set`, `append`, and - /// `delete` operations will fail with `header-error.immutable`. - /// - /// The `headers` returned are a child resource: it must be dropped before - /// the parent `incoming-request` is dropped. Dropping this - /// `incoming-request` before all children are dropped will trap. - @since(version = 0.2.0) - headers: func() -> headers; - /// Gives the `incoming-body` associated with this request. Will only - /// return success at most once, and subsequent calls will return error. - @since(version = 0.2.0) - consume: func() -> result; - } - - /// Represents an outgoing HTTP Request. - @since(version = 0.2.0) - resource outgoing-request { - /// Construct a new `outgoing-request` with a default `method` of `GET`, and - /// `none` values for `path-with-query`, `scheme`, and `authority`. - /// - /// * `headers` is the HTTP Headers for the Request. - /// - /// It is possible to construct, or manipulate with the accessor functions - /// below, an `outgoing-request` with an invalid combination of `scheme` - /// and `authority`, or `headers` which are not permitted to be sent. - /// It is the obligation of the `outgoing-handler.handle` implementation - /// to reject invalid constructions of `outgoing-request`. - @since(version = 0.2.0) - constructor(headers: headers); - /// Returns the resource corresponding to the outgoing Body for this - /// Request. - /// - /// Returns success on the first call: the `outgoing-body` resource for - /// this `outgoing-request` can be retrieved at most once. Subsequent - /// calls will return error. - @since(version = 0.2.0) - body: func() -> result; - /// Get the Method for the Request. - @since(version = 0.2.0) - method: func() -> method; - /// Set the Method for the Request. Fails if the string present in a - /// `method.other` argument is not a syntactically valid method. - @since(version = 0.2.0) - set-method: func(method: method) -> result; - /// Get the combination of the HTTP Path and Query for the Request. - /// When `none`, this represents an empty Path and empty Query. - @since(version = 0.2.0) - path-with-query: func() -> option; - /// Set the combination of the HTTP Path and Query for the Request. - /// When `none`, this represents an empty Path and empty Query. Fails is the - /// string given is not a syntactically valid path and query uri component. - @since(version = 0.2.0) - set-path-with-query: func(path-with-query: option) -> result; - /// Get the HTTP Related Scheme for the Request. When `none`, the - /// implementation may choose an appropriate default scheme. - @since(version = 0.2.0) - scheme: func() -> option; - /// Set the HTTP Related Scheme for the Request. When `none`, the - /// implementation may choose an appropriate default scheme. Fails if the - /// string given is not a syntactically valid uri scheme. - @since(version = 0.2.0) - set-scheme: func(scheme: option) -> result; - /// Get the authority of the Request's target URI. A value of `none` may be used - /// with Related Schemes which do not require an authority. The HTTP and - /// HTTPS schemes always require an authority. - @since(version = 0.2.0) - authority: func() -> option; - /// Set the authority of the Request's target URI. A value of `none` may be used - /// with Related Schemes which do not require an authority. The HTTP and - /// HTTPS schemes always require an authority. Fails if the string given is - /// not a syntactically valid URI authority. - @since(version = 0.2.0) - set-authority: func(authority: option) -> result; - /// Get the headers associated with the Request. - /// - /// The returned `headers` resource is immutable: `set`, `append`, and - /// `delete` operations will fail with `header-error.immutable`. - /// - /// This headers resource is a child: it must be dropped before the parent - /// `outgoing-request` is dropped, or its ownership is transferred to - /// another component by e.g. `outgoing-handler.handle`. - @since(version = 0.2.0) - headers: func() -> headers; - } - - /// Parameters for making an HTTP Request. Each of these parameters is - /// currently an optional timeout applicable to the transport layer of the - /// HTTP protocol. - /// - /// These timeouts are separate from any the user may use to bound a - /// blocking call to `wasi:io/poll.poll`. - @since(version = 0.2.0) - resource request-options { - /// Construct a default `request-options` value. - @since(version = 0.2.0) - constructor(); - /// The timeout for the initial connect to the HTTP Server. - @since(version = 0.2.0) - connect-timeout: func() -> option; - /// Set the timeout for the initial connect to the HTTP Server. An error - /// return value indicates that this timeout is not supported. - @since(version = 0.2.0) - set-connect-timeout: func(duration: option) -> result; - /// The timeout for receiving the first byte of the Response body. - @since(version = 0.2.0) - first-byte-timeout: func() -> option; - /// Set the timeout for receiving the first byte of the Response body. An - /// error return value indicates that this timeout is not supported. - @since(version = 0.2.0) - set-first-byte-timeout: func(duration: option) -> result; - /// The timeout for receiving subsequent chunks of bytes in the Response - /// body stream. - @since(version = 0.2.0) - between-bytes-timeout: func() -> option; - /// Set the timeout for receiving subsequent chunks of bytes in the Response - /// body stream. An error return value indicates that this timeout is not - /// supported. - @since(version = 0.2.0) - set-between-bytes-timeout: func(duration: option) -> result; - } - - /// Represents the ability to send an HTTP Response. - /// - /// This resource is used by the `wasi:http/incoming-handler` interface to - /// allow a Response to be sent corresponding to the Request provided as the - /// other argument to `incoming-handler.handle`. - @since(version = 0.2.0) - resource response-outparam { - /// Send an HTTP 1xx response. - /// - /// Unlike `response-outparam.set`, this does not consume the - /// `response-outparam`, allowing the guest to send an arbitrary number of - /// informational responses before sending the final response using - /// `response-outparam.set`. - /// - /// This will return an `HTTP-protocol-error` if `status` is not in the - /// range [100-199], or an `internal-error` if the implementation does not - /// support informational responses. - @unstable(feature = informational-outbound-responses) - send-informational: func(status: u16, headers: headers) -> result<_, error-code>; - /// Set the value of the `response-outparam` to either send a response, - /// or indicate an error. - /// - /// This method consumes the `response-outparam` to ensure that it is - /// called at most once. If it is never called, the implementation - /// will respond with an error. - /// - /// The user may provide an `error` to `response` to allow the - /// implementation determine how to respond with an HTTP error response. - @since(version = 0.2.0) - set: static func(param: response-outparam, response: result); - } - - /// This type corresponds to the HTTP standard Status Code. - @since(version = 0.2.0) - type status-code = u16; - - /// Represents an incoming HTTP Response. - @since(version = 0.2.0) - resource incoming-response { - /// Returns the status code from the incoming response. - @since(version = 0.2.0) - status: func() -> status-code; - /// Returns the headers from the incoming response. - /// - /// The returned `headers` resource is immutable: `set`, `append`, and - /// `delete` operations will fail with `header-error.immutable`. - /// - /// This headers resource is a child: it must be dropped before the parent - /// `incoming-response` is dropped. - @since(version = 0.2.0) - headers: func() -> headers; - /// Returns the incoming body. May be called at most once. Returns error - /// if called additional times. - @since(version = 0.2.0) - consume: func() -> result; - } - - /// Represents an incoming HTTP Request or Response's Body. - /// - /// A body has both its contents - a stream of bytes - and a (possibly - /// empty) set of trailers, indicating that the full contents of the - /// body have been received. This resource represents the contents as - /// an `input-stream` and the delivery of trailers as a `future-trailers`, - /// and ensures that the user of this interface may only be consuming either - /// the body contents or waiting on trailers at any given time. - @since(version = 0.2.0) - resource incoming-body { - /// Returns the contents of the body, as a stream of bytes. - /// - /// Returns success on first call: the stream representing the contents - /// can be retrieved at most once. Subsequent calls will return error. - /// - /// The returned `input-stream` resource is a child: it must be dropped - /// before the parent `incoming-body` is dropped, or consumed by - /// `incoming-body.finish`. - /// - /// This invariant ensures that the implementation can determine whether - /// the user is consuming the contents of the body, waiting on the - /// `future-trailers` to be ready, or neither. This allows for network - /// backpressure is to be applied when the user is consuming the body, - /// and for that backpressure to not inhibit delivery of the trailers if - /// the user does not read the entire body. - @since(version = 0.2.0) - %stream: func() -> result; - /// Takes ownership of `incoming-body`, and returns a `future-trailers`. - /// This function will trap if the `input-stream` child is still alive. - @since(version = 0.2.0) - finish: static func(this: incoming-body) -> future-trailers; - } - - /// Represents a future which may eventually return trailers, or an error. - /// - /// In the case that the incoming HTTP Request or Response did not have any - /// trailers, this future will resolve to the empty set of trailers once the - /// complete Request or Response body has been received. - @since(version = 0.2.0) - resource future-trailers { - /// Returns a pollable which becomes ready when either the trailers have - /// been received, or an error has occurred. When this pollable is ready, - /// the `get` method will return `some`. - @since(version = 0.2.0) - subscribe: func() -> pollable; - /// Returns the contents of the trailers, or an error which occurred, - /// once the future is ready. - /// - /// The outer `option` represents future readiness. Users can wait on this - /// `option` to become `some` using the `subscribe` method. - /// - /// The outer `result` is used to retrieve the trailers or error at most - /// once. It will be success on the first call in which the outer option - /// is `some`, and error on subsequent calls. - /// - /// The inner `result` represents that either the HTTP Request or Response - /// body, as well as any trailers, were received successfully, or that an - /// error occurred receiving them. The optional `trailers` indicates whether - /// or not trailers were present in the body. - /// - /// When some `trailers` are returned by this method, the `trailers` - /// resource is immutable, and a child. Use of the `set`, `append`, or - /// `delete` methods will return an error, and the resource must be - /// dropped before the parent `future-trailers` is dropped. - @since(version = 0.2.0) - get: func() -> option, error-code>>>; - } - - /// Represents an outgoing HTTP Response. - @since(version = 0.2.0) - resource outgoing-response { - /// Construct an `outgoing-response`, with a default `status-code` of `200`. - /// If a different `status-code` is needed, it must be set via the - /// `set-status-code` method. - /// - /// * `headers` is the HTTP Headers for the Response. - @since(version = 0.2.0) - constructor(headers: headers); - /// Get the HTTP Status Code for the Response. - @since(version = 0.2.0) - status-code: func() -> status-code; - /// Set the HTTP Status Code for the Response. Fails if the status-code - /// given is not a valid http status code. - @since(version = 0.2.0) - set-status-code: func(status-code: status-code) -> result; - /// Get the headers associated with the Request. - /// - /// The returned `headers` resource is immutable: `set`, `append`, and - /// `delete` operations will fail with `header-error.immutable`. - /// - /// This headers resource is a child: it must be dropped before the parent - /// `outgoing-request` is dropped, or its ownership is transferred to - /// another component by e.g. `outgoing-handler.handle`. - @since(version = 0.2.0) - headers: func() -> headers; - /// Returns the resource corresponding to the outgoing Body for this Response. - /// - /// Returns success on the first call: the `outgoing-body` resource for - /// this `outgoing-response` can be retrieved at most once. Subsequent - /// calls will return error. - @since(version = 0.2.0) - body: func() -> result; - } - - /// Represents an outgoing HTTP Request or Response's Body. - /// - /// A body has both its contents - a stream of bytes - and a (possibly - /// empty) set of trailers, inducating the full contents of the body - /// have been sent. This resource represents the contents as an - /// `output-stream` child resource, and the completion of the body (with - /// optional trailers) with a static function that consumes the - /// `outgoing-body` resource, and ensures that the user of this interface - /// may not write to the body contents after the body has been finished. - /// - /// If the user code drops this resource, as opposed to calling the static - /// method `finish`, the implementation should treat the body as incomplete, - /// and that an error has occurred. The implementation should propagate this - /// error to the HTTP protocol by whatever means it has available, - /// including: corrupting the body on the wire, aborting the associated - /// Request, or sending a late status code for the Response. - @since(version = 0.2.0) - resource outgoing-body { - /// Returns a stream for writing the body contents. - /// - /// The returned `output-stream` is a child resource: it must be dropped - /// before the parent `outgoing-body` resource is dropped (or finished), - /// otherwise the `outgoing-body` drop or `finish` will trap. - /// - /// Returns success on the first call: the `output-stream` resource for - /// this `outgoing-body` may be retrieved at most once. Subsequent calls - /// will return error. - @since(version = 0.2.0) - write: func() -> result; - /// Finalize an outgoing body, optionally providing trailers. This must be - /// called to signal that the response is complete. If the `outgoing-body` - /// is dropped without calling `outgoing-body.finalize`, the implementation - /// should treat the body as corrupted. - /// - /// Fails if the body's `outgoing-request` or `outgoing-response` was - /// constructed with a Content-Length header, and the contents written - /// to the body (via `write`) does not match the value given in the - /// Content-Length. - @since(version = 0.2.0) - finish: static func(this: outgoing-body, trailers: option) -> result<_, error-code>; - } - - /// Represents a future which may eventually return an incoming HTTP - /// Response, or an error. - /// - /// This resource is returned by the `wasi:http/outgoing-handler` interface to - /// provide the HTTP Response corresponding to the sent Request. - @since(version = 0.2.0) - resource future-incoming-response { - /// Returns a pollable which becomes ready when either the Response has - /// been received, or an error has occurred. When this pollable is ready, - /// the `get` method will return `some`. - @since(version = 0.2.0) - subscribe: func() -> pollable; - /// Returns the incoming HTTP Response, or an error, once one is ready. - /// - /// The outer `option` represents future readiness. Users can wait on this - /// `option` to become `some` using the `subscribe` method. - /// - /// The outer `result` is used to retrieve the response or error at most - /// once. It will be success on the first call in which the outer option - /// is `some`, and error on subsequent calls. - /// - /// The inner `result` represents that either the incoming HTTP Response - /// status and headers have received successfully, or that an error - /// occurred. Errors may also occur while consuming the response body, - /// but those will be reported by the `incoming-body` and its - /// `output-stream` child. - @since(version = 0.2.0) - get: func() -> option>>; - } - - /// Attempts to extract a http-related `error` from the wasi:io `error` - /// provided. - /// - /// Stream operations which return - /// `wasi:io/stream/stream-error::last-operation-failed` have a payload of - /// type `wasi:io/error/error` with more information about the operation - /// that failed. This payload can be passed through to this function to see - /// if there's http-related information about the error to return. - /// - /// Note that this function is fallible because not all io-errors are - /// http-related errors. - @since(version = 0.2.0) - http-error-code: func(err: borrow) -> option; -} - -/// This interface defines a handler of incoming HTTP Requests. It should -/// be exported by components which can respond to HTTP Requests. -@since(version = 0.2.0) -interface incoming-handler { - @since(version = 0.2.0) - use types.{incoming-request, response-outparam}; - - /// This function is invoked with an incoming HTTP Request, and a resource - /// `response-outparam` which provides the capability to reply with an HTTP - /// Response. The response is sent by calling the `response-outparam.set` - /// method, which allows execution to continue after the response has been - /// sent. This enables both streaming to the response body, and performing other - /// work. - /// - /// The implementor of this function must write a response to the - /// `response-outparam` before returning, or else the caller will respond - /// with an error on its behalf. - @since(version = 0.2.0) - handle: func(request: incoming-request, response-out: response-outparam); -} - -/// This interface defines a handler of outgoing HTTP Requests. It should be -/// imported by components which wish to make HTTP Requests. -@since(version = 0.2.0) -interface outgoing-handler { - @since(version = 0.2.0) - use types.{outgoing-request, request-options, future-incoming-response, error-code}; - - /// This function is invoked with an outgoing HTTP Request, and it returns - /// a resource `future-incoming-response` which represents an HTTP Response - /// which may arrive in the future. - /// - /// The `options` argument accepts optional parameters for the HTTP - /// protocol's transport layer. - /// - /// This function may return an error if the `outgoing-request` is invalid - /// or not allowed to be made. Otherwise, protocol errors are reported - /// through the `future-incoming-response`. - @since(version = 0.2.0) - handle: func(request: outgoing-request, options: option) -> result; -} - -/// The `wasi:http/imports` world imports all the APIs for HTTP proxies. -/// It is intended to be `include`d in other worlds. -@since(version = 0.2.0) -world imports { - @since(version = 0.2.0) - import wasi:io/poll@0.2.6; - @since(version = 0.2.0) - import wasi:clocks/monotonic-clock@0.2.6; - @since(version = 0.2.0) - import wasi:clocks/wall-clock@0.2.6; - @since(version = 0.2.0) - import wasi:random/random@0.2.6; - @since(version = 0.2.0) - import wasi:io/error@0.2.6; - @since(version = 0.2.0) - import wasi:io/streams@0.2.6; - @since(version = 0.2.0) - import wasi:cli/stdout@0.2.6; - @since(version = 0.2.0) - import wasi:cli/stderr@0.2.6; - @since(version = 0.2.0) - import wasi:cli/stdin@0.2.6; - @since(version = 0.2.0) - import types; - @since(version = 0.2.0) - import outgoing-handler; -} -/// The `wasi:http/proxy` world captures a widely-implementable intersection of -/// hosts that includes HTTP forward and reverse proxies. Components targeting -/// this world may concurrently stream in and out any number of incoming and -/// outgoing HTTP requests. -@since(version = 0.2.0) -world proxy { - @since(version = 0.2.0) - import wasi:io/poll@0.2.6; - @since(version = 0.2.0) - import wasi:clocks/monotonic-clock@0.2.6; - @since(version = 0.2.0) - import wasi:clocks/wall-clock@0.2.6; - @since(version = 0.2.0) - import wasi:random/random@0.2.6; - @since(version = 0.2.0) - import wasi:io/error@0.2.6; - @since(version = 0.2.0) - import wasi:io/streams@0.2.6; - @since(version = 0.2.0) - import wasi:cli/stdout@0.2.6; - @since(version = 0.2.0) - import wasi:cli/stderr@0.2.6; - @since(version = 0.2.0) - import wasi:cli/stdin@0.2.6; - @since(version = 0.2.0) - import types; - @since(version = 0.2.0) - import outgoing-handler; - - @since(version = 0.2.0) - export incoming-handler; -} diff --git a/components/wit/deps/wasi-io-0.2.6/package.wit b/components/wit/deps/wasi-io-0.2.6/package.wit deleted file mode 100644 index 72fefbe..0000000 --- a/components/wit/deps/wasi-io-0.2.6/package.wit +++ /dev/null @@ -1,48 +0,0 @@ -package wasi:io@0.2.6; - -interface error { - resource error { - to-debug-string: func() -> string; - } -} - -interface poll { - resource pollable { - ready: func() -> bool; - block: func(); - } - - poll: func(in: list>) -> list; -} - -interface streams { - use error.{error}; - use poll.{pollable}; - - variant stream-error { - last-operation-failed(error), - closed, - } - - resource input-stream { - read: func(len: u64) -> result, stream-error>; - blocking-read: func(len: u64) -> result, stream-error>; - skip: func(len: u64) -> result; - blocking-skip: func(len: u64) -> result; - subscribe: func() -> pollable; - } - - resource output-stream { - check-write: func() -> result; - write: func(contents: list) -> result<_, stream-error>; - blocking-write-and-flush: func(contents: list) -> result<_, stream-error>; - flush: func() -> result<_, stream-error>; - blocking-flush: func() -> result<_, stream-error>; - subscribe: func() -> pollable; - write-zeroes: func(len: u64) -> result<_, stream-error>; - blocking-write-zeroes-and-flush: func(len: u64) -> result<_, stream-error>; - splice: func(src: borrow, len: u64) -> result; - blocking-splice: func(src: borrow, len: u64) -> result; - } -} - diff --git a/components/wit/deps/wasi-keyvalue-0.2.0-draft2/package.wit b/components/wit/deps/wasi-keyvalue-0.2.0-draft2/package.wit deleted file mode 100644 index bb646b1..0000000 --- a/components/wit/deps/wasi-keyvalue-0.2.0-draft2/package.wit +++ /dev/null @@ -1,276 +0,0 @@ -package wasi:keyvalue@0.2.0-draft2; - -/// A keyvalue interface that provides eventually consistent key-value operations. -/// -/// Each of these operations acts on a single key-value pair. -/// -/// The value in the key-value pair is defined as a `u8` byte array and the intention is that it is -/// the common denominator for all data types defined by different key-value stores to handle data, -/// ensuring compatibility between different key-value stores. Note: the clients will be expecting -/// serialization/deserialization overhead to be handled by the key-value store. The value could be -/// a serialized object from JSON, HTML or vendor-specific data types like AWS S3 objects. -/// -/// Data consistency in a key value store refers to the guarantee that once a write operation -/// completes, all subsequent read operations will return the value that was written. -/// -/// Any implementation of this interface must have enough consistency to guarantee "reading your -/// writes." In particular, this means that the client should never get a value that is older than -/// the one it wrote, but it MAY get a newer value if one was written around the same time. These -/// guarantees only apply to the same client (which will likely be provided by the host or an -/// external capability of some kind). In this context a "client" is referring to the caller or -/// guest that is consuming this interface. Once a write request is committed by a specific client, -/// all subsequent read requests by the same client will reflect that write or any subsequent -/// writes. Another client running in a different context may or may not immediately see the result -/// due to the replication lag. As an example of all of this, if a value at a given key is A, and -/// the client writes B, then immediately reads, it should get B. If something else writes C in -/// quick succession, then the client may get C. However, a client running in a separate context may -/// still see A or B -interface store { - /// The set of errors which may be raised by functions in this package - variant error { - /// The host does not recognize the store identifier requested. - no-such-store, - /// The requesting component does not have access to the specified store - /// (which may or may not exist). - access-denied, - /// Some implementation-specific error has occurred (e.g. I/O) - other(string), - } - - /// A response to a `list-keys` operation. - record key-response { - /// The list of keys returned by the query. - keys: list, - /// The continuation token to use to fetch the next page of keys. If this is `null`, then - /// there are no more keys to fetch. - cursor: option, - } - - /// A bucket is a collection of key-value pairs. Each key-value pair is stored as a entry in the - /// bucket, and the bucket itself acts as a collection of all these entries. - /// - /// It is worth noting that the exact terminology for bucket in key-value stores can very - /// depending on the specific implementation. For example: - /// - /// 1. Amazon DynamoDB calls a collection of key-value pairs a table - /// 2. Redis has hashes, sets, and sorted sets as different types of collections - /// 3. Cassandra calls a collection of key-value pairs a column family - /// 4. MongoDB calls a collection of key-value pairs a collection - /// 5. Riak calls a collection of key-value pairs a bucket - /// 6. Memcached calls a collection of key-value pairs a slab - /// 7. Azure Cosmos DB calls a collection of key-value pairs a container - /// - /// In this interface, we use the term `bucket` to refer to a collection of key-value pairs - resource bucket { - /// Get the value associated with the specified `key` - /// - /// The value is returned as an option. If the key-value pair exists in the - /// store, it returns `Ok(value)`. If the key does not exist in the - /// store, it returns `Ok(none)`. - /// - /// If any other error occurs, it returns an `Err(error)`. - get: func(key: string) -> result>, error>; - /// Set the value associated with the key in the store. If the key already - /// exists in the store, it overwrites the value. - /// - /// If the key does not exist in the store, it creates a new key-value pair. - /// - /// If any other error occurs, it returns an `Err(error)`. - set: func(key: string, value: list) -> result<_, error>; - /// Delete the key-value pair associated with the key in the store. - /// - /// If the key does not exist in the store, it does nothing. - /// - /// If any other error occurs, it returns an `Err(error)`. - delete: func(key: string) -> result<_, error>; - /// Check if the key exists in the store. - /// - /// If the key exists in the store, it returns `Ok(true)`. If the key does - /// not exist in the store, it returns `Ok(false)`. - /// - /// If any other error occurs, it returns an `Err(error)`. - exists: func(key: string) -> result; - /// Get all the keys in the store with an optional cursor (for use in pagination). It - /// returns a list of keys. Please note that for most KeyValue implementations, this is a - /// can be a very expensive operation and so it should be used judiciously. Implementations - /// can return any number of keys in a single response, but they should never attempt to - /// send more data than is reasonable (i.e. on a small edge device, this may only be a few - /// KB, while on a large machine this could be several MB). Any response should also return - /// a cursor that can be used to fetch the next page of keys. See the `key-response` record - /// for more information. - /// - /// Note that the keys are not guaranteed to be returned in any particular order. - /// - /// If the store is empty, it returns an empty list. - /// - /// MAY show an out-of-date list of keys if there are concurrent writes to the store. - /// - /// If any error occurs, it returns an `Err(error)`. - list-keys: func(cursor: option) -> result; - } - - /// Get the bucket with the specified identifier. - /// - /// `identifier` must refer to a bucket provided by the host. - /// - /// `error::no-such-store` will be raised if the `identifier` is not recognized. - open: func(identifier: string) -> result; -} - -/// A keyvalue interface that provides atomic operations. -/// -/// Atomic operations are single, indivisible operations. When a fault causes an atomic operation to -/// fail, it will appear to the invoker of the atomic operation that the action either completed -/// successfully or did nothing at all. -/// -/// Please note that this interface is bare functions that take a reference to a bucket. This is to -/// get around the current lack of a way to "extend" a resource with additional methods inside of -/// wit. Future version of the interface will instead extend these methods on the base `bucket` -/// resource. -interface atomics { - use store.{bucket, error}; - - /// A handle to a CAS (compare-and-swap) operation. - resource cas { - /// Construct a new CAS operation. Implementors can map the underlying functionality - /// (transactions, versions, etc) as desired. - new: static func(bucket: borrow, key: string) -> result; - /// Get the current value of the key (if it exists). This allows for avoiding reads if all - /// that is needed to ensure the atomicity of the operation - current: func() -> result>, error>; - } - - /// The error returned by a CAS operation - variant cas-error { - /// A store error occurred when performing the operation - store-error(error), - /// The CAS operation failed because the value was too old. This returns a new CAS handle - /// for easy retries. Implementors MUST return a CAS handle that has been updated to the - /// latest version or transaction. - cas-failed(cas), - } - - /// Atomically increment the value associated with the key in the store by the given delta. It - /// returns the new value. - /// - /// If the key does not exist in the store, it creates a new key-value pair with the value set - /// to the given delta. - /// - /// If any other error occurs, it returns an `Err(error)`. - increment: func(bucket: borrow, key: string, delta: s64) -> result; - - /// Perform the swap on a CAS operation. This consumes the CAS handle and returns an error if - /// the CAS operation failed. - swap: func(cas: cas, value: list) -> result<_, cas-error>; -} - -/// A keyvalue interface that provides batch operations. -/// -/// A batch operation is an operation that operates on multiple keys at once. -/// -/// Batch operations are useful for reducing network round-trip time. For example, if you want to -/// get the values associated with 100 keys, you can either do 100 get operations or you can do 1 -/// batch get operation. The batch operation is faster because it only needs to make 1 network call -/// instead of 100. -/// -/// A batch operation does not guarantee atomicity, meaning that if the batch operation fails, some -/// of the keys may have been modified and some may not. -/// -/// This interface does has the same consistency guarantees as the `store` interface, meaning that -/// you should be able to "read your writes." -/// -/// Please note that this interface is bare functions that take a reference to a bucket. This is to -/// get around the current lack of a way to "extend" a resource with additional methods inside of -/// wit. Future version of the interface will instead extend these methods on the base `bucket` -/// resource. -interface batch { - use store.{bucket, error}; - - /// Get the key-value pairs associated with the keys in the store. It returns a list of - /// key-value pairs. - /// - /// If any of the keys do not exist in the store, it returns a `none` value for that pair in the - /// list. - /// - /// MAY show an out-of-date value if there are concurrent writes to the store. - /// - /// If any other error occurs, it returns an `Err(error)`. - get-many: func(bucket: borrow, keys: list) -> result>>>, error>; - - /// Set the values associated with the keys in the store. If the key already exists in the - /// store, it overwrites the value. - /// - /// Note that the key-value pairs are not guaranteed to be set in the order they are provided. - /// - /// If any of the keys do not exist in the store, it creates a new key-value pair. - /// - /// If any other error occurs, it returns an `Err(error)`. When an error occurs, it does not - /// rollback the key-value pairs that were already set. Thus, this batch operation does not - /// guarantee atomicity, implying that some key-value pairs could be set while others might - /// fail. - /// - /// Other concurrent operations may also be able to see the partial results. - set-many: func(bucket: borrow, key-values: list>>) -> result<_, error>; - - /// Delete the key-value pairs associated with the keys in the store. - /// - /// Note that the key-value pairs are not guaranteed to be deleted in the order they are - /// provided. - /// - /// If any of the keys do not exist in the store, it skips the key. - /// - /// If any other error occurs, it returns an `Err(error)`. When an error occurs, it does not - /// rollback the key-value pairs that were already deleted. Thus, this batch operation does not - /// guarantee atomicity, implying that some key-value pairs could be deleted while others might - /// fail. - /// - /// Other concurrent operations may also be able to see the partial results. - delete-many: func(bucket: borrow, keys: list) -> result<_, error>; -} - -/// A keyvalue interface that provides watch operations. -/// -/// This interface is used to provide event-driven mechanisms to handle -/// keyvalue changes. -interface watcher { - use store.{bucket}; - - /// Handle the `set` event for the given bucket and key. It includes a reference to the `bucket` - /// that can be used to interact with the store. - on-set: func(bucket: bucket, key: string, value: list); - - /// Handle the `delete` event for the given bucket and key. It includes a reference to the - /// `bucket` that can be used to interact with the store. - on-delete: func(bucket: bucket, key: string); -} - -/// The `wasi:keyvalue/imports` world provides common APIs for interacting with key-value stores. -/// Components targeting this world will be able to do: -/// -/// 1. CRUD (create, read, update, delete) operations on key-value stores. -/// 2. Atomic `increment` and CAS (compare-and-swap) operations. -/// 3. Batch operations that can reduce the number of round trips to the network. -world imports { - /// The `store` capability allows the component to perform eventually consistent operations on - /// the key-value store. - import store; - /// The `atomic` capability allows the component to perform atomic / `increment` and CAS - /// (compare-and-swap) operations. - import atomics; - /// The `batch` capability allows the component to perform eventually consistent batch - /// operations that can reduce the number of round trips to the network. - import batch; -} -world watch-service { - /// The `store` capability allows the component to perform eventually consistent operations on - /// the key-value store. - import store; - /// The `atomic` capability allows the component to perform atomic / `increment` and CAS - /// (compare-and-swap) operations. - import atomics; - /// The `batch` capability allows the component to perform eventually consistent batch - /// operations that can reduce the number of round trips to the network. - import batch; - - export watcher; -} diff --git a/components/wit/deps/wasi-random-0.2.6/package.wit b/components/wit/deps/wasi-random-0.2.6/package.wit deleted file mode 100644 index 7adcffa..0000000 --- a/components/wit/deps/wasi-random-0.2.6/package.wit +++ /dev/null @@ -1,18 +0,0 @@ -package wasi:random@0.2.6; - -interface random { - get-random-bytes: func(len: u64) -> list; - - get-random-u64: func() -> u64; -} - -interface insecure { - get-insecure-random-bytes: func(len: u64) -> list; - - get-insecure-random-u64: func() -> u64; -} - -interface insecure-seed { - insecure-seed: func() -> tuple; -} - diff --git a/components/wit/deps/wasi-sockets-0.2.6/package.wit b/components/wit/deps/wasi-sockets-0.2.6/package.wit deleted file mode 100644 index db6d1a2..0000000 --- a/components/wit/deps/wasi-sockets-0.2.6/package.wit +++ /dev/null @@ -1,949 +0,0 @@ -package wasi:sockets@0.2.6; - -@since(version = 0.2.0) -interface network { - @unstable(feature = network-error-code) - use wasi:io/error@0.2.6.{error}; - - /// An opaque resource that represents access to (a subset of) the network. - /// This enables context-based security for networking. - /// There is no need for this to map 1:1 to a physical network interface. - @since(version = 0.2.0) - resource network; - - /// Error codes. - /// - /// In theory, every API can return any error code. - /// In practice, API's typically only return the errors documented per API - /// combined with a couple of errors that are always possible: - /// - `unknown` - /// - `access-denied` - /// - `not-supported` - /// - `out-of-memory` - /// - `concurrency-conflict` - /// - /// See each individual API for what the POSIX equivalents are. They sometimes differ per API. - @since(version = 0.2.0) - enum error-code { - /// Unknown error - unknown, - /// Access denied. - /// - /// POSIX equivalent: EACCES, EPERM - access-denied, - /// The operation is not supported. - /// - /// POSIX equivalent: EOPNOTSUPP - not-supported, - /// One of the arguments is invalid. - /// - /// POSIX equivalent: EINVAL - invalid-argument, - /// Not enough memory to complete the operation. - /// - /// POSIX equivalent: ENOMEM, ENOBUFS, EAI_MEMORY - out-of-memory, - /// The operation timed out before it could finish completely. - timeout, - /// This operation is incompatible with another asynchronous operation that is already in progress. - /// - /// POSIX equivalent: EALREADY - concurrency-conflict, - /// Trying to finish an asynchronous operation that: - /// - has not been started yet, or: - /// - was already finished by a previous `finish-*` call. - /// - /// Note: this is scheduled to be removed when `future`s are natively supported. - not-in-progress, - /// The operation has been aborted because it could not be completed immediately. - /// - /// Note: this is scheduled to be removed when `future`s are natively supported. - would-block, - /// The operation is not valid in the socket's current state. - invalid-state, - /// A new socket resource could not be created because of a system limit. - new-socket-limit, - /// A bind operation failed because the provided address is not an address that the `network` can bind to. - address-not-bindable, - /// A bind operation failed because the provided address is already in use or because there are no ephemeral ports available. - address-in-use, - /// The remote address is not reachable - remote-unreachable, - /// The TCP connection was forcefully rejected - connection-refused, - /// The TCP connection was reset. - connection-reset, - /// A TCP connection was aborted. - connection-aborted, - /// The size of a datagram sent to a UDP socket exceeded the maximum - /// supported size. - datagram-too-large, - /// Name does not exist or has no suitable associated IP addresses. - name-unresolvable, - /// A temporary failure in name resolution occurred. - temporary-resolver-failure, - /// A permanent failure in name resolution occurred. - permanent-resolver-failure, - } - - @since(version = 0.2.0) - enum ip-address-family { - /// Similar to `AF_INET` in POSIX. - ipv4, - /// Similar to `AF_INET6` in POSIX. - ipv6, - } - - @since(version = 0.2.0) - type ipv4-address = tuple; - - @since(version = 0.2.0) - type ipv6-address = tuple; - - @since(version = 0.2.0) - variant ip-address { - ipv4(ipv4-address), - ipv6(ipv6-address), - } - - @since(version = 0.2.0) - record ipv4-socket-address { - /// sin_port - port: u16, - /// sin_addr - address: ipv4-address, - } - - @since(version = 0.2.0) - record ipv6-socket-address { - /// sin6_port - port: u16, - /// sin6_flowinfo - flow-info: u32, - /// sin6_addr - address: ipv6-address, - /// sin6_scope_id - scope-id: u32, - } - - @since(version = 0.2.0) - variant ip-socket-address { - ipv4(ipv4-socket-address), - ipv6(ipv6-socket-address), - } - - /// Attempts to extract a network-related `error-code` from the stream - /// `error` provided. - /// - /// Stream operations which return `stream-error::last-operation-failed` - /// have a payload with more information about the operation that failed. - /// This payload can be passed through to this function to see if there's - /// network-related information about the error to return. - /// - /// Note that this function is fallible because not all stream-related - /// errors are network-related errors. - @unstable(feature = network-error-code) - network-error-code: func(err: borrow) -> option; -} - -/// This interface provides a value-export of the default network handle.. -@since(version = 0.2.0) -interface instance-network { - @since(version = 0.2.0) - use network.{network}; - - /// Get a handle to the default network. - @since(version = 0.2.0) - instance-network: func() -> network; -} - -@since(version = 0.2.0) -interface ip-name-lookup { - @since(version = 0.2.0) - use wasi:io/poll@0.2.6.{pollable}; - @since(version = 0.2.0) - use network.{network, error-code, ip-address}; - - @since(version = 0.2.0) - resource resolve-address-stream { - /// Returns the next address from the resolver. - /// - /// This function should be called multiple times. On each call, it will - /// return the next address in connection order preference. If all - /// addresses have been exhausted, this function returns `none`. - /// - /// This function never returns IPv4-mapped IPv6 addresses. - /// - /// # Typical errors - /// - `name-unresolvable`: Name does not exist or has no suitable associated IP addresses. (EAI_NONAME, EAI_NODATA, EAI_ADDRFAMILY) - /// - `temporary-resolver-failure`: A temporary failure in name resolution occurred. (EAI_AGAIN) - /// - `permanent-resolver-failure`: A permanent failure in name resolution occurred. (EAI_FAIL) - /// - `would-block`: A result is not available yet. (EWOULDBLOCK, EAGAIN) - @since(version = 0.2.0) - resolve-next-address: func() -> result, error-code>; - /// Create a `pollable` which will resolve once the stream is ready for I/O. - /// - /// Note: this function is here for WASI 0.2 only. - /// It's planned to be removed when `future` is natively supported in Preview3. - @since(version = 0.2.0) - subscribe: func() -> pollable; - } - - /// Resolve an internet host name to a list of IP addresses. - /// - /// Unicode domain names are automatically converted to ASCII using IDNA encoding. - /// If the input is an IP address string, the address is parsed and returned - /// as-is without making any external requests. - /// - /// See the wasi-socket proposal README.md for a comparison with getaddrinfo. - /// - /// This function never blocks. It either immediately fails or immediately - /// returns successfully with a `resolve-address-stream` that can be used - /// to (asynchronously) fetch the results. - /// - /// # Typical errors - /// - `invalid-argument`: `name` is a syntactically invalid domain name or IP address. - /// - /// # References: - /// - - /// - - /// - - /// - - @since(version = 0.2.0) - resolve-addresses: func(network: borrow, name: string) -> result; -} - -@since(version = 0.2.0) -interface tcp { - @since(version = 0.2.0) - use wasi:io/streams@0.2.6.{input-stream, output-stream}; - @since(version = 0.2.0) - use wasi:io/poll@0.2.6.{pollable}; - @since(version = 0.2.0) - use wasi:clocks/monotonic-clock@0.2.6.{duration}; - @since(version = 0.2.0) - use network.{network, error-code, ip-socket-address, ip-address-family}; - - @since(version = 0.2.0) - enum shutdown-type { - /// Similar to `SHUT_RD` in POSIX. - receive, - /// Similar to `SHUT_WR` in POSIX. - send, - /// Similar to `SHUT_RDWR` in POSIX. - both, - } - - /// A TCP socket resource. - /// - /// The socket can be in one of the following states: - /// - `unbound` - /// - `bind-in-progress` - /// - `bound` (See note below) - /// - `listen-in-progress` - /// - `listening` - /// - `connect-in-progress` - /// - `connected` - /// - `closed` - /// See - /// for more information. - /// - /// Note: Except where explicitly mentioned, whenever this documentation uses - /// the term "bound" without backticks it actually means: in the `bound` state *or higher*. - /// (i.e. `bound`, `listen-in-progress`, `listening`, `connect-in-progress` or `connected`) - /// - /// In addition to the general error codes documented on the - /// `network::error-code` type, TCP socket methods may always return - /// `error(invalid-state)` when in the `closed` state. - @since(version = 0.2.0) - resource tcp-socket { - /// Bind the socket to a specific network on the provided IP address and port. - /// - /// If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is left to the implementation to decide which - /// network interface(s) to bind to. - /// If the TCP/UDP port is zero, the socket will be bound to a random free port. - /// - /// Bind can be attempted multiple times on the same socket, even with - /// different arguments on each iteration. But never concurrently and - /// only as long as the previous bind failed. Once a bind succeeds, the - /// binding can't be changed anymore. - /// - /// # Typical errors - /// - `invalid-argument`: The `local-address` has the wrong address family. (EAFNOSUPPORT, EFAULT on Windows) - /// - `invalid-argument`: `local-address` is not a unicast address. (EINVAL) - /// - `invalid-argument`: `local-address` is an IPv4-mapped IPv6 address. (EINVAL) - /// - `invalid-state`: The socket is already bound. (EINVAL) - /// - `address-in-use`: No ephemeral ports available. (EADDRINUSE, ENOBUFS on Windows) - /// - `address-in-use`: Address is already in use. (EADDRINUSE) - /// - `address-not-bindable`: `local-address` is not an address that the `network` can bind to. (EADDRNOTAVAIL) - /// - `not-in-progress`: A `bind` operation is not in progress. - /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) - /// - /// # Implementors note - /// When binding to a non-zero port, this bind operation shouldn't be affected by the TIME_WAIT - /// state of a recently closed socket on the same local address. In practice this means that the SO_REUSEADDR - /// socket option should be set implicitly on all platforms, except on Windows where this is the default behavior - /// and SO_REUSEADDR performs something different entirely. - /// - /// Unlike in POSIX, in WASI the bind operation is async. This enables - /// interactive WASI hosts to inject permission prompts. Runtimes that - /// don't want to make use of this ability can simply call the native - /// `bind` as part of either `start-bind` or `finish-bind`. - /// - /// # References - /// - - /// - - /// - - /// - - @since(version = 0.2.0) - start-bind: func(network: borrow, local-address: ip-socket-address) -> result<_, error-code>; - @since(version = 0.2.0) - finish-bind: func() -> result<_, error-code>; - /// Connect to a remote endpoint. - /// - /// On success: - /// - the socket is transitioned into the `connected` state. - /// - a pair of streams is returned that can be used to read & write to the connection - /// - /// After a failed connection attempt, the socket will be in the `closed` - /// state and the only valid action left is to `drop` the socket. A single - /// socket can not be used to connect more than once. - /// - /// # Typical errors - /// - `invalid-argument`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) - /// - `invalid-argument`: `remote-address` is not a unicast address. (EINVAL, ENETUNREACH on Linux, EAFNOSUPPORT on MacOS) - /// - `invalid-argument`: `remote-address` is an IPv4-mapped IPv6 address. (EINVAL, EADDRNOTAVAIL on Illumos) - /// - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EADDRNOTAVAIL on Windows) - /// - `invalid-argument`: The port in `remote-address` is set to 0. (EADDRNOTAVAIL on Windows) - /// - `invalid-argument`: The socket is already attached to a different network. The `network` passed to `connect` must be identical to the one passed to `bind`. - /// - `invalid-state`: The socket is already in the `connected` state. (EISCONN) - /// - `invalid-state`: The socket is already in the `listening` state. (EOPNOTSUPP, EINVAL on Windows) - /// - `timeout`: Connection timed out. (ETIMEDOUT) - /// - `connection-refused`: The connection was forcefully rejected. (ECONNREFUSED) - /// - `connection-reset`: The connection was reset. (ECONNRESET) - /// - `connection-aborted`: The connection was aborted. (ECONNABORTED) - /// - `remote-unreachable`: The remote address is not reachable. (EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) - /// - `address-in-use`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD) - /// - `not-in-progress`: A connect operation is not in progress. - /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) - /// - /// # Implementors note - /// The POSIX equivalent of `start-connect` is the regular `connect` syscall. - /// Because all WASI sockets are non-blocking this is expected to return - /// EINPROGRESS, which should be translated to `ok()` in WASI. - /// - /// The POSIX equivalent of `finish-connect` is a `poll` for event `POLLOUT` - /// with a timeout of 0 on the socket descriptor. Followed by a check for - /// the `SO_ERROR` socket option, in case the poll signaled readiness. - /// - /// # References - /// - - /// - - /// - - /// - - @since(version = 0.2.0) - start-connect: func(network: borrow, remote-address: ip-socket-address) -> result<_, error-code>; - @since(version = 0.2.0) - finish-connect: func() -> result, error-code>; - /// Start listening for new connections. - /// - /// Transitions the socket into the `listening` state. - /// - /// Unlike POSIX, the socket must already be explicitly bound. - /// - /// # Typical errors - /// - `invalid-state`: The socket is not bound to any local address. (EDESTADDRREQ) - /// - `invalid-state`: The socket is already in the `connected` state. (EISCONN, EINVAL on BSD) - /// - `invalid-state`: The socket is already in the `listening` state. - /// - `address-in-use`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE) - /// - `not-in-progress`: A listen operation is not in progress. - /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) - /// - /// # Implementors note - /// Unlike in POSIX, in WASI the listen operation is async. This enables - /// interactive WASI hosts to inject permission prompts. Runtimes that - /// don't want to make use of this ability can simply call the native - /// `listen` as part of either `start-listen` or `finish-listen`. - /// - /// # References - /// - - /// - - /// - - /// - - @since(version = 0.2.0) - start-listen: func() -> result<_, error-code>; - @since(version = 0.2.0) - finish-listen: func() -> result<_, error-code>; - /// Accept a new client socket. - /// - /// The returned socket is bound and in the `connected` state. The following properties are inherited from the listener socket: - /// - `address-family` - /// - `keep-alive-enabled` - /// - `keep-alive-idle-time` - /// - `keep-alive-interval` - /// - `keep-alive-count` - /// - `hop-limit` - /// - `receive-buffer-size` - /// - `send-buffer-size` - /// - /// On success, this function returns the newly accepted client socket along with - /// a pair of streams that can be used to read & write to the connection. - /// - /// # Typical errors - /// - `invalid-state`: Socket is not in the `listening` state. (EINVAL) - /// - `would-block`: No pending connections at the moment. (EWOULDBLOCK, EAGAIN) - /// - `connection-aborted`: An incoming connection was pending, but was terminated by the client before this listener could accept it. (ECONNABORTED) - /// - `new-socket-limit`: The new socket resource could not be created because of a system limit. (EMFILE, ENFILE) - /// - /// # References - /// - - /// - - /// - - /// - - @since(version = 0.2.0) - accept: func() -> result, error-code>; - /// Get the bound local address. - /// - /// POSIX mentions: - /// > If the socket has not been bound to a local name, the value - /// > stored in the object pointed to by `address` is unspecified. - /// - /// WASI is stricter and requires `local-address` to return `invalid-state` when the socket hasn't been bound yet. - /// - /// # Typical errors - /// - `invalid-state`: The socket is not bound to any local address. - /// - /// # References - /// - - /// - - /// - - /// - - @since(version = 0.2.0) - local-address: func() -> result; - /// Get the remote address. - /// - /// # Typical errors - /// - `invalid-state`: The socket is not connected to a remote address. (ENOTCONN) - /// - /// # References - /// - - /// - - /// - - /// - - @since(version = 0.2.0) - remote-address: func() -> result; - /// Whether the socket is in the `listening` state. - /// - /// Equivalent to the SO_ACCEPTCONN socket option. - @since(version = 0.2.0) - is-listening: func() -> bool; - /// Whether this is a IPv4 or IPv6 socket. - /// - /// Equivalent to the SO_DOMAIN socket option. - @since(version = 0.2.0) - address-family: func() -> ip-address-family; - /// Hints the desired listen queue size. Implementations are free to ignore this. - /// - /// If the provided value is 0, an `invalid-argument` error is returned. - /// Any other value will never cause an error, but it might be silently clamped and/or rounded. - /// - /// # Typical errors - /// - `not-supported`: (set) The platform does not support changing the backlog size after the initial listen. - /// - `invalid-argument`: (set) The provided value was 0. - /// - `invalid-state`: (set) The socket is in the `connect-in-progress` or `connected` state. - @since(version = 0.2.0) - set-listen-backlog-size: func(value: u64) -> result<_, error-code>; - /// Enables or disables keepalive. - /// - /// The keepalive behavior can be adjusted using: - /// - `keep-alive-idle-time` - /// - `keep-alive-interval` - /// - `keep-alive-count` - /// These properties can be configured while `keep-alive-enabled` is false, but only come into effect when `keep-alive-enabled` is true. - /// - /// Equivalent to the SO_KEEPALIVE socket option. - @since(version = 0.2.0) - keep-alive-enabled: func() -> result; - @since(version = 0.2.0) - set-keep-alive-enabled: func(value: bool) -> result<_, error-code>; - /// Amount of time the connection has to be idle before TCP starts sending keepalive packets. - /// - /// If the provided value is 0, an `invalid-argument` error is returned. - /// Any other value will never cause an error, but it might be silently clamped and/or rounded. - /// I.e. after setting a value, reading the same setting back may return a different value. - /// - /// Equivalent to the TCP_KEEPIDLE socket option. (TCP_KEEPALIVE on MacOS) - /// - /// # Typical errors - /// - `invalid-argument`: (set) The provided value was 0. - @since(version = 0.2.0) - keep-alive-idle-time: func() -> result; - @since(version = 0.2.0) - set-keep-alive-idle-time: func(value: duration) -> result<_, error-code>; - /// The time between keepalive packets. - /// - /// If the provided value is 0, an `invalid-argument` error is returned. - /// Any other value will never cause an error, but it might be silently clamped and/or rounded. - /// I.e. after setting a value, reading the same setting back may return a different value. - /// - /// Equivalent to the TCP_KEEPINTVL socket option. - /// - /// # Typical errors - /// - `invalid-argument`: (set) The provided value was 0. - @since(version = 0.2.0) - keep-alive-interval: func() -> result; - @since(version = 0.2.0) - set-keep-alive-interval: func(value: duration) -> result<_, error-code>; - /// The maximum amount of keepalive packets TCP should send before aborting the connection. - /// - /// If the provided value is 0, an `invalid-argument` error is returned. - /// Any other value will never cause an error, but it might be silently clamped and/or rounded. - /// I.e. after setting a value, reading the same setting back may return a different value. - /// - /// Equivalent to the TCP_KEEPCNT socket option. - /// - /// # Typical errors - /// - `invalid-argument`: (set) The provided value was 0. - @since(version = 0.2.0) - keep-alive-count: func() -> result; - @since(version = 0.2.0) - set-keep-alive-count: func(value: u32) -> result<_, error-code>; - /// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. - /// - /// If the provided value is 0, an `invalid-argument` error is returned. - /// - /// # Typical errors - /// - `invalid-argument`: (set) The TTL value must be 1 or higher. - @since(version = 0.2.0) - hop-limit: func() -> result; - @since(version = 0.2.0) - set-hop-limit: func(value: u8) -> result<_, error-code>; - /// The kernel buffer space reserved for sends/receives on this socket. - /// - /// If the provided value is 0, an `invalid-argument` error is returned. - /// Any other value will never cause an error, but it might be silently clamped and/or rounded. - /// I.e. after setting a value, reading the same setting back may return a different value. - /// - /// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options. - /// - /// # Typical errors - /// - `invalid-argument`: (set) The provided value was 0. - @since(version = 0.2.0) - receive-buffer-size: func() -> result; - @since(version = 0.2.0) - set-receive-buffer-size: func(value: u64) -> result<_, error-code>; - @since(version = 0.2.0) - send-buffer-size: func() -> result; - @since(version = 0.2.0) - set-send-buffer-size: func(value: u64) -> result<_, error-code>; - /// Create a `pollable` which can be used to poll for, or block on, - /// completion of any of the asynchronous operations of this socket. - /// - /// When `finish-bind`, `finish-listen`, `finish-connect` or `accept` - /// return `error(would-block)`, this pollable can be used to wait for - /// their success or failure, after which the method can be retried. - /// - /// The pollable is not limited to the async operation that happens to be - /// in progress at the time of calling `subscribe` (if any). Theoretically, - /// `subscribe` only has to be called once per socket and can then be - /// (re)used for the remainder of the socket's lifetime. - /// - /// See - /// for more information. - /// - /// Note: this function is here for WASI 0.2 only. - /// It's planned to be removed when `future` is natively supported in Preview3. - @since(version = 0.2.0) - subscribe: func() -> pollable; - /// Initiate a graceful shutdown. - /// - /// - `receive`: The socket is not expecting to receive any data from - /// the peer. The `input-stream` associated with this socket will be - /// closed. Any data still in the receive queue at time of calling - /// this method will be discarded. - /// - `send`: The socket has no more data to send to the peer. The `output-stream` - /// associated with this socket will be closed and a FIN packet will be sent. - /// - `both`: Same effect as `receive` & `send` combined. - /// - /// This function is idempotent; shutting down a direction more than once - /// has no effect and returns `ok`. - /// - /// The shutdown function does not close (drop) the socket. - /// - /// # Typical errors - /// - `invalid-state`: The socket is not in the `connected` state. (ENOTCONN) - /// - /// # References - /// - - /// - - /// - - /// - - @since(version = 0.2.0) - shutdown: func(shutdown-type: shutdown-type) -> result<_, error-code>; - } -} - -@since(version = 0.2.0) -interface tcp-create-socket { - @since(version = 0.2.0) - use network.{network, error-code, ip-address-family}; - @since(version = 0.2.0) - use tcp.{tcp-socket}; - - /// Create a new TCP socket. - /// - /// Similar to `socket(AF_INET or AF_INET6, SOCK_STREAM, IPPROTO_TCP)` in POSIX. - /// On IPv6 sockets, IPV6_V6ONLY is enabled by default and can't be configured otherwise. - /// - /// This function does not require a network capability handle. This is considered to be safe because - /// at time of creation, the socket is not bound to any `network` yet. Up to the moment `bind`/`connect` - /// is called, the socket is effectively an in-memory configuration object, unable to communicate with the outside world. - /// - /// All sockets are non-blocking. Use the wasi-poll interface to block on asynchronous operations. - /// - /// # Typical errors - /// - `not-supported`: The specified `address-family` is not supported. (EAFNOSUPPORT) - /// - `new-socket-limit`: The new socket resource could not be created because of a system limit. (EMFILE, ENFILE) - /// - /// # References - /// - - /// - - /// - - /// - - @since(version = 0.2.0) - create-tcp-socket: func(address-family: ip-address-family) -> result; -} - -@since(version = 0.2.0) -interface udp { - @since(version = 0.2.0) - use wasi:io/poll@0.2.6.{pollable}; - @since(version = 0.2.0) - use network.{network, error-code, ip-socket-address, ip-address-family}; - - /// A received datagram. - @since(version = 0.2.0) - record incoming-datagram { - /// The payload. - /// - /// Theoretical max size: ~64 KiB. In practice, typically less than 1500 bytes. - data: list, - /// The source address. - /// - /// This field is guaranteed to match the remote address the stream was initialized with, if any. - /// - /// Equivalent to the `src_addr` out parameter of `recvfrom`. - remote-address: ip-socket-address, - } - - /// A datagram to be sent out. - @since(version = 0.2.0) - record outgoing-datagram { - /// The payload. - data: list, - /// The destination address. - /// - /// The requirements on this field depend on how the stream was initialized: - /// - with a remote address: this field must be None or match the stream's remote address exactly. - /// - without a remote address: this field is required. - /// - /// If this value is None, the send operation is equivalent to `send` in POSIX. Otherwise it is equivalent to `sendto`. - remote-address: option, - } - - /// A UDP socket handle. - @since(version = 0.2.0) - resource udp-socket { - /// Bind the socket to a specific network on the provided IP address and port. - /// - /// If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is left to the implementation to decide which - /// network interface(s) to bind to. - /// If the port is zero, the socket will be bound to a random free port. - /// - /// # Typical errors - /// - `invalid-argument`: The `local-address` has the wrong address family. (EAFNOSUPPORT, EFAULT on Windows) - /// - `invalid-state`: The socket is already bound. (EINVAL) - /// - `address-in-use`: No ephemeral ports available. (EADDRINUSE, ENOBUFS on Windows) - /// - `address-in-use`: Address is already in use. (EADDRINUSE) - /// - `address-not-bindable`: `local-address` is not an address that the `network` can bind to. (EADDRNOTAVAIL) - /// - `not-in-progress`: A `bind` operation is not in progress. - /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) - /// - /// # Implementors note - /// Unlike in POSIX, in WASI the bind operation is async. This enables - /// interactive WASI hosts to inject permission prompts. Runtimes that - /// don't want to make use of this ability can simply call the native - /// `bind` as part of either `start-bind` or `finish-bind`. - /// - /// # References - /// - - /// - - /// - - /// - - @since(version = 0.2.0) - start-bind: func(network: borrow, local-address: ip-socket-address) -> result<_, error-code>; - @since(version = 0.2.0) - finish-bind: func() -> result<_, error-code>; - /// Set up inbound & outbound communication channels, optionally to a specific peer. - /// - /// This function only changes the local socket configuration and does not generate any network traffic. - /// On success, the `remote-address` of the socket is updated. The `local-address` may be updated as well, - /// based on the best network path to `remote-address`. - /// - /// When a `remote-address` is provided, the returned streams are limited to communicating with that specific peer: - /// - `send` can only be used to send to this destination. - /// - `receive` will only return datagrams sent from the provided `remote-address`. - /// - /// This method may be called multiple times on the same socket to change its association, but - /// only the most recently returned pair of streams will be operational. Implementations may trap if - /// the streams returned by a previous invocation haven't been dropped yet before calling `stream` again. - /// - /// The POSIX equivalent in pseudo-code is: - /// ```text - /// if (was previously connected) { - /// connect(s, AF_UNSPEC) - /// } - /// if (remote_address is Some) { - /// connect(s, remote_address) - /// } - /// ``` - /// - /// Unlike in POSIX, the socket must already be explicitly bound. - /// - /// # Typical errors - /// - `invalid-argument`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) - /// - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL) - /// - `invalid-argument`: The port in `remote-address` is set to 0. (EDESTADDRREQ, EADDRNOTAVAIL) - /// - `invalid-state`: The socket is not bound. - /// - `address-in-use`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD) - /// - `remote-unreachable`: The remote address is not reachable. (ECONNRESET, ENETRESET, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) - /// - `connection-refused`: The connection was refused. (ECONNREFUSED) - /// - /// # References - /// - - /// - - /// - - /// - - @since(version = 0.2.0) - %stream: func(remote-address: option) -> result, error-code>; - /// Get the current bound address. - /// - /// POSIX mentions: - /// > If the socket has not been bound to a local name, the value - /// > stored in the object pointed to by `address` is unspecified. - /// - /// WASI is stricter and requires `local-address` to return `invalid-state` when the socket hasn't been bound yet. - /// - /// # Typical errors - /// - `invalid-state`: The socket is not bound to any local address. - /// - /// # References - /// - - /// - - /// - - /// - - @since(version = 0.2.0) - local-address: func() -> result; - /// Get the address the socket is currently streaming to. - /// - /// # Typical errors - /// - `invalid-state`: The socket is not streaming to a specific remote address. (ENOTCONN) - /// - /// # References - /// - - /// - - /// - - /// - - @since(version = 0.2.0) - remote-address: func() -> result; - /// Whether this is a IPv4 or IPv6 socket. - /// - /// Equivalent to the SO_DOMAIN socket option. - @since(version = 0.2.0) - address-family: func() -> ip-address-family; - /// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. - /// - /// If the provided value is 0, an `invalid-argument` error is returned. - /// - /// # Typical errors - /// - `invalid-argument`: (set) The TTL value must be 1 or higher. - @since(version = 0.2.0) - unicast-hop-limit: func() -> result; - @since(version = 0.2.0) - set-unicast-hop-limit: func(value: u8) -> result<_, error-code>; - /// The kernel buffer space reserved for sends/receives on this socket. - /// - /// If the provided value is 0, an `invalid-argument` error is returned. - /// Any other value will never cause an error, but it might be silently clamped and/or rounded. - /// I.e. after setting a value, reading the same setting back may return a different value. - /// - /// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options. - /// - /// # Typical errors - /// - `invalid-argument`: (set) The provided value was 0. - @since(version = 0.2.0) - receive-buffer-size: func() -> result; - @since(version = 0.2.0) - set-receive-buffer-size: func(value: u64) -> result<_, error-code>; - @since(version = 0.2.0) - send-buffer-size: func() -> result; - @since(version = 0.2.0) - set-send-buffer-size: func(value: u64) -> result<_, error-code>; - /// Create a `pollable` which will resolve once the socket is ready for I/O. - /// - /// Note: this function is here for WASI 0.2 only. - /// It's planned to be removed when `future` is natively supported in Preview3. - @since(version = 0.2.0) - subscribe: func() -> pollable; - } - - @since(version = 0.2.0) - resource incoming-datagram-stream { - /// Receive messages on the socket. - /// - /// This function attempts to receive up to `max-results` datagrams on the socket without blocking. - /// The returned list may contain fewer elements than requested, but never more. - /// - /// This function returns successfully with an empty list when either: - /// - `max-results` is 0, or: - /// - `max-results` is greater than 0, but no results are immediately available. - /// This function never returns `error(would-block)`. - /// - /// # Typical errors - /// - `remote-unreachable`: The remote address is not reachable. (ECONNRESET, ENETRESET on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) - /// - `connection-refused`: The connection was refused. (ECONNREFUSED) - /// - /// # References - /// - - /// - - /// - - /// - - /// - - /// - - /// - - /// - - @since(version = 0.2.0) - receive: func(max-results: u64) -> result, error-code>; - /// Create a `pollable` which will resolve once the stream is ready to receive again. - /// - /// Note: this function is here for WASI 0.2 only. - /// It's planned to be removed when `future` is natively supported in Preview3. - @since(version = 0.2.0) - subscribe: func() -> pollable; - } - - @since(version = 0.2.0) - resource outgoing-datagram-stream { - /// Check readiness for sending. This function never blocks. - /// - /// Returns the number of datagrams permitted for the next call to `send`, - /// or an error. Calling `send` with more datagrams than this function has - /// permitted will trap. - /// - /// When this function returns ok(0), the `subscribe` pollable will - /// become ready when this function will report at least ok(1), or an - /// error. - /// - /// Never returns `would-block`. - check-send: func() -> result; - /// Send messages on the socket. - /// - /// This function attempts to send all provided `datagrams` on the socket without blocking and - /// returns how many messages were actually sent (or queued for sending). This function never - /// returns `error(would-block)`. If none of the datagrams were able to be sent, `ok(0)` is returned. - /// - /// This function semantically behaves the same as iterating the `datagrams` list and sequentially - /// sending each individual datagram until either the end of the list has been reached or the first error occurred. - /// If at least one datagram has been sent successfully, this function never returns an error. - /// - /// If the input list is empty, the function returns `ok(0)`. - /// - /// Each call to `send` must be permitted by a preceding `check-send`. Implementations must trap if - /// either `check-send` was not called or `datagrams` contains more items than `check-send` permitted. - /// - /// # Typical errors - /// - `invalid-argument`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) - /// - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL) - /// - `invalid-argument`: The port in `remote-address` is set to 0. (EDESTADDRREQ, EADDRNOTAVAIL) - /// - `invalid-argument`: The socket is in "connected" mode and `remote-address` is `some` value that does not match the address passed to `stream`. (EISCONN) - /// - `invalid-argument`: The socket is not "connected" and no value for `remote-address` was provided. (EDESTADDRREQ) - /// - `remote-unreachable`: The remote address is not reachable. (ECONNRESET, ENETRESET on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) - /// - `connection-refused`: The connection was refused. (ECONNREFUSED) - /// - `datagram-too-large`: The datagram is too large. (EMSGSIZE) - /// - /// # References - /// - - /// - - /// - - /// - - /// - - /// - - /// - - /// - - @since(version = 0.2.0) - send: func(datagrams: list) -> result; - /// Create a `pollable` which will resolve once the stream is ready to send again. - /// - /// Note: this function is here for WASI 0.2 only. - /// It's planned to be removed when `future` is natively supported in Preview3. - @since(version = 0.2.0) - subscribe: func() -> pollable; - } -} - -@since(version = 0.2.0) -interface udp-create-socket { - @since(version = 0.2.0) - use network.{network, error-code, ip-address-family}; - @since(version = 0.2.0) - use udp.{udp-socket}; - - /// Create a new UDP socket. - /// - /// Similar to `socket(AF_INET or AF_INET6, SOCK_DGRAM, IPPROTO_UDP)` in POSIX. - /// On IPv6 sockets, IPV6_V6ONLY is enabled by default and can't be configured otherwise. - /// - /// This function does not require a network capability handle. This is considered to be safe because - /// at time of creation, the socket is not bound to any `network` yet. Up to the moment `bind` is called, - /// the socket is effectively an in-memory configuration object, unable to communicate with the outside world. - /// - /// All sockets are non-blocking. Use the wasi-poll interface to block on asynchronous operations. - /// - /// # Typical errors - /// - `not-supported`: The specified `address-family` is not supported. (EAFNOSUPPORT) - /// - `new-socket-limit`: The new socket resource could not be created because of a system limit. (EMFILE, ENFILE) - /// - /// # References: - /// - - /// - - /// - - /// - - @since(version = 0.2.0) - create-udp-socket: func(address-family: ip-address-family) -> result; -} - -@since(version = 0.2.0) -world imports { - @since(version = 0.2.0) - import wasi:io/error@0.2.6; - @since(version = 0.2.0) - import network; - @since(version = 0.2.0) - import instance-network; - @since(version = 0.2.0) - import wasi:io/poll@0.2.6; - @since(version = 0.2.0) - import udp; - @since(version = 0.2.0) - import udp-create-socket; - @since(version = 0.2.0) - import wasi:io/streams@0.2.6; - @since(version = 0.2.0) - import wasi:clocks/monotonic-clock@0.2.6; - @since(version = 0.2.0) - import tcp; - @since(version = 0.2.0) - import tcp-create-socket; - @since(version = 0.2.0) - import ip-name-lookup; -} diff --git a/lib/.gitignore b/lib/.gitignore deleted file mode 100644 index adea07b..0000000 --- a/lib/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*.wasm -*.wasm.md diff --git a/make/components.mk b/make/components.mk new file mode 100644 index 0000000..3fa3bf6 --- /dev/null +++ b/make/components.mk @@ -0,0 +1,91 @@ +COMPONENTS_DIR := target/components + +COMPONENTS = $(sort $(foreach file,$(wildcard $(addprefix components/*/,wit/*.constants.wit *.properties *.wac *.wkg Cargo.toml)),$(word 2,$(subst /, ,$(file))))) + +.PHONY: clean-components +clean-components: clean-wit + rm -rf ${COMPONENTS_DIR} + +.PHONY: components +components: ${COMPONENTS_DIR}/interface.wasm $(foreach component,$(COMPONENTS),${COMPONENTS_DIR}/$(component)/$(component).wasm ${COMPONENTS_DIR}/$(component)/$(component).debug.wasm) + +define BUILD_COMPONENT + +.PHONY: components/$1 +components/$1: ${COMPONENTS_DIR}/$1/$1.wasm ${COMPONENTS_DIR}/$1/$1.debug.wasm + +ifneq ($(wildcard components/$1/wit/$1.constants.wit),) + +${COMPONENTS_DIR}/$1/$1.wasm: components/$1/wit/deps ${COMPONENTS_DIR}/$1/README.md | $(call tool,componentized-constants-cli) + $(CONSTANTS) --wit components/$1/wit -o ${COMPONENTS_DIR}/$1/$1.wasm + +${COMPONENTS_DIR}/$1/$1.debug.wasm: components/$1/wit/deps ${COMPONENTS_DIR}/$1/README.md | $(call tool,componentized-constants-cli) + $(CONSTANTS) --wit components/$1/wit -o ${COMPONENTS_DIR}/$1/$1.debug.wasm + +else ifneq ($(wildcard components/$1/$1.properties),) + +${COMPONENTS_DIR}/$1/$1.wasm: components/$1/$1.properties ${COMPONENTS_DIR}/$1/README.md | $(call tool,componentized-static-config-cli) + $(STATIC_CONFIG) -f components/$1/$1.properties -o ${COMPONENTS_DIR}/$1/$1.wasm + +${COMPONENTS_DIR}/$1/$1.debug.wasm: components/$1/$1.properties ${COMPONENTS_DIR}/$1/README.md | $(call tool,componentized-static-config-cli) + $(STATIC_CONFIG) -f components/$1/$1.properties -o ${COMPONENTS_DIR}/$1/$1.debug.wasm + +else ifneq ($(wildcard components/$1/$1.wac),) + +# the local packages the composition instantiates, e.g. `new local:latch-n2 { ... }` +WAC_DEPS_$1 := $$(shell grep -v '^\s*//' components/$1/$1.wac | grep -oE 'local:[a-z0-9-]+' | sed 's/^local://' | sort -u) + +${COMPONENTS_DIR}/$1/$1.wasm: components/$1/$1.wac $$(foreach component,$$(WAC_DEPS_$1),$${COMPONENTS_DIR}/$$(component)/$$(component).wasm) ${COMPONENTS_DIR}/$1/README.md | $(call tool,wac-cli) + $(WAC) compose $$(foreach component,$$(WAC_DEPS_$1),-d local:$$(component)=$${COMPONENTS_DIR}/$$(component)/$$(component).wasm) -o ${COMPONENTS_DIR}/$1/$1.wasm components/$1/$1.wac + +${COMPONENTS_DIR}/$1/$1.debug.wasm: components/$1/$1.wac $$(foreach component,$$(WAC_DEPS_$1),$${COMPONENTS_DIR}/$$(component)/$$(component).debug.wasm) ${COMPONENTS_DIR}/$1/README.md | $(call tool,wac-cli) + $(WAC) compose $$(foreach component,$$(WAC_DEPS_$1),-d local:$$(component)=$${COMPONENTS_DIR}/$$(component)/$$(component).debug.wasm) -o ${COMPONENTS_DIR}/$1/$1.debug.wasm components/$1/$1.wac + +else ifneq ($(wildcard components/$1/$1.wkg),) + +${COMPONENTS_DIR}/$1/$1.wasm: components/$1/$1.wkg ${COMPONENTS_DIR}/$1/README.md | $(call tool,wkg) + $(WKG) oci pull $(shell cat components/$1/$1.wkg 2> /dev/null | head -1) -o ${COMPONENTS_DIR}/$1/$1.wasm + +${COMPONENTS_DIR}/$1/$1.debug.wasm: components/$1/$1.wkg ${COMPONENTS_DIR}/$1/README.md | $(call tool,wkg) + $(WKG) oci pull $(shell cat components/$1/$1.wkg 2> /dev/null | tail -1 2> /dev/null) -o ${COMPONENTS_DIR}/$1/$1.debug.wasm + +# cargo is checked last, other strategies may have a Cargo.toml for tests of non-rust sources +else ifneq ($(wildcard components/$1/Cargo.toml),) + +# a binary is a wasi:cli/command, the wasm32-wasip2 target builds it into a component +ifneq ($(wildcard components/$1/src/main.rs),) + +${COMPONENTS_DIR}/$1/$1.wasm: Cargo.toml Cargo.lock components/wit/deps $(shell find components/$1 -type f) $(shell find crates -type f 2> /dev/null) ${COMPONENTS_DIR}/$1/README.md + cargo build -p $1 --target wasm32-wasip2 --release + cp target/wasm32-wasip2/release/$1.wasm ${COMPONENTS_DIR}/$1/$1.wasm + +${COMPONENTS_DIR}/$1/$1.debug.wasm: Cargo.toml Cargo.lock components/wit/deps $(shell find components/$1 -type f) $(shell find crates -type f 2> /dev/null) ${COMPONENTS_DIR}/$1/README.md + cargo build -p $1 --target wasm32-wasip2 + cp target/wasm32-wasip2/debug/$1.wasm ${COMPONENTS_DIR}/$1/$1.debug.wasm + +else + +${COMPONENTS_DIR}/$1/$1.wasm: Cargo.toml Cargo.lock components/wit/deps $(shell find components/$1 -type f) $(shell find crates -type f 2> /dev/null) ${COMPONENTS_DIR}/$1/README.md | $(call tool,wasm-tools) + cargo build -p $1 --target wasm32-unknown-unknown --release + $(WASM_TOOLS) component new target/wasm32-unknown-unknown/release/$(subst -,_,$1).wasm -o ${COMPONENTS_DIR}/$1/$1.wasm + +${COMPONENTS_DIR}/$1/$1.debug.wasm: Cargo.toml Cargo.lock components/wit/deps $(shell find components/$1 -type f) $(shell find crates -type f 2> /dev/null) ${COMPONENTS_DIR}/$1/README.md | $(call tool,wasm-tools) + cargo build --target wasm32-unknown-unknown -p $1 + $(WASM_TOOLS) component new target/wasm32-unknown-unknown/debug/$(subst -,_,$1).wasm -o ${COMPONENTS_DIR}/$1/$1.debug.wasm + +endif + +endif + +${COMPONENTS_DIR}/$1/README.md: components/$1/README.md + @mkdir -p ${COMPONENTS_DIR}/$1 + @cp components/$1/README.md ${COMPONENTS_DIR}/$1/README.md + +endef + +$(foreach component,$(COMPONENTS),$(eval $(call BUILD_COMPONENT,$(component)))) + +${COMPONENTS_DIR}/interface.wasm: wit/deps README.md | $(call tool,wkg) + @mkdir -p ${COMPONENTS_DIR} + $(WKG) build -o ${COMPONENTS_DIR}/interface.wasm + @cp README.md ${COMPONENTS_DIR}/README.md diff --git a/make/pinned.jq b/make/pinned.jq new file mode 100644 index 0000000..78fc559 --- /dev/null +++ b/make/pinned.jq @@ -0,0 +1,6 @@ +# generates target/tools/pinned.mk from `cargo metadata --no-deps` of tools/Cargo.toml, see make/tools.mk +.packages[0] as $tools +| ($tools.dependencies[].name | select($tools.metadata.bins[.] == null) | error("\(.) is missing from [package.metadata.bins] in tools/Cargo.toml")), + "TOOLS := \([$tools.dependencies[].name] | join(" "))", + "TOOLS_PINNED := \([$tools.dependencies[] | "\(.name)@\(.req | ltrimstr("="))"] | join(" "))", + ($tools.metadata.bins[][] | "\(ascii_upcase | gsub("-"; "_")) := $(TOOLS_BIN)/\(.)") diff --git a/make/publish.mk b/make/publish.mk new file mode 100644 index 0000000..2b3a472 --- /dev/null +++ b/make/publish.mk @@ -0,0 +1,15 @@ +# sign published components with cosign, `SIGN=false` to push without signing, e.g. to a local registry +SIGN ?= true +# append each published file and its image to this file, e.g. `gate.wasm ghcr.io/componentized/http/gate:0.1.0@sha256:...` +PUBLISH_LOG ?= + +# the files that can be published, e.g. gate.wasm, published from target/components/gate/gate.wasm +PUBLISH_FILES := interface.wasm $(foreach component,$(filter-out dep-% test-%,$(COMPONENTS)),$(component).wasm $(component).debug.wasm) + +.PHONY: publish ## Publish each component in the target/components directory +publish: $(addprefix publish-,$(PUBLISH_FILES)) + +.PHONY: $(addprefix publish-,$(PUBLISH_FILES)) +$(addprefix publish-,$(PUBLISH_FILES)): publish-%: | $(call tool,wkg) + @VERSION="$(VERSION)" REPOSITORY="$(REPOSITORY)" COMPONENTS_DIR="$(COMPONENTS_DIR)" SIGN="$(SIGN)" PUBLISH_LOG="$(PUBLISH_LOG)" \ + scripts/publish.sh $* diff --git a/make/tools.mk b/make/tools.mk new file mode 100644 index 0000000..f408f38 --- /dev/null +++ b/make/tools.mk @@ -0,0 +1,53 @@ +TOOLS_DIR := target/tools/$(shell rustc --print host-tuple) +# absolute, tools also run from other directories, e.g. `cd components && wkg fetch` +TOOLS_BIN := $(abspath $(TOOLS_DIR))/bin +# for the scripts and tests the recipes run, they look up the tools on the PATH +export PATH := $(TOOLS_BIN):$(PATH) + +# the tools pinned in tools/Cargo.toml, generated when it changes, make restarts after generating it. +# Defines TOOLS, TOOLS_PINNED with the version of each tool, e.g. `wkg@0.16.1`, and a variable for +# each binary a tool installs, e.g. `WKG := $(TOOLS_BIN)/wkg`. Recipes run the pinned tools by path, +# make 3.81 runs a simple command without a shell and looks it up on its own PATH, before the export +# above, which would find a tool already on the system. +TOOLS_MK := target/tools/pinned.mk +include $(TOOLS_MK) + +# cargo binstall downloads prebuilt binaries, without it the tools are built with cargo install +CARGO_INSTALL := $(if $(shell command -v cargo-binstall 2> /dev/null),cargo binstall --no-confirm --disable-telemetry,cargo install) + +.PHONY: clean-tools +clean-tools: + rm -rf ${TOOLS_DIR} + +# each tool is a dependency of the tools package, the binaries it installs are listed in +# [package.metadata.bins] +$(TOOLS_MK): tools/Cargo.toml make/pinned.jq + @mkdir -p $(@D) + @set -o pipefail; cargo metadata --manifest-path tools/Cargo.toml --no-deps --format-version 1 | jq -r -f make/pinned.jq > $@.tmp + @mv $@.tmp $@ + +tool_version = $(patsubst $(1)@%,%,$(filter $(1)@%,$(TOOLS_PINNED))) +# a stamp naming the version of a tool installed in $(TOOLS_DIR)/bin, e.g. `wkg@0.16.1`, the binary +# does not say which version it is. Bumping the pinned version names a stamp that does not exist yet, +# so the tool is installed again. +tool = $(TOOLS_DIR)/.installed/$(1)@$(call tool_version,$(1)) + +.PHONY: tools ## Install the cli tools pinned in tools/Cargo.toml +tools: $(foreach name,$(TOOLS),$(call tool,$(name))) + +.PHONY: tools-path ## Print the directory of the installed tools for this platform, to add to the PATH +tools-path: + @echo $(TOOLS_BIN) + +define INSTALL_TOOL + +$(call tool,$1): + $(CARGO_INSTALL) --locked --root $(TOOLS_DIR) --version $(call tool_version,$1) $1 + @mkdir -p $$(@D) + @# only the installed version has a stamp, so going back to a previous version installs it again + @rm -f $$(@D)/$1@* + @touch $$@ + +endef + +$(foreach name,$(TOOLS),$(eval $(call INSTALL_TOOL,$(name)))) diff --git a/make/wit.mk b/make/wit.mk new file mode 100644 index 0000000..f1e07d7 --- /dev/null +++ b/make/wit.mk @@ -0,0 +1,29 @@ +# a path relative to the root of the repository, e.g. `wit` for `components/../wit` +relpath = $(if $(filter $(CURDIR),$(abspath $(1))),.,$(patsubst $(CURDIR)/%,%,$(abspath $(1)))) + +# directories with a wkg.toml, each fetches the dependencies of its wit directory into wit/deps, +# e.g. `.` and `components` +WKG_DIRS := $(sort $(patsubst ./%,%,$(patsubst %/,%,$(dir $(shell find . -name wkg.toml -not -path './target/*' -not -path '*/deps/*'))))) + +# the wit/deps directory of a directory with a wkg.toml, e.g. `wit/deps` for `.` +wit_deps = $(patsubst ./%,%,$(1)/wit/deps) + +WIT_DEPS := $(foreach dir,$(WKG_DIRS),$(call wit_deps,$(dir))) + +.PHONY: clean-wit ## Remove the fetched wit dependencies, fetched again by `make wit` +clean-wit: + rm -rf $(WIT_DEPS) + +.PHONY: wit +wit: $(WIT_DEPS) + +define FETCH_WIT + +# a package overridden with a local path, e.g. `{ path = "../wit" }`, has its dependencies fetched first, +# an override without dependencies of its own is skipped, e.g. `{ path = "./wit/overrides/wasi-keyvalue" }` +$(call wit_deps,$1): $1/wkg.toml $1/wkg.lock $(shell find $1/wit -type f -name "*.wit" -not -path "*/deps/*") $(filter $(WIT_DEPS),$(foreach path,$(shell sed -n 's/.*path *= *"\(.*\)".*/\1/p' $1/wkg.toml),$(call relpath,$1/$(path))/deps)) | $(call tool,wkg) + $(if $(filter .,$1),,cd $1 && )$(WKG) fetch + +endef + +$(foreach dir,$(WKG_DIRS),$(eval $(call FETCH_WIT,$(dir)))) diff --git a/scripts/bump-version.sh b/scripts/bump-version.sh new file mode 100755 index 0000000..a3a9666 --- /dev/null +++ b/scripts/bump-version.sh @@ -0,0 +1,105 @@ +#!/usr/bin/env bash + +# Bump the version of the wit interface package, and of the crates. +# +# scripts/bump-version.sh +# +# Updates the package declaration and every reference to the package in tracked files, then +# refreshes the generated wit dependencies. The crates share the interface's version: the +# workspace version the crates inherit, and the workspace's requirement on the library, move to the +# new version too. Items whose `@since` names an unreleased (prerelease) +# version move to the new version, since they were never published under the old one. Items +# released under the old version keep their `@since`. +# +# 0.1.0-dev -> 0.1.0 releases 0.1.0, `@since(version = 0.1.0-dev)` becomes 0.1.0 +# 0.1.0 -> 0.2.0-dev starts 0.2.0, `@since(version = 0.1.0)` is unchanged + +set -euo pipefail + +cd "$(dirname "$0")/.." + +PACKAGE="${PACKAGE:-componentized:$(basename $(git rev-parse --show-toplevel))}" +# the library crate, the workspace's requirement on it moves to the new version +LIBRARY="${LIBRARY:-componentized-constants}" +SEMVER='^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$' + +new="${1:-}" +if [[ ! "$new" =~ $SEMVER ]]; then + echo "usage: $0 , e.g. 0.1.0 or 0.2.0-dev" >&2 + exit 1 +fi + +old=$(sed -n "s/^package ${PACKAGE}@\(.*\);$/\1/p" wit/worlds.wit) +if [[ -z "$old" ]]; then + echo "unable to find the ${PACKAGE} package declaration in wit/worlds.wit" >&2 + exit 1 +fi + +# succeeds when version $1 is lower than version $2, a prerelease is lower than its release +version_lt() { + local a_core="${1%%-*}" b_core="${2%%-*}" + local a_pre="" b_pre="" + [[ "$1" == *-* ]] && a_pre="${1#*-}" + [[ "$2" == *-* ]] && b_pre="${2#*-}" + if [[ "$a_core" != "$b_core" ]]; then + local IFS=. + local -a a=($a_core) b=($b_core) + for i in 0 1 2; do + (( a[i] < b[i] )) && return 0 + (( a[i] > b[i] )) && return 1 + done + fi + [[ -n "$a_pre" && -z "$b_pre" ]] && return 0 + [[ -z "$a_pre" && -n "$b_pre" ]] && return 1 + [[ -n "$a_pre" && "$a_pre" < "$b_pre" ]] +} + +if ! version_lt "$old" "$new"; then + echo "the new version ${new} must be greater than the current version ${old}" >&2 + exit 1 +fi + +# the bump-version workflow offers the current version as the default for the next bump, checked +# before changing anything +workflow=.github/workflows/bump-version.yaml +workflow_default="default: \"${old}\" # the current version, kept current by scripts/bump-version.sh" +if ! grep -qF "$workflow_default" "$workflow"; then + echo "unable to find the current version as the default in ${workflow}, expected: ${workflow_default}" >&2 + exit 1 +fi + +old_re="${old//./\\.}" +# references to the package or one of its interfaces, an interface named for a keyword is escaped +# with `%`, e.g. `componentized:constants/%u8@0.1.0` +ref_re="${PACKAGE}(/%?[a-z0-9-]+)?" +# the fetched wit dependencies and the wkg.lock files are left to `make wit`, wkg replaces the +# dependencies and updates the locks for the new version +files=$(git grep --untracked -l -E "${ref_re}@${old_re}" -- ':(exclude,glob)**/wit/deps/**' ':(exclude,glob)**/wkg.lock' || true) +for file in $files; do + sed -i.bak -E "s#(${ref_re})@${old_re}#\1@${new}#g" "$file" + rm "$file.bak" + echo "updated ${file}" +done + +if [[ "$old" == *-* ]]; then + files=$(git grep --untracked -l -F "@since(version = ${old})" -- 'wit/*.wit' || true) + for file in $files; do + sed -i.bak "s/@since(version = ${old_re})/@since(version = ${new})/g" "$file" + rm "$file.bak" + echo "updated @since in ${file}" + done +fi + +# the version in the [workspace.package] section, inherited by the crates +perl -pi -e 'if (/^\[workspace\.package\]/ .. /^\[(?!workspace\.package\])/) { s/^version = "[^"]*"/version = "'"${new}"'"/ }' Cargo.toml +echo "updated the workspace version in Cargo.toml" +perl -pi -e 's/^(\Q'"${LIBRARY}"'\E = \{.*\bversion = ")[^"]*(")/${1}'"${new}"'${2}/' Cargo.toml +echo "updated the ${LIBRARY} requirement in Cargo.toml" + +perl -pi -e 's{^(\s+)\Q'"${workflow_default}"'\E$}{${1}'"${workflow_default/\"${old}\"/\"${new}\"}"'}' "$workflow" +echo "updated the default version in ${workflow}" + +# regenerate the wit dependencies for the new version +make wit components test + +echo "bumped ${PACKAGE} from ${old} to ${new}" \ No newline at end of file diff --git a/scripts/init-devcontainer.sh b/scripts/init-devcontainer.sh new file mode 100755 index 0000000..7a6f39d --- /dev/null +++ b/scripts/init-devcontainer.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +# Setup script run once for new devcontainers to init the environment. + +set -euo pipefail + +# valkey-cli, to inspect the Valkey service the dev container runs with +sudo apt-get update && sudo apt-get install -y valkey-tools + +curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash + +cargo check + +echo "export \"PATH=$(make -s tools-path):${PATH}\"" >> ~/.bashrc +make tools diff --git a/scripts/publish.sh b/scripts/publish.sh new file mode 100755 index 0000000..669c112 --- /dev/null +++ b/scripts/publish.sh @@ -0,0 +1,106 @@ +#!/usr/bin/env bash + +# Publish a built component to an OCI registry, and sign it with cosign. +# +# VERSION= REPOSITORY= scripts/publish.sh +# +# The file is one of the files built into the components directory, components are in a directory +# of their own, the interface is not, e.g. gate.wasm from target/components/gate/gate.wasm and +# interface.wasm from target/components/interface.wasm. The interface is published to the +# repository, each component to a repository of its own under it, a debug build is tagged with a +# `_debug` suffix. +# +# interface.wasm -> ${REPOSITORY}:0.1.0 +# gate.wasm -> ${REPOSITORY}/gate:0.1.0 +# gate.debug.wasm -> ${REPOSITORY}/gate:0.1.0_debug +# +# VERSION the version to publish, a leading `v` is dropped from the tag, e.g. v0.1.0 +# REPOSITORY the repository to publish to, e.g. ghcr.io/componentized/http +# GITHUB_REPOSITORY the GitHub repository the components are built from, e.g. componentized/http +# COMPONENTS_DIR the directory the components are built into, defaults to target/components +# SIGN sign the published components with cosign, `false` to push without signing, +# e.g. to a local registry +# PUBLISH_LOG append the published file and its image to this file, e.g. +# `gate.wasm ghcr.io/componentized/http/gate:0.1.0@sha256:...` + +set -euo pipefail + +cd "$(dirname "$0")/.." + +file="${1:-}" +if [[ -z "$file" ]]; then + echo "usage: $0 , e.g. interface.wasm or gate.wasm" >&2 + exit 1 +fi +: "${VERSION:?VERSION is undefined}" +: "${REPOSITORY:?REPOSITORY is undefined}" +GITHUB_REPOSITORY="${GITHUB_REPOSITORY:-componentized/$(basename "$(git rev-parse --show-toplevel)")}" +COMPONENTS_DIR="${COMPONENTS_DIR:-target/components}" +SIGN="${SIGN:-true}" +PUBLISH_LOG="${PUBLISH_LOG:-}" + +component="${file%.wasm}" +component="${component%.debug}" +debug="" +[[ "$file" == *.debug.wasm ]] && debug=true + +if [[ "$file" == interface.wasm ]]; then + component_file="$file" + title="${GITHUB_REPOSITORY/\//:}" +else + component_file="${component}/${file}" + title="${GITHUB_REPOSITORY/\//:}-${component}" +fi +[[ -n "$debug" ]] && title="${title} (debug)" + +# the description is the line following the title in the readme +readme="${COMPONENTS_DIR}/$(dirname "$component_file")/README.md" +description=$(sed -n 3p "$readme") + +commit=$(git rev-parse HEAD) +revision="$commit" +git diff --quiet HEAD || revision="${commit}+dirty" +url="https://github.com/${GITHUB_REPOSITORY}/tree/${commit}" +[[ -f "components/${component}/README.md" ]] && url="${url}/components/${component}" + +component_version="$VERSION" +[[ -n "$debug" ]] && component_version="${VERSION}+debug" +tag="${component_version#v}" +tag="${tag//+/_}" + +if [[ "$file" == interface.wasm ]]; then + image="${REPOSITORY}:${tag}" +else + image="${REPOSITORY}/${component}:${tag}" +fi + +echo "::group::${file} -> ${image}" + +digest=$( + wkg oci push \ + --annotation "org.opencontainers.image.title=${title}" \ + --annotation "org.opencontainers.image.description=${description}" \ + --annotation "org.opencontainers.image.version=${component_version}" \ + --annotation "org.opencontainers.image.url=${url}" \ + --annotation "org.opencontainers.image.source=https://github.com/${GITHUB_REPOSITORY}.git" \ + --annotation "org.opencontainers.image.revision=${revision}" \ + --annotation "org.opencontainers.image.licenses=Apache-2.0" \ + "$image" \ + "${COMPONENTS_DIR}/${component_file}" \ + 2>&1 \ + | tee /dev/stderr \ + | grep -o 'sha256:[a-f0-9]\{64\}' +) + +if [[ -n "$PUBLISH_LOG" ]]; then + size=$(wc -c < "${COMPONENTS_DIR}/${component_file}" | tr -d ' ') + echo "${file} ${size} ${image}@${digest}" >> "$PUBLISH_LOG" +fi + +if [[ "$SIGN" == true ]]; then + cosign sign --yes "${image}@${digest}" +else + echo "Not signing ${image}@${digest}, SIGN=${SIGN}" +fi + +echo "::endgroup::" diff --git a/tools/Cargo.toml b/tools/Cargo.toml new file mode 100644 index 0000000..65632c5 --- /dev/null +++ b/tools/Cargo.toml @@ -0,0 +1,30 @@ +# The versions of the cli tools the build uses, installed by `make tools` with cargo binstall. +# +# This package is never built, it lists the tools as dependencies so dependabot bumps them. Each +# tool is pinned to an exact version with `=`, the Makefile installs that version. +[package] +name = "tools" +version = "0.0.0" +edition = "2024" +license = "Apache-2.0" +publish = false + +[lib] +path = "lib.rs" + +# the binaries each tool installs, the Makefile runs each by path from a variable named after it, +# e.g. `$(WASM_TOOLS)` for `wasm-tools` +[package.metadata.bins] +wac-cli = ["wac"] +wasm-tools = ["wasm-tools"] +wasmtime-cli = ["wasmtime"] +wkg = ["wkg"] + +[dependencies] +wac-cli = "=0.12.0" +wasm-tools = "=1.261.0" +wasmtime-cli = "=49.0.2" +wkg = "=0.16.1" + +# not a member of the repository's workspace +[workspace] diff --git a/tools/lib.rs b/tools/lib.rs new file mode 100644 index 0000000..d3769d7 --- /dev/null +++ b/tools/lib.rs @@ -0,0 +1 @@ +//! Never built, see `Cargo.toml`. From 4b6d7a24bdd66b101b23d6d02e08b5880d179afe Mon Sep 17 00:00:00 2001 From: Scott Andrews Date: Fri, 9 Oct 2026 00:08:05 -0400 Subject: [PATCH 2/2] fix tests to render hello response Signed-off-by: Scott Andrews --- components/dep-cli/src/main.rs | 11 +++++++++-- components/ops/src/resp/mod.rs | 3 +++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/components/dep-cli/src/main.rs b/components/dep-cli/src/main.rs index 1aa7dd7..c48e855 100644 --- a/components/dep-cli/src/main.rs +++ b/components/dep-cli/src/main.rs @@ -400,11 +400,13 @@ fn exec() -> Result<(), Error> { Value::String(item) => println!("- {item}"), Value::Integer(item) => println!("- {item}"), Value::BulkString(item) => println!("- {item}"), - _ => todo!(), + item => { + println!("- {}", indent(&item.to_string(), " ")) + } } } } - _ => todo!(), + value => println!("{key}:\n {}", indent(&value.to_string(), " ")), } } } @@ -569,6 +571,11 @@ fn exec() -> Result<(), Error> { Ok(()) } +/// Indents each line after the first, for multi-line values printed after a prefix. +fn indent(value: &str, padding: &str) -> String { + value.replace('\n', &format!("\n{padding}")) +} + impl fmt::Display for Value { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { diff --git a/components/ops/src/resp/mod.rs b/components/ops/src/resp/mod.rs index f987b1e..ba7ba4f 100644 --- a/components/ops/src/resp/mod.rs +++ b/components/ops/src/resp/mod.rs @@ -97,6 +97,7 @@ fn buf_encode(value: Value, buf: &mut Vec) { Value::Map(val) => { buf.push(b'%'); buf.extend_from_slice(val.len().to_string().as_bytes()); + buf.extend_from_slice(CRLF_BYTES); for (key, value) in val { buf_encode(key.into(), buf); buf_encode(value.into(), buf); @@ -105,6 +106,7 @@ fn buf_encode(value: Value, buf: &mut Vec) { Value::Set(val) => { buf.push(b'~'); buf.extend_from_slice(val.len().to_string().as_bytes()); + buf.extend_from_slice(CRLF_BYTES); for item in val { buf_encode(item.into(), buf); } @@ -112,6 +114,7 @@ fn buf_encode(value: Value, buf: &mut Vec) { Value::Push(val) => { buf.push(b'>'); buf.extend_from_slice(val.len().to_string().as_bytes()); + buf.extend_from_slice(CRLF_BYTES); for item in val { buf_encode(item.into(), buf); }