From 678eca995a447e34057440145298a7bedeffc76f Mon Sep 17 00:00:00 2001 From: Victor Adossi Date: Thu, 15 May 2025 13:18:47 +0900 Subject: [PATCH] feat(examples): add Hono HTTP server example This commit adds an example of a WASI-compliant HTTP server that is powered by Hono. Signed-off-by: Victor Adossi Co-authored-by: rajsite --- .github/workflows/main.yml | 2 + examples/components/README.md | 6 +- .../components/http-server-hono/.gitignore | 3 + .../components/http-server-hono/README.md | 345 ++++++++ .../interfaces/wasi-cli-environment.d.ts | 22 + .../wasi-clocks-monotonic-clock.d.ts | 10 + .../wasi-http-incoming-handler.d.ts | 18 + .../types/interfaces/wasi-http-types.d.ts | 753 ++++++++++++++++++ .../types/interfaces/wasi-io-error.d.ts | 10 + .../types/interfaces/wasi-io-poll.d.ts | 12 + .../types/interfaces/wasi-io-streams.d.ts | 43 + .../http-server-hono/generated/types/wit.d.ts | 16 + .../components/http-server-hono/package.json | 30 + .../http-server-hono/rollup.config.mjs | 12 + .../http-server-hono/scripts/demo.js | 78 ++ .../components/http-server-hono/src/app.ts | 18 + .../http-server-hono/src/component.ts | 14 + .../components/http-server-hono/tsconfig.json | 23 + .../http-server-hono/wit/component.wit | 10 + .../wit/deps/wasi-cli-0.2.3/package.wit | 246 ++++++ .../wit/deps/wasi-clocks-0.2.3/package.wit | 29 + .../deps/wasi-filesystem-0.2.3/package.wit | 158 ++++ .../wit/deps/wasi-http-0.2.3/package.wit | 721 +++++++++++++++++ .../wit/deps/wasi-io-0.2.3/package.wit | 48 ++ .../wit/deps/wasi-random-0.2.3/package.wit | 18 + .../wit/deps/wasi-sockets-0.2.3/package.wit | 179 +++++ examples/components/http-server-hono/wkg.lock | 21 + package.json | 3 +- 28 files changed, 2845 insertions(+), 3 deletions(-) create mode 100644 examples/components/http-server-hono/.gitignore create mode 100644 examples/components/http-server-hono/README.md create mode 100644 examples/components/http-server-hono/generated/types/interfaces/wasi-cli-environment.d.ts create mode 100644 examples/components/http-server-hono/generated/types/interfaces/wasi-clocks-monotonic-clock.d.ts create mode 100644 examples/components/http-server-hono/generated/types/interfaces/wasi-http-incoming-handler.d.ts create mode 100644 examples/components/http-server-hono/generated/types/interfaces/wasi-http-types.d.ts create mode 100644 examples/components/http-server-hono/generated/types/interfaces/wasi-io-error.d.ts create mode 100644 examples/components/http-server-hono/generated/types/interfaces/wasi-io-poll.d.ts create mode 100644 examples/components/http-server-hono/generated/types/interfaces/wasi-io-streams.d.ts create mode 100644 examples/components/http-server-hono/generated/types/wit.d.ts create mode 100644 examples/components/http-server-hono/package.json create mode 100644 examples/components/http-server-hono/rollup.config.mjs create mode 100644 examples/components/http-server-hono/scripts/demo.js create mode 100644 examples/components/http-server-hono/src/app.ts create mode 100644 examples/components/http-server-hono/src/component.ts create mode 100644 examples/components/http-server-hono/tsconfig.json create mode 100644 examples/components/http-server-hono/wit/component.wit create mode 100644 examples/components/http-server-hono/wit/deps/wasi-cli-0.2.3/package.wit create mode 100644 examples/components/http-server-hono/wit/deps/wasi-clocks-0.2.3/package.wit create mode 100644 examples/components/http-server-hono/wit/deps/wasi-filesystem-0.2.3/package.wit create mode 100644 examples/components/http-server-hono/wit/deps/wasi-http-0.2.3/package.wit create mode 100644 examples/components/http-server-hono/wit/deps/wasi-io-0.2.3/package.wit create mode 100644 examples/components/http-server-hono/wit/deps/wasi-random-0.2.3/package.wit create mode 100644 examples/components/http-server-hono/wit/deps/wasi-sockets-0.2.3/package.wit create mode 100644 examples/components/http-server-hono/wkg.lock diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f884740ff..e25b12b28 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -384,6 +384,8 @@ jobs: workspace: examples/components/http-hello-world - name: http-server-fetch-handler workspace: examples/components/http-server-fetch-handler + - name: http-server-hono + workspace: examples/components/http-server-hono steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 diff --git a/examples/components/README.md b/examples/components/README.md index 9251135a0..ee8200a3e 100644 --- a/examples/components/README.md +++ b/examples/components/README.md @@ -8,11 +8,12 @@ A brief description of the examples contained in this folder: | Example | Component Description | |------------------------------------------------------------|--------------------------------------------------------------------------------------------------| | [`add`](./add) | `export`s basic functionality with simple types | -| [`string-reverse`](./string-reverse) | `export`s basic functionality with a slightly more involved WIT interface and more complex types | -| [`string-reverse-upper`](./string-reverse-upper) | `import`s functionality to build more advanced computation to `export` | | [`http-hello-world`](./http-hello-world) | HTTP server using the [`wasi:http/incoming-handler`][wasi-http], the hard way. | | [`http-server-fetch-handler`](./http-server-fetch-handler) | HTTP server using standards-forward `fetch()` event handling built into [StarlingMonkey][sm] | +| [`http-server-hono`](./http-server-hono) | HTTP server using the standards-forward [Hono][hono] framework | | [`node-fetch`](./node-fetch) | Performs a HTTP request using `fetch()` | +| [`string-reverse-upper`](./string-reverse-upper) | `import`s functionality to build more advanced computation to `export` | +| [`string-reverse`](./string-reverse) | `export`s basic functionality with a slightly more involved WIT interface and more complex types | | [`webidl-book-library`](./webidl-book-library) | Showcases [WebIDL][webidl] support using [`webidl2wit`][webidl2wit] | [nodejs]: https://nodejs.org @@ -20,3 +21,4 @@ A brief description of the examples contained in this folder: [wasi-http]: https://github.com/WebAssembly/wasi-http [webidl]: https://developer.mozilla.org/en-US/docs/Glossary/WebIDL [webidl2wit]: https://github.com/wasi-gfx/webidl2wit +[hono]: https://hono.dev diff --git a/examples/components/http-server-hono/.gitignore b/examples/components/http-server-hono/.gitignore new file mode 100644 index 000000000..2dde7537c --- /dev/null +++ b/examples/components/http-server-hono/.gitignore @@ -0,0 +1,3 @@ +node_modules +dist +*.wasm diff --git a/examples/components/http-server-hono/README.md b/examples/components/http-server-hono/README.md new file mode 100644 index 000000000..771f3b4f7 --- /dev/null +++ b/examples/components/http-server-hono/README.md @@ -0,0 +1,345 @@ +# Hono HTTP server example + +This repository showcases using a WebAssembly component built with the Javascript WebAssembly Component +toolchain (`jco`) to respond to web requests using the [Hono][hono] web framework. + +## How it works + +Handling web requests is part of [WebAssembly System Interface (WASI)][wasi], under an interface called [`wasi:http`][wasi-http] +(in particular `wasi:http/incoming-handler`), thanks to [StarlingMonkey][sm] which is used when running `jco componentize`. + +Hono works with WASI without any major changes because it is extraordinarily *standards compliant*. +Since StarlingMonkey supports web standards (pushed forward by the [WinterCG/WinterTC][wintertc]), +Hono works easily with WASI standard compatible runtimes like StarlingMonkey. + +To *use* our request handling component from NodeJS environments, we can use `jco transpile` to run the +component. This serves as a "virtual" WebAssembly + WASI host (see [`@bytecodealliance/preview2-shim`][p2-shims]) +that handles incoming HTTP requests (`wasi:http/incoming-handler`). + +> [!NOTE] +> WebAssembly components are *not* the same as WebAssembly Modules (asm.js, emscripten, etc), +> they are much more powerful and support many more features. +> +> If you don't know what any of the above means, don't worry about it -- check out the [Component Model Book][cm-book], +> or keep following along and experiment! + +[hono]: https://hono.dev +[sm]: https://github.com/bytecodealliance/StarlingMonkey +[wasi]: https://github.com/WebAssembly/WASI/tree/main +[mdn-fetch]: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API +[wasi-http]: https://github.com/WebAssembly/wasi-http +[p2-shims]: https://www.npmjs.com/package/@bytecodealliance/preview2-shim +[cm-book]: https://component-model.bytecodealliance.org/ +[wintertc]: https://wintertc.org/ + +## Quickstart + +To build this example into a demo page that you can visit, run: + +```console +npm install +npm run all +``` +> [!NOTE] +> Feel free to replace `npm` with whatever npm-compatible tooling you prefer. + +The `all` script will: + +- Build the component +- Transpile it +- Serve the WebAssembly component (via `demo.js`), using the `jco serve` (do not use this in production!) +- Send a single request to trigger the output + +
+

