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