Expected output

+ +```console +> http-server-hono@0.1.0 all +> npm run build && npm run demo + + +> http-server-hono@0.1.0 build +> npm run gen:types && npm run build:js && npm run build:component + + +> http-server-hono@0.1.0 gen:types +> jco guest-types wit -o generated/types + + + Generated Guest Typescript Definition Files (.d.ts): + + - generated/types/interfaces/wasi-cli-environment.d.ts 0.76 KiB + - generated/types/interfaces/wasi-clocks-monotonic-clock.d.ts 0.42 KiB + - generated/types/interfaces/wasi-http-incoming-handler.d.ts 0.92 KiB + - generated/types/interfaces/wasi-http-types.d.ts 26.8 KiB + - generated/types/interfaces/wasi-io-error.d.ts 0.19 KiB + - generated/types/interfaces/wasi-io-poll.d.ts 0.26 KiB + - generated/types/interfaces/wasi-io-streams.d.ts 1.3 KiB + - generated/types/wit.d.ts 1.19 KiB + + +> http-server-hono@0.1.0 build:js +> rollup -c + + +src/component.ts → dist/component.js... +(!) Circular dependency +../../../node_modules/hono/dist/request.js -> ../../../node_modules/hono/dist/utils/body.js -> ../../../node_modules/hono/dist/request.js +created dist/component.js in 1s + +> http-server-hono@0.1.0 build:component +> jco componentize -w wit -o dist/component.wasm dist/component.js + +ALL /* + logger2 +GET / + [handler] +OK Successfully written dist/component.wasm. + +> http-server-hono@0.1.0 demo +> node scripts/demo.js + +fetch() OUTPUT: +Hello world! +``` + +
+ +## Step-by-step + +Want to go through it step-by-step? Read along from here. + +### Install dependencies + +Similar to any other NodeJS project, you can install dependencies with `npm install`: + +```console +npm install +``` + +> [!NOTE] +> Feel free to replace `npm` with whatever npm-compatible tooling you prefer. + +### Install WIT dependencies + +> [!NOTE] +> This step is already done for you (the files are present in this repo). But the following steps are useful if you need to add / modify the `component.wit` and need to update the `wit/deps` to correspond. + +This project makes use of the [`wasi:http`][wasi-http] and [`wasi:cli`][wasi-cli] interfaces, and they have to be +installed from the central repository. + +If using [`wkg`][wkg] (standard ecosystem tooling for pulling WIT interfaces), you can do this in one step: + +```console +wkg wit fetch +``` + +> [!NOTE] +> Generally, files in `wit/deps` are usually third party dependencies managed by WebAssembly ecosystem tooling, +> contrasted with the first party WIT for your component (ex. `wit/compnent.wit`). + +[wkg]: https://github.com/bytecodealliance/wasm-pkg-tools/tree/main +[wasi-cli]: https://github.com/WebAssembly/wasi-cli + +### Build the WebAssembly component + +You can build the [Javascript code in `component.js`](./src/component.js) into a WebAssembly component by running: + +```console +npm run build +``` + +
+

Expected output

+ +You should see output like the following: + +```console +> http-server-hono@0.1.0 build +> npm run gen:types && npm run build:js && npm run build:component + + +> http-server-hono@0.1.0 gen:types +> jco guest-types wit -o generated/types + + + Generated Guest Typescript Definition Files (.d.ts): + + - generated/types/interfaces/wasi-cli-environment.d.ts 0.76 KiB + - generated/types/interfaces/wasi-clocks-monotonic-clock.d.ts 0.42 KiB + - generated/types/interfaces/wasi-http-incoming-handler.d.ts 0.92 KiB + - generated/types/interfaces/wasi-http-types.d.ts 26.8 KiB + - generated/types/interfaces/wasi-io-error.d.ts 0.19 KiB + - generated/types/interfaces/wasi-io-poll.d.ts 0.26 KiB + - generated/types/interfaces/wasi-io-streams.d.ts 1.3 KiB + - generated/types/wit.d.ts 1.19 KiB + + +> http-server-hono@0.1.0 build:js +> rollup -c + + +src/component.ts → dist/component.js... +(!) Circular dependency +../../../node_modules/hono/dist/request.js -> ../../../node_modules/hono/dist/utils/body.js -> ../../../node_modules/hono/dist/request.js +created dist/component.js in 1s + +> http-server-hono@0.1.0 build:component +> jco componentize -w wit -o dist/component.wasm dist/component.js + +ALL /* + logger2 +GET / + [handler] +OK Successfully written dist/component.wasm. +``` + +
+ +#### Aside: Components & WebAssembly System Interface (WASI) + +WebAssembly Components are built against the system interfaces available in [WASI][wasi]. + +For example, using a tool called [`wasm-tools`][wasm-tools] we can see the imports and exports +of the component we've just built: + +``` +wasm-tools component wit dist/component.wasm +``` + +You should see output that looks something like this: + +```wit +package root:component; + +world root { + import wasi:cli/environment@0.2.3; + import wasi:io/poll@0.2.3; + import wasi:clocks/monotonic-clock@0.2.3; + import wasi:io/error@0.2.3; + import wasi:io/streams@0.2.3; + import wasi:http/types@0.2.3; + import wasi:cli/stdin@0.2.3; + import wasi:cli/stdout@0.2.3; + import wasi:cli/stderr@0.2.3; + import wasi:cli/terminal-input@0.2.3; + import wasi:cli/terminal-output@0.2.3; + import wasi:cli/terminal-stdin@0.2.3; + import wasi:cli/terminal-stdout@0.2.3; + import wasi:cli/terminal-stderr@0.2.3; + import wasi:clocks/wall-clock@0.2.3; + import wasi:filesystem/types@0.2.3; + import wasi:filesystem/preopens@0.2.3; + import wasi:random/random@0.2.3; + import wasi:http/outgoing-handler@0.2.3; /// <---- This import is used by `fetch()`! + + export wasi:http/incoming-handler@0.2.3; /// <---- This export enables responding to HTTP requests +} + +/// ... elided ... +``` + +> [!NOTE] +> The *meaning* of all of these `import`s and `export`s is somewhat out of scope for this example, so we won't discuss +> further, but please check out the [Component Model Book][cm-book] for more details. + +[wasm-tools]: https://github.com/bytecodealliance/wasm-tools + +### Transpile the WebAssembly component for NodeJS + +As we noted earlier, WebAssembly Components are built against the system interfaces available in [WASI][wasi]. + +One of the benefits of using components and WASI is that we can *reimplement* those interfaces when +the platform changes (this is sometimes called "virtual platform layering"). The host running the WebAssembly +component can provide dependencies as necessary. + +Thanks to `jco transpile` we can take our WebAssembly component (or any other WebAssembly component) and use +it *on NodeJS*, by converting the WebAssembly component into code that `node` *can run today* and +[providing shims/polyfills][npm-p2-shim] for WASI functionality as necessary. + +In practice this means producing a bundle of JS + WebAssembly Modules that can run in NodeJS: + +```console +npm run transpile +``` + +
+

Expected output

+ +You should see output like the following: + +``` +> http-server-hono@0.1.0 transpile +> jco transpile -o dist/transpiled dist/component.wasm + + + Transpiled JS Component Files: + + - dist/transpiled/component.core.wasm 11.3 MiB + - dist/transpiled/component.core2.wasm 16.1 KiB + - dist/transpiled/component.core3.wasm 6.37 KiB + - dist/transpiled/component.d.ts 2.37 KiB + - dist/transpiled/component.js 265 KiB + - dist/transpiled/interfaces/wasi-cli-environment.d.ts 0.2 KiB + - dist/transpiled/interfaces/wasi-cli-stderr.d.ts 0.16 KiB + - dist/transpiled/interfaces/wasi-cli-stdin.d.ts 0.15 KiB + - dist/transpiled/interfaces/wasi-cli-stdout.d.ts 0.16 KiB + - dist/transpiled/interfaces/wasi-cli-terminal-input.d.ts 0.17 KiB + - dist/transpiled/interfaces/wasi-cli-terminal-output.d.ts 0.17 KiB + - dist/transpiled/interfaces/wasi-cli-terminal-stderr.d.ts 0.2 KiB + - dist/transpiled/interfaces/wasi-cli-terminal-stdin.d.ts 0.2 KiB + - dist/transpiled/interfaces/wasi-cli-terminal-stdout.d.ts 0.2 KiB + - dist/transpiled/interfaces/wasi-clocks-monotonic-clock.d.ts 0.37 KiB + - dist/transpiled/interfaces/wasi-clocks-wall-clock.d.ts 0.2 KiB + - dist/transpiled/interfaces/wasi-filesystem-preopens.d.ts 0.19 KiB + - dist/transpiled/interfaces/wasi-filesystem-types.d.ts 2.93 KiB + - dist/transpiled/interfaces/wasi-http-incoming-handler.d.ts 0.3 KiB + - dist/transpiled/interfaces/wasi-http-outgoing-handler.d.ts 0.47 KiB + - dist/transpiled/interfaces/wasi-http-types.d.ts 9.88 KiB + - dist/transpiled/interfaces/wasi-io-error.d.ts 0.18 KiB + - dist/transpiled/interfaces/wasi-io-poll.d.ts 0.25 KiB + - dist/transpiled/interfaces/wasi-io-streams.d.ts 1.14 KiB + - dist/transpiled/interfaces/wasi-random-random.d.ts 0.14 KiB +``` + +
+ +The most important file in the generated bundle is `dist/transpiled/component.js` -- this is +the entrypoint for a (NodeJS, browser)script that wants to use the component we've just built. + +[npm-p2-shim]: https://www.npmjs.com/package/@bytecodealliance/preview2-shim + +## Run the Demo + +To be able to use our transpiled component, the included [`demo.js` script](./scripts/demo.js) which uses `jco serve` +to serve the component. + +To run the demo: + +```console +npm run demo +``` + +
+

Expected output

+ +You should see output like the following: + +``` +> demo +> node demo.js + +fetch() OUTPUT: +Hello World +``` + +
+ +## Potential Issues + +Much of the Hono ecosystem *just works*, due to how Hono is built, so you can start to build more complex apps. + +Where you *might* run into trouble is building/bundling the following: + +- Leveraging [adapter-specific features](https://hono.dev/docs/helpers/adapter) where support has not been added (i.e. `env()`) +- Leveraging [third-party middleware](https://hono.dev/docs/middleware/third-party) or JS libraries that require runtime-specific features (Node built-ins, `node-gyp`, etc) diff --git a/examples/components/http-server-hono/generated/types/interfaces/wasi-cli-environment.d.ts b/examples/components/http-server-hono/generated/types/interfaces/wasi-cli-environment.d.ts new file mode 100644 index 000000000..a2b0bc15b --- /dev/null +++ b/examples/components/http-server-hono/generated/types/interfaces/wasi-cli-environment.d.ts @@ -0,0 +1,22 @@ +declare module 'wasi:cli/environment@0.2.3' { + /** + * 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. + */ + export function getEnvironment(): Array<[string, string]>; + /** + * Get the POSIX-style arguments to the program. + */ + export function getArguments(): Array; + /** + * Return a path that programs should use as their initial current working + * directory, interpreting `.` as shorthand for this. + */ + export function initialCwd(): string | undefined; +} diff --git a/examples/components/http-server-hono/generated/types/interfaces/wasi-clocks-monotonic-clock.d.ts b/examples/components/http-server-hono/generated/types/interfaces/wasi-clocks-monotonic-clock.d.ts new file mode 100644 index 000000000..9fab04f89 --- /dev/null +++ b/examples/components/http-server-hono/generated/types/interfaces/wasi-clocks-monotonic-clock.d.ts @@ -0,0 +1,10 @@ +/// +declare module 'wasi:clocks/monotonic-clock@0.2.3' { + export function now(): Instant; + export function resolution(): Duration; + export function subscribeInstant(when: Instant): Pollable; + export function subscribeDuration(when: Duration): Pollable; + export type Pollable = import('wasi:io/poll@0.2.3').Pollable; + export type Instant = bigint; + export type Duration = bigint; +} diff --git a/examples/components/http-server-hono/generated/types/interfaces/wasi-http-incoming-handler.d.ts b/examples/components/http-server-hono/generated/types/interfaces/wasi-http-incoming-handler.d.ts new file mode 100644 index 000000000..afe2b2881 --- /dev/null +++ b/examples/components/http-server-hono/generated/types/interfaces/wasi-http-incoming-handler.d.ts @@ -0,0 +1,18 @@ +/// +declare module 'wasi:http/incoming-handler@0.2.3' { + /** + * 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. + */ + export function handle(request: IncomingRequest, responseOut: ResponseOutparam): void; + export type IncomingRequest = import('wasi:http/types@0.2.3').IncomingRequest; + export type ResponseOutparam = import('wasi:http/types@0.2.3').ResponseOutparam; +} diff --git a/examples/components/http-server-hono/generated/types/interfaces/wasi-http-types.d.ts b/examples/components/http-server-hono/generated/types/interfaces/wasi-http-types.d.ts new file mode 100644 index 000000000..07384a279 --- /dev/null +++ b/examples/components/http-server-hono/generated/types/interfaces/wasi-http-types.d.ts @@ -0,0 +1,753 @@ +/// +/// +/// +/// +declare module 'wasi:http/types@0.2.3' { + /** + * 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. + */ + export function httpErrorCode(err: IoError): ErrorCode | undefined; + export type Duration = import('wasi:clocks/monotonic-clock@0.2.3').Duration; + export type InputStream = import('wasi:io/streams@0.2.3').InputStream; + export type OutputStream = import('wasi:io/streams@0.2.3').OutputStream; + export type IoError = import('wasi:io/error@0.2.3').Error; + export type Pollable = import('wasi:io/poll@0.2.3').Pollable; + /** + * This type corresponds to HTTP standard Methods. + */ + export type Method = MethodGet | MethodHead | MethodPost | MethodPut | MethodDelete | MethodConnect | MethodOptions | MethodTrace | MethodPatch | MethodOther; + export interface MethodGet { + tag: 'get', + } + export interface MethodHead { + tag: 'head', + } + export interface MethodPost { + tag: 'post', + } + export interface MethodPut { + tag: 'put', + } + export interface MethodDelete { + tag: 'delete', + } + export interface MethodConnect { + tag: 'connect', + } + export interface MethodOptions { + tag: 'options', + } + export interface MethodTrace { + tag: 'trace', + } + export interface MethodPatch { + tag: 'patch', + } + export interface MethodOther { + tag: 'other', + val: string, + } + /** + * This type corresponds to HTTP standard Related Schemes. + */ + export type Scheme = SchemeHttp | SchemeHttps | SchemeOther; + export interface SchemeHttp { + tag: 'HTTP', + } + export interface SchemeHttps { + tag: 'HTTPS', + } + export interface SchemeOther { + tag: 'other', + val: string, + } + /** + * Defines the case payload type for `DNS-error` above: + */ + export interface DnsErrorPayload { + rcode?: string, + infoCode?: number, + } + /** + * Defines the case payload type for `TLS-alert-received` above: + */ + export interface TlsAlertReceivedPayload { + alertId?: number, + alertMessage?: string, + } + /** + * Defines the case payload type for `HTTP-response-{header,trailer}-size` above: + */ + export interface FieldSizePayload { + fieldName?: string, + fieldSize?: number, + } + /** + * These cases are inspired by the IANA HTTP Proxy Error Types: + * + */ + export type ErrorCode = ErrorCodeDnsTimeout | ErrorCodeDnsError | ErrorCodeDestinationNotFound | ErrorCodeDestinationUnavailable | ErrorCodeDestinationIpProhibited | ErrorCodeDestinationIpUnroutable | ErrorCodeConnectionRefused | ErrorCodeConnectionTerminated | ErrorCodeConnectionTimeout | ErrorCodeConnectionReadTimeout | ErrorCodeConnectionWriteTimeout | ErrorCodeConnectionLimitReached | ErrorCodeTlsProtocolError | ErrorCodeTlsCertificateError | ErrorCodeTlsAlertReceived | ErrorCodeHttpRequestDenied | ErrorCodeHttpRequestLengthRequired | ErrorCodeHttpRequestBodySize | ErrorCodeHttpRequestMethodInvalid | ErrorCodeHttpRequestUriInvalid | ErrorCodeHttpRequestUriTooLong | ErrorCodeHttpRequestHeaderSectionSize | ErrorCodeHttpRequestHeaderSize | ErrorCodeHttpRequestTrailerSectionSize | ErrorCodeHttpRequestTrailerSize | ErrorCodeHttpResponseIncomplete | ErrorCodeHttpResponseHeaderSectionSize | ErrorCodeHttpResponseHeaderSize | ErrorCodeHttpResponseBodySize | ErrorCodeHttpResponseTrailerSectionSize | ErrorCodeHttpResponseTrailerSize | ErrorCodeHttpResponseTransferCoding | ErrorCodeHttpResponseContentCoding | ErrorCodeHttpResponseTimeout | ErrorCodeHttpUpgradeFailed | ErrorCodeHttpProtocolError | ErrorCodeLoopDetected | ErrorCodeConfigurationError | ErrorCodeInternalError; + export interface ErrorCodeDnsTimeout { + tag: 'DNS-timeout', + } + export interface ErrorCodeDnsError { + tag: 'DNS-error', + val: DnsErrorPayload, + } + export interface ErrorCodeDestinationNotFound { + tag: 'destination-not-found', + } + export interface ErrorCodeDestinationUnavailable { + tag: 'destination-unavailable', + } + export interface ErrorCodeDestinationIpProhibited { + tag: 'destination-IP-prohibited', + } + export interface ErrorCodeDestinationIpUnroutable { + tag: 'destination-IP-unroutable', + } + export interface ErrorCodeConnectionRefused { + tag: 'connection-refused', + } + export interface ErrorCodeConnectionTerminated { + tag: 'connection-terminated', + } + export interface ErrorCodeConnectionTimeout { + tag: 'connection-timeout', + } + export interface ErrorCodeConnectionReadTimeout { + tag: 'connection-read-timeout', + } + export interface ErrorCodeConnectionWriteTimeout { + tag: 'connection-write-timeout', + } + export interface ErrorCodeConnectionLimitReached { + tag: 'connection-limit-reached', + } + export interface ErrorCodeTlsProtocolError { + tag: 'TLS-protocol-error', + } + export interface ErrorCodeTlsCertificateError { + tag: 'TLS-certificate-error', + } + export interface ErrorCodeTlsAlertReceived { + tag: 'TLS-alert-received', + val: TlsAlertReceivedPayload, + } + export interface ErrorCodeHttpRequestDenied { + tag: 'HTTP-request-denied', + } + export interface ErrorCodeHttpRequestLengthRequired { + tag: 'HTTP-request-length-required', + } + export interface ErrorCodeHttpRequestBodySize { + tag: 'HTTP-request-body-size', + val: bigint | undefined, + } + export interface ErrorCodeHttpRequestMethodInvalid { + tag: 'HTTP-request-method-invalid', + } + export interface ErrorCodeHttpRequestUriInvalid { + tag: 'HTTP-request-URI-invalid', + } + export interface ErrorCodeHttpRequestUriTooLong { + tag: 'HTTP-request-URI-too-long', + } + export interface ErrorCodeHttpRequestHeaderSectionSize { + tag: 'HTTP-request-header-section-size', + val: number | undefined, + } + export interface ErrorCodeHttpRequestHeaderSize { + tag: 'HTTP-request-header-size', + val: FieldSizePayload | undefined, + } + export interface ErrorCodeHttpRequestTrailerSectionSize { + tag: 'HTTP-request-trailer-section-size', + val: number | undefined, + } + export interface ErrorCodeHttpRequestTrailerSize { + tag: 'HTTP-request-trailer-size', + val: FieldSizePayload, + } + export interface ErrorCodeHttpResponseIncomplete { + tag: 'HTTP-response-incomplete', + } + export interface ErrorCodeHttpResponseHeaderSectionSize { + tag: 'HTTP-response-header-section-size', + val: number | undefined, + } + export interface ErrorCodeHttpResponseHeaderSize { + tag: 'HTTP-response-header-size', + val: FieldSizePayload, + } + export interface ErrorCodeHttpResponseBodySize { + tag: 'HTTP-response-body-size', + val: bigint | undefined, + } + export interface ErrorCodeHttpResponseTrailerSectionSize { + tag: 'HTTP-response-trailer-section-size', + val: number | undefined, + } + export interface ErrorCodeHttpResponseTrailerSize { + tag: 'HTTP-response-trailer-size', + val: FieldSizePayload, + } + export interface ErrorCodeHttpResponseTransferCoding { + tag: 'HTTP-response-transfer-coding', + val: string | undefined, + } + export interface ErrorCodeHttpResponseContentCoding { + tag: 'HTTP-response-content-coding', + val: string | undefined, + } + export interface ErrorCodeHttpResponseTimeout { + tag: 'HTTP-response-timeout', + } + export interface ErrorCodeHttpUpgradeFailed { + tag: 'HTTP-upgrade-failed', + } + export interface ErrorCodeHttpProtocolError { + tag: 'HTTP-protocol-error', + } + export interface ErrorCodeLoopDetected { + tag: 'loop-detected', + } + export interface ErrorCodeConfigurationError { + tag: '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. + */ + export interface ErrorCodeInternalError { + tag: 'internal-error', + val: string | undefined, + } + /** + * This type enumerates the different kinds of errors that may occur when + * setting or appending to a `fields` resource. + */ + export type HeaderError = HeaderErrorInvalidSyntax | HeaderErrorForbidden | HeaderErrorImmutable; + /** + * This error indicates that a `field-name` or `field-value` was + * syntactically invalid when used with an operation that sets headers in a + * `fields`. + */ + export interface HeaderErrorInvalidSyntax { + tag: 'invalid-syntax', + } + /** + * This error indicates that a forbidden `field-name` was used when trying + * to set a header in a `fields`. + */ + export interface HeaderErrorForbidden { + tag: 'forbidden', + } + /** + * This error indicates that the operation on the `fields` was not + * permitted because the fields are immutable. + */ + export interface HeaderErrorImmutable { + tag: '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. + */ + export type FieldKey = 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. + */ + export type FieldName = FieldKey; + /** + * 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. + */ + export type FieldValue = Uint8Array; + /** + * Headers is an alias for Fields. + */ + export type Headers = Fields; + /** + * Trailers is an alias for Fields. + */ + export type Trailers = Fields; + /** + * This type corresponds to the HTTP standard Status Code. + */ + export type StatusCode = number; + export type Result = { tag: 'ok', val: T } | { tag: 'err', val: E }; + + export class Fields { + /** + * Construct an empty HTTP Fields. + * + * The resulting `fields` is mutable. + */ + 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. + */ + static fromList(entries: Array<[FieldName, FieldValue]>): Fields; + /** + * 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. + */ + get(name: FieldName): Array; + /** + * Returns `true` when the name is present in this `fields`. If the name is + * syntactically invalid, `false` is returned. + */ + has(name: FieldName): boolean; + /** + * 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. + */ + set(name: FieldName, value: Array): void; + /** + * 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. + */ + 'delete'(name: FieldName): void; + /** + * 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. + */ + append(name: FieldName, value: FieldValue): void; + /** + * 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. + */ + entries(): Array<[FieldName, FieldValue]>; + /** + * 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. + */ + clone(): Fields; + } + + export class FutureIncomingResponse { + /** + * This type does not have a public constructor. + */ + private constructor(); + /** + * 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`. + */ + subscribe(): 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. + */ + get(): Result, void> | undefined; + } + + export class FutureTrailers { + /** + * This type does not have a public constructor. + */ + private constructor(); + /** + * 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`. + */ + subscribe(): 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. + */ + get(): Result, void> | undefined; + } + + export class IncomingBody { + /** + * This type does not have a public constructor. + */ + private constructor(); + /** + * 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. + */ + stream(): InputStream; + /** + * Takes ownership of `incoming-body`, and returns a `future-trailers`. + * This function will trap if the `input-stream` child is still alive. + */ + static finish(this_: IncomingBody): FutureTrailers; + } + + export class IncomingRequest { + /** + * This type does not have a public constructor. + */ + private constructor(); + /** + * Returns the method of the incoming request. + */ + method(): Method; + /** + * Returns the path with query parameters from the request, as a string. + */ + pathWithQuery(): string | undefined; + /** + * Returns the protocol scheme from the request. + */ + scheme(): Scheme | undefined; + /** + * Returns the authority of the Request's target URI, if present. + */ + authority(): string | undefined; + /** + * 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. + */ + headers(): Headers; + /** + * Gives the `incoming-body` associated with this request. Will only + * return success at most once, and subsequent calls will return error. + */ + consume(): IncomingBody; + } + + export class IncomingResponse { + /** + * This type does not have a public constructor. + */ + private constructor(); + /** + * Returns the status code from the incoming response. + */ + status(): StatusCode; + /** + * 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. + */ + headers(): Headers; + /** + * Returns the incoming body. May be called at most once. Returns error + * if called additional times. + */ + consume(): IncomingBody; + } + + export class OutgoingBody { + /** + * This type does not have a public constructor. + */ + private constructor(); + /** + * 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. + */ + write(): OutputStream; + /** + * 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. + */ + static finish(this_: OutgoingBody, trailers: Trailers | undefined): void; + } + + export class OutgoingRequest { + /** + * 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`. + */ + 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. + */ + body(): OutgoingBody; + /** + * Get the Method for the Request. + */ + method(): Method; + /** + * Set the Method for the Request. Fails if the string present in a + * `method.other` argument is not a syntactically valid method. + */ + setMethod(method: Method): void; + /** + * Get the combination of the HTTP Path and Query for the Request. + * When `none`, this represents an empty Path and empty Query. + */ + pathWithQuery(): string | undefined; + /** + * 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. + */ + setPathWithQuery(pathWithQuery: string | undefined): void; + /** + * Get the HTTP Related Scheme for the Request. When `none`, the + * implementation may choose an appropriate default scheme. + */ + scheme(): Scheme | undefined; + /** + * 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. + */ + setScheme(scheme: Scheme | undefined): void; + /** + * 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. + */ + authority(): string | undefined; + /** + * 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. + */ + setAuthority(authority: string | undefined): void; + /** + * 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`. + */ + headers(): Headers; + } + + export class OutgoingResponse { + /** + * 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. + */ + constructor(headers: Headers) + /** + * Get the HTTP Status Code for the Response. + */ + statusCode(): StatusCode; + /** + * Set the HTTP Status Code for the Response. Fails if the status-code + * given is not a valid http status code. + */ + setStatusCode(statusCode: StatusCode): void; + /** + * 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`. + */ + headers(): 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. + */ + body(): OutgoingBody; + } + + export class RequestOptions { + /** + * Construct a default `request-options` value. + */ + constructor() + /** + * The timeout for the initial connect to the HTTP Server. + */ + connectTimeout(): Duration | undefined; + /** + * Set the timeout for the initial connect to the HTTP Server. An error + * return value indicates that this timeout is not supported. + */ + setConnectTimeout(duration: Duration | undefined): void; + /** + * The timeout for receiving the first byte of the Response body. + */ + firstByteTimeout(): Duration | undefined; + /** + * Set the timeout for receiving the first byte of the Response body. An + * error return value indicates that this timeout is not supported. + */ + setFirstByteTimeout(duration: Duration | undefined): void; + /** + * The timeout for receiving subsequent chunks of bytes in the Response + * body stream. + */ + betweenBytesTimeout(): Duration | undefined; + /** + * 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. + */ + setBetweenBytesTimeout(duration: Duration | undefined): void; + } + + export class ResponseOutparam { + /** + * This type does not have a public constructor. + */ + private constructor(); + /** + * 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. + */ + static set(param: ResponseOutparam, response: Result): void; + } +} diff --git a/examples/components/http-server-hono/generated/types/interfaces/wasi-io-error.d.ts b/examples/components/http-server-hono/generated/types/interfaces/wasi-io-error.d.ts new file mode 100644 index 000000000..a9775a06e --- /dev/null +++ b/examples/components/http-server-hono/generated/types/interfaces/wasi-io-error.d.ts @@ -0,0 +1,10 @@ +declare module 'wasi:io/error@0.2.3' { + + export class Error { + /** + * This type does not have a public constructor. + */ + private constructor(); + toDebugString(): string; + } +} diff --git a/examples/components/http-server-hono/generated/types/interfaces/wasi-io-poll.d.ts b/examples/components/http-server-hono/generated/types/interfaces/wasi-io-poll.d.ts new file mode 100644 index 000000000..6c7d4bab9 --- /dev/null +++ b/examples/components/http-server-hono/generated/types/interfaces/wasi-io-poll.d.ts @@ -0,0 +1,12 @@ +declare module 'wasi:io/poll@0.2.3' { + export function poll(in_: Array): Uint32Array; + + export class Pollable { + /** + * This type does not have a public constructor. + */ + private constructor(); + ready(): boolean; + block(): void; + } +} diff --git a/examples/components/http-server-hono/generated/types/interfaces/wasi-io-streams.d.ts b/examples/components/http-server-hono/generated/types/interfaces/wasi-io-streams.d.ts new file mode 100644 index 000000000..2708b9c7d --- /dev/null +++ b/examples/components/http-server-hono/generated/types/interfaces/wasi-io-streams.d.ts @@ -0,0 +1,43 @@ +/// +/// +declare module 'wasi:io/streams@0.2.3' { + export type Error = import('wasi:io/error@0.2.3').Error; + export type Pollable = import('wasi:io/poll@0.2.3').Pollable; + export type StreamError = StreamErrorLastOperationFailed | StreamErrorClosed; + export interface StreamErrorLastOperationFailed { + tag: 'last-operation-failed', + val: Error, + } + export interface StreamErrorClosed { + tag: 'closed', + } + + export class InputStream { + /** + * This type does not have a public constructor. + */ + private constructor(); + read(len: bigint): Uint8Array; + blockingRead(len: bigint): Uint8Array; + skip(len: bigint): bigint; + blockingSkip(len: bigint): bigint; + subscribe(): Pollable; + } + + export class OutputStream { + /** + * This type does not have a public constructor. + */ + private constructor(); + checkWrite(): bigint; + write(contents: Uint8Array): void; + blockingWriteAndFlush(contents: Uint8Array): void; + flush(): void; + blockingFlush(): void; + subscribe(): Pollable; + writeZeroes(len: bigint): void; + blockingWriteZeroesAndFlush(len: bigint): void; + splice(src: InputStream, len: bigint): bigint; + blockingSplice(src: InputStream, len: bigint): bigint; + } +} diff --git a/examples/components/http-server-hono/generated/types/wit.d.ts b/examples/components/http-server-hono/generated/types/wit.d.ts new file mode 100644 index 000000000..1094d4739 --- /dev/null +++ b/examples/components/http-server-hono/generated/types/wit.d.ts @@ -0,0 +1,16 @@ +/// +/// +/// +/// +/// +/// +/// +declare module 'example:hono/component' { + export type * as WasiCliEnvironment023 from 'wasi:cli/environment@0.2.3'; // import wasi:cli/environment@0.2.3 + export type * as WasiClocksMonotonicClock023 from 'wasi:clocks/monotonic-clock@0.2.3'; // import wasi:clocks/monotonic-clock@0.2.3 + export type * as WasiHttpTypes023 from 'wasi:http/types@0.2.3'; // import wasi:http/types@0.2.3 + export type * as WasiIoError023 from 'wasi:io/error@0.2.3'; // import wasi:io/error@0.2.3 + export type * as WasiIoPoll023 from 'wasi:io/poll@0.2.3'; // import wasi:io/poll@0.2.3 + export type * as WasiIoStreams023 from 'wasi:io/streams@0.2.3'; // import wasi:io/streams@0.2.3 + export * as incomingHandler from 'wasi:http/incoming-handler@0.2.3'; // export wasi:http/incoming-handler@0.2.3 +} diff --git a/examples/components/http-server-hono/package.json b/examples/components/http-server-hono/package.json new file mode 100644 index 000000000..e56932d54 --- /dev/null +++ b/examples/components/http-server-hono/package.json @@ -0,0 +1,30 @@ +{ + "name": "http-server-hono", + "description": "Example of a WASI-enabled WebAsssembly HTTP server powered by Hono", + "private": true, + "main": "src/component.ts", + "type": "module", + "scripts": { + "gen:types": "jco guest-types wit -o generated/types", + "build:js": "rollup -c", + "build:component": "jco componentize -w wit -o dist/component.wasm dist/component.js", + "build": "npm run gen:types && npm run build:js && npm run build:component", + "transpile": "jco transpile -o dist/transpiled dist/component.wasm", + "serve": "jco serve dist/compnent.wasm", + "demo": "node scripts/demo.js", + "all": "npm run build && npm run demo" + }, + "license": "MIT", + "devDependencies": { + "@bytecodealliance/componentize-js": "^0.18.2", + "@bytecodealliance/jco": "^1.11.2", + "@rollup/plugin-node-resolve": "^16.0.1", + "@rollup/plugin-typescript": "^12.1.2", + "rollup": "^4.40.2", + "terminate": "^2.8.0", + "typescript": "^5.8.3" + }, + "dependencies": { + "hono": "^4.7.9" + } +} diff --git a/examples/components/http-server-hono/rollup.config.mjs b/examples/components/http-server-hono/rollup.config.mjs new file mode 100644 index 000000000..6ee6bc0ef --- /dev/null +++ b/examples/components/http-server-hono/rollup.config.mjs @@ -0,0 +1,12 @@ +import typescript from "@rollup/plugin-typescript"; +import nodeResolve from "@rollup/plugin-node-resolve"; + +export default { + input: "src/component.ts", + external: /wasi:.*/, + output: { + file: "dist/component.js", + format: "esm", + }, + plugins: [typescript({ noEmitOnError: true }), nodeResolve()], +}; diff --git a/examples/components/http-server-hono/scripts/demo.js b/examples/components/http-server-hono/scripts/demo.js new file mode 100644 index 000000000..0834695de --- /dev/null +++ b/examples/components/http-server-hono/scripts/demo.js @@ -0,0 +1,78 @@ +/** + * This file contains a demo implementation that serves the built HTTP server WebAssembly component + * in this project on localhost. + * + * To serve the component, we use `jco serve`, given that it is a dependency. + */ + +import { fileURLToPath } from "node:url"; +import { createServer as createNetServer } from "node:net"; +import { env } from "node:process"; +import { stat } from "node:fs/promises"; +import { spawn } from "node:child_process"; + +import terminate from "terminate"; + +/** Where to find Jco as an executable */ +const JCO_PATH = env.JCO_PATH ?? "jco"; + +/** Path to the WASM file to be used */ +const WASM_PATH = fileURLToPath( + new URL(env.WASM_PATH ?? "../dist/component.wasm", import.meta.url), +); + +async function main() { + // Determine paths to jco and output wasm + const wasmPathExists = await stat(WASM_PATH) + .then((p) => p.isFile()) + .catch(() => false); + if (!wasmPathExists) { + throw new Error( + `Missing/invalid Wasm binary @ [${WASM_PATH}] (has 'npm run build' been run?)`, + ); + } + + // Generate a random port + const randomPort = await getRandomPort(); + + // Spawn jco serve + const proc = spawn(JCO_PATH, ["serve", "--port", randomPort, WASM_PATH], { + detached: false, + stdio: "pipe", + shell: false, + }); + + // Wait for the server to start + await new Promise((resolve) => { + proc.stderr.on("data", (data) => { + if (data.includes("Server listening")) { + resolve(); + } + }); + }); + + // Execute the WASM module running via jco serve + try { + const resp = await fetch(`http://localhost:${randomPort}`); + const respText = await resp.text(); + console.log(`fetch() OUTPUT:\n${respText}`); + } catch (err) { + throw err; + } finally { + await terminate(proc.pid); + } +} + +// Utility function for getting a random port +export async function getRandomPort() { + return await new Promise((resolve) => { + const server = createNetServer(); + server.listen(0, function () { + const port = this.address().port; + server.on("close", () => resolve(port)); + server.close(); + }); + }); +} + +await main(); diff --git a/examples/components/http-server-hono/src/app.ts b/examples/components/http-server-hono/src/app.ts new file mode 100644 index 000000000..61b78b2b6 --- /dev/null +++ b/examples/components/http-server-hono/src/app.ts @@ -0,0 +1,18 @@ +import { Hono } from "hono"; +import { showRoutes } from "hono/dev"; +import { logger } from "hono/logger"; + +export const app = new Hono(); + +app.use(logger()); + +app.get("/", async (c) => { + return c.text("Hello world!"); +}); + +// showRoutes() logs all the routes available, +// but this line only runs once during component build, due +// to component optimization intricacies (wizer) +showRoutes(app, { + verbose: true, +}); diff --git a/examples/components/http-server-hono/src/component.ts b/examples/components/http-server-hono/src/component.ts new file mode 100644 index 000000000..fef4e9714 --- /dev/null +++ b/examples/components/http-server-hono/src/component.ts @@ -0,0 +1,14 @@ +import { app } from "./app.js"; + +/** + * Register the Hono application with the global fetch listener as supported by the underlying StarlingMonkey JS runtime. + * + * Since both Hono and StarlingMonkey are aligned Web Standards (WinterCG/WinterTC), + * this enables Hono to run smoothly in WASI-enabled (`wasi:http`) Webassembly environments. + * + * See: https://github.com/bytecodealliance/ComponentizeJS#using-starlingmonkeys-fetch-event + * See: https://hono.dev/docs/concepts/web-standard + * See: https://wintertc.org/ + * See: https://github.com/WebAssembly/wasi-http + */ +app.fire(); diff --git a/examples/components/http-server-hono/tsconfig.json b/examples/components/http-server-hono/tsconfig.json new file mode 100644 index 000000000..ed2ec9b88 --- /dev/null +++ b/examples/components/http-server-hono/tsconfig.json @@ -0,0 +1,23 @@ +{ + /* Visit https://aka.ms/tsconfig to read more about this file */ + "compilerOptions": { + /* Language and Environment */ + "target": "esnext", + "module": "nodenext", + "moduleResolution": "nodenext", + "esModuleInterop": true, + + /* Source code */ + "types": [ + "./generated/types/wit.d.ts", + ], + "allowJs": false, + + /* Generation */ + "noEmit": true, + "forceConsistentCasingInFileNames": true, + + /* Type Checking */ + "strict": true + } +} diff --git a/examples/components/http-server-hono/wit/component.wit b/examples/components/http-server-hono/wit/component.wit new file mode 100644 index 000000000..16c815de4 --- /dev/null +++ b/examples/components/http-server-hono/wit/component.wit @@ -0,0 +1,10 @@ +package example:hono; + +world component { + import wasi:cli/environment@0.2.3; + export wasi:http/incoming-handler@0.2.3; + + // TODO(FIX): v0.2.4 wasi interfaces can be leveraged after componentize-js v0.18.3 is used by jco + // import wasi:cli/environment@0.2.4; + // export wasi:http/incoming-handler@0.2.4; +} \ No newline at end of file diff --git a/examples/components/http-server-hono/wit/deps/wasi-cli-0.2.3/package.wit b/examples/components/http-server-hono/wit/deps/wasi-cli-0.2.3/package.wit new file mode 100644 index 000000000..b86dfece5 --- /dev/null +++ b/examples/components/http-server-hono/wit/deps/wasi-cli-0.2.3/package.wit @@ -0,0 +1,246 @@ +package wasi:cli@0.2.3; + +@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); +} + +@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.3.{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.3.{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.3.{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.3; + @since(version = 0.2.0) + import wasi:io/poll@0.2.3; + @since(version = 0.2.0) + import wasi:io/streams@0.2.3; + @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.3; + @since(version = 0.2.0) + import wasi:clocks/wall-clock@0.2.3; + @since(version = 0.2.0) + import wasi:filesystem/types@0.2.3; + @since(version = 0.2.0) + import wasi:filesystem/preopens@0.2.3; + @since(version = 0.2.0) + import wasi:sockets/network@0.2.3; + @since(version = 0.2.0) + import wasi:sockets/instance-network@0.2.3; + @since(version = 0.2.0) + import wasi:sockets/udp@0.2.3; + @since(version = 0.2.0) + import wasi:sockets/udp-create-socket@0.2.3; + @since(version = 0.2.0) + import wasi:sockets/tcp@0.2.3; + @since(version = 0.2.0) + import wasi:sockets/tcp-create-socket@0.2.3; + @since(version = 0.2.0) + import wasi:sockets/ip-name-lookup@0.2.3; + @since(version = 0.2.0) + import wasi:random/random@0.2.3; + @since(version = 0.2.0) + import wasi:random/insecure@0.2.3; + @since(version = 0.2.0) + import wasi:random/insecure-seed@0.2.3; +} +@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.3; + @since(version = 0.2.0) + import wasi:io/poll@0.2.3; + @since(version = 0.2.0) + import wasi:io/streams@0.2.3; + @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.3; + @since(version = 0.2.0) + import wasi:clocks/wall-clock@0.2.3; + @since(version = 0.2.0) + import wasi:filesystem/types@0.2.3; + @since(version = 0.2.0) + import wasi:filesystem/preopens@0.2.3; + @since(version = 0.2.0) + import wasi:sockets/network@0.2.3; + @since(version = 0.2.0) + import wasi:sockets/instance-network@0.2.3; + @since(version = 0.2.0) + import wasi:sockets/udp@0.2.3; + @since(version = 0.2.0) + import wasi:sockets/udp-create-socket@0.2.3; + @since(version = 0.2.0) + import wasi:sockets/tcp@0.2.3; + @since(version = 0.2.0) + import wasi:sockets/tcp-create-socket@0.2.3; + @since(version = 0.2.0) + import wasi:sockets/ip-name-lookup@0.2.3; + @since(version = 0.2.0) + import wasi:random/random@0.2.3; + @since(version = 0.2.0) + import wasi:random/insecure@0.2.3; + @since(version = 0.2.0) + import wasi:random/insecure-seed@0.2.3; + + @since(version = 0.2.0) + export run; +} diff --git a/examples/components/http-server-hono/wit/deps/wasi-clocks-0.2.3/package.wit b/examples/components/http-server-hono/wit/deps/wasi-clocks-0.2.3/package.wit new file mode 100644 index 000000000..92f26622e --- /dev/null +++ b/examples/components/http-server-hono/wit/deps/wasi-clocks-0.2.3/package.wit @@ -0,0 +1,29 @@ +package wasi:clocks@0.2.3; + +interface monotonic-clock { + use wasi:io/poll@0.2.3.{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; +} + diff --git a/examples/components/http-server-hono/wit/deps/wasi-filesystem-0.2.3/package.wit b/examples/components/http-server-hono/wit/deps/wasi-filesystem-0.2.3/package.wit new file mode 100644 index 000000000..1111df18a --- /dev/null +++ b/examples/components/http-server-hono/wit/deps/wasi-filesystem-0.2.3/package.wit @@ -0,0 +1,158 @@ +package wasi:filesystem@0.2.3; + +interface types { + use wasi:io/streams@0.2.3.{input-stream, output-stream, error}; + use wasi:clocks/wall-clock@0.2.3.{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/examples/components/http-server-hono/wit/deps/wasi-http-0.2.3/package.wit b/examples/components/http-server-hono/wit/deps/wasi-http-0.2.3/package.wit new file mode 100644 index 000000000..932f08986 --- /dev/null +++ b/examples/components/http-server-hono/wit/deps/wasi-http-0.2.3/package.wit @@ -0,0 +1,721 @@ +package wasi:http@0.2.3; + +/// 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.3.{duration}; + @since(version = 0.2.0) + use wasi:io/streams@0.2.3.{input-stream, output-stream}; + @since(version = 0.2.0) + use wasi:io/error@0.2.3.{error as io-error}; + @since(version = 0.2.0) + use wasi:io/poll@0.2.3.{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 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 { + /// 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.3; + @since(version = 0.2.0) + import wasi:clocks/monotonic-clock@0.2.3; + @since(version = 0.2.0) + import wasi:clocks/wall-clock@0.2.3; + @since(version = 0.2.0) + import wasi:random/random@0.2.3; + @since(version = 0.2.0) + import wasi:io/error@0.2.3; + @since(version = 0.2.0) + import wasi:io/streams@0.2.3; + @since(version = 0.2.0) + import wasi:cli/stdout@0.2.3; + @since(version = 0.2.0) + import wasi:cli/stderr@0.2.3; + @since(version = 0.2.0) + import wasi:cli/stdin@0.2.3; + @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.3; + @since(version = 0.2.0) + import wasi:clocks/monotonic-clock@0.2.3; + @since(version = 0.2.0) + import wasi:clocks/wall-clock@0.2.3; + @since(version = 0.2.0) + import wasi:random/random@0.2.3; + @since(version = 0.2.0) + import wasi:io/error@0.2.3; + @since(version = 0.2.0) + import wasi:io/streams@0.2.3; + @since(version = 0.2.0) + import wasi:cli/stdout@0.2.3; + @since(version = 0.2.0) + import wasi:cli/stderr@0.2.3; + @since(version = 0.2.0) + import wasi:cli/stdin@0.2.3; + @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/examples/components/http-server-hono/wit/deps/wasi-io-0.2.3/package.wit b/examples/components/http-server-hono/wit/deps/wasi-io-0.2.3/package.wit new file mode 100644 index 000000000..e4d5ef7b2 --- /dev/null +++ b/examples/components/http-server-hono/wit/deps/wasi-io-0.2.3/package.wit @@ -0,0 +1,48 @@ +package wasi:io@0.2.3; + +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/examples/components/http-server-hono/wit/deps/wasi-random-0.2.3/package.wit b/examples/components/http-server-hono/wit/deps/wasi-random-0.2.3/package.wit new file mode 100644 index 000000000..bc1f89199 --- /dev/null +++ b/examples/components/http-server-hono/wit/deps/wasi-random-0.2.3/package.wit @@ -0,0 +1,18 @@ +package wasi:random@0.2.3; + +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/examples/components/http-server-hono/wit/deps/wasi-sockets-0.2.3/package.wit b/examples/components/http-server-hono/wit/deps/wasi-sockets-0.2.3/package.wit new file mode 100644 index 000000000..e7236e1b5 --- /dev/null +++ b/examples/components/http-server-hono/wit/deps/wasi-sockets-0.2.3/package.wit @@ -0,0 +1,179 @@ +package wasi:sockets@0.2.3; + +interface network { + resource network; + + enum error-code { + unknown, + access-denied, + not-supported, + invalid-argument, + out-of-memory, + timeout, + concurrency-conflict, + not-in-progress, + would-block, + invalid-state, + new-socket-limit, + address-not-bindable, + address-in-use, + remote-unreachable, + connection-refused, + connection-reset, + connection-aborted, + datagram-too-large, + name-unresolvable, + temporary-resolver-failure, + permanent-resolver-failure, + } + + enum ip-address-family { + ipv4, + ipv6, + } + + type ipv4-address = tuple; + + type ipv6-address = tuple; + + variant ip-address { + ipv4(ipv4-address), + ipv6(ipv6-address), + } + + record ipv4-socket-address { + port: u16, + address: ipv4-address, + } + + record ipv6-socket-address { + port: u16, + flow-info: u32, + address: ipv6-address, + scope-id: u32, + } + + variant ip-socket-address { + ipv4(ipv4-socket-address), + ipv6(ipv6-socket-address), + } +} + +interface instance-network { + use network.{network}; + + instance-network: func() -> network; +} + +interface udp { + use wasi:io/poll@0.2.3.{pollable}; + use network.{network, error-code, ip-socket-address, ip-address-family}; + + record incoming-datagram { + data: list, + remote-address: ip-socket-address, + } + + record outgoing-datagram { + data: list, + remote-address: option, + } + + resource udp-socket { + start-bind: func(network: borrow, local-address: ip-socket-address) -> result<_, error-code>; + finish-bind: func() -> result<_, error-code>; + %stream: func(remote-address: option) -> result, error-code>; + local-address: func() -> result; + remote-address: func() -> result; + address-family: func() -> ip-address-family; + unicast-hop-limit: func() -> result; + set-unicast-hop-limit: func(value: u8) -> result<_, error-code>; + receive-buffer-size: func() -> result; + set-receive-buffer-size: func(value: u64) -> result<_, error-code>; + send-buffer-size: func() -> result; + set-send-buffer-size: func(value: u64) -> result<_, error-code>; + subscribe: func() -> pollable; + } + + resource incoming-datagram-stream { + receive: func(max-results: u64) -> result, error-code>; + subscribe: func() -> pollable; + } + + resource outgoing-datagram-stream { + check-send: func() -> result; + send: func(datagrams: list) -> result; + subscribe: func() -> pollable; + } +} + +interface udp-create-socket { + use network.{network, error-code, ip-address-family}; + use udp.{udp-socket}; + + create-udp-socket: func(address-family: ip-address-family) -> result; +} + +interface tcp { + use wasi:io/streams@0.2.3.{input-stream, output-stream}; + use wasi:io/poll@0.2.3.{pollable}; + use wasi:clocks/monotonic-clock@0.2.3.{duration}; + use network.{network, error-code, ip-socket-address, ip-address-family}; + + enum shutdown-type { + receive, + send, + both, + } + + resource tcp-socket { + start-bind: func(network: borrow, local-address: ip-socket-address) -> result<_, error-code>; + finish-bind: func() -> result<_, error-code>; + start-connect: func(network: borrow, remote-address: ip-socket-address) -> result<_, error-code>; + finish-connect: func() -> result, error-code>; + start-listen: func() -> result<_, error-code>; + finish-listen: func() -> result<_, error-code>; + accept: func() -> result, error-code>; + local-address: func() -> result; + remote-address: func() -> result; + is-listening: func() -> bool; + address-family: func() -> ip-address-family; + set-listen-backlog-size: func(value: u64) -> result<_, error-code>; + keep-alive-enabled: func() -> result; + set-keep-alive-enabled: func(value: bool) -> result<_, error-code>; + keep-alive-idle-time: func() -> result; + set-keep-alive-idle-time: func(value: duration) -> result<_, error-code>; + keep-alive-interval: func() -> result; + set-keep-alive-interval: func(value: duration) -> result<_, error-code>; + keep-alive-count: func() -> result; + set-keep-alive-count: func(value: u32) -> result<_, error-code>; + hop-limit: func() -> result; + set-hop-limit: func(value: u8) -> result<_, error-code>; + receive-buffer-size: func() -> result; + set-receive-buffer-size: func(value: u64) -> result<_, error-code>; + send-buffer-size: func() -> result; + set-send-buffer-size: func(value: u64) -> result<_, error-code>; + subscribe: func() -> pollable; + shutdown: func(shutdown-type: shutdown-type) -> result<_, error-code>; + } +} + +interface tcp-create-socket { + use network.{network, error-code, ip-address-family}; + use tcp.{tcp-socket}; + + create-tcp-socket: func(address-family: ip-address-family) -> result; +} + +interface ip-name-lookup { + use wasi:io/poll@0.2.3.{pollable}; + use network.{network, error-code, ip-address}; + + resource resolve-address-stream { + resolve-next-address: func() -> result, error-code>; + subscribe: func() -> pollable; + } + + resolve-addresses: func(network: borrow, name: string) -> result; +} + diff --git a/examples/components/http-server-hono/wkg.lock b/examples/components/http-server-hono/wkg.lock new file mode 100644 index 000000000..d89181d38 --- /dev/null +++ b/examples/components/http-server-hono/wkg.lock @@ -0,0 +1,21 @@ +# This file is automatically generated. +# It is not intended for manual editing. +version = 1 + +[[packages]] +name = "wasi:cli" +registry = "wasi.dev" + +[[packages.versions]] +requirement = "=0.2.3" +version = "0.2.3" +digest = "sha256:8f97d837e1f856a225422869d5c34752204d1befb5a04d0cd80541aec17a20c1" + +[[packages]] +name = "wasi:http" +registry = "wasi.dev" + +[[packages.versions]] +requirement = "=0.2.3" +version = "0.2.3" +digest = "sha256:e526c1586efc94cd148e33725139be05c4bb58ba20466d348282bd8dc3999f1d" diff --git a/package.json b/package.json index d0d35723c..998b6e98c 100644 --- a/package.json +++ b/package.json @@ -6,8 +6,9 @@ "examples/components/add", "examples/components/adder", "examples/components/http-hello-world", - "examples/components/node-fetch", "examples/components/http-server-fetch-handler", + "examples/components/http-server-hono", + "examples/components/node-fetch", "examples/components/string-reverse", "examples/components/string-reverse-upper", "examples/components/webidl-book-library"