diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 93387a4..0b052ad 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - dir: [consent-management, data-collection] + dir: [edge-function, data-collection] defaults: run: working-directory: ${{ matrix.dir }} diff --git a/consent-management/src/index.ts b/consent-management/src/index.ts deleted file mode 100644 index c4b77e7..0000000 --- a/consent-management/src/index.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type { - consentManagement as EdgeeConsentManagement, -} from "../types/wit"; - - -const convertDict = (dict: EdgeeConsentManagement.Dict): Map => { - const data = new Map(); - - for (const [key, value] of dict) { - data.set(key, value); - } - - return data; -}; - -export const consentManagement: typeof EdgeeConsentManagement = { - map(cookies: EdgeeConsentManagement.Dict, settings: EdgeeConsentManagement.Dict) { - const dictSettings = convertDict(settings); - const dictCookies = convertDict(cookies); - - if (!dictCookies.has('example')) { - return; - } - const example = dictCookies.get('example'); - if (example === "granted") { - return "granted"; - } else if (example === "denied") { - return "denied"; - } - return "pending"; - }, -}; diff --git a/consent-management/test/index.spec.ts b/consent-management/test/index.spec.ts deleted file mode 100644 index 919eb28..0000000 --- a/consent-management/test/index.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { assert } from "chai"; -import { describe, it } from "mocha"; -import { consentManagement } from '../src/index'; -import { Dict } from "../types/interfaces/edgee-components-consent-management"; - -describe('consent management component', function () { - - const sampleSettings: Dict = [ - ["example", "api_value"] - ]; - - const sampleCookies: Dict = [ - ["example", "denied"] - ]; - - describe('#map()', function () { - it('should return consent denied', function () { - const req = consentManagement.map(sampleCookies, sampleSettings); - assert.equal(req, "denied"); - }); - }); - -}); diff --git a/consent-management/types/interfaces/edgee-components-consent-management.d.ts b/consent-management/types/interfaces/edgee-components-consent-management.d.ts deleted file mode 100644 index 95e046f..0000000 --- a/consent-management/types/interfaces/edgee-components-consent-management.d.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** @module Interface edgee:components/consent-management@1.0.0 **/ -export function map(cookies: Dict, settings: Dict): Consent | undefined; -export type Dict = Array<[string, string]>; -/** - * # Variants - * - * ## `"pending"` - * - * ## `"granted"` - * - * ## `"denied"` - */ -export type Consent = 'pending' | 'granted' | 'denied'; diff --git a/consent-management/types/wit.d.ts b/consent-management/types/wit.d.ts deleted file mode 100644 index 5b252df..0000000 --- a/consent-management/types/wit.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -// world edgee:native/consent-management -export * as consentManagement from './interfaces/edgee-components-consent-management.js'; // export edgee:components/consent-management@1.0.0 diff --git a/consent-management/.gitignore b/edge-function/.gitignore similarity index 100% rename from consent-management/.gitignore rename to edge-function/.gitignore diff --git a/consent-management/LICENSE b/edge-function/LICENSE similarity index 100% rename from consent-management/LICENSE rename to edge-function/LICENSE diff --git a/consent-management/README.md b/edge-function/README.md similarity index 100% rename from consent-management/README.md rename to edge-function/README.md diff --git a/consent-management/edgee-component.toml b/edge-function/edgee-component.toml similarity index 63% rename from consent-management/edgee-component.toml rename to edge-function/edgee-component.toml index c1cde60..66706f1 100644 --- a/consent-management/edgee-component.toml +++ b/edge-function/edgee-component.toml @@ -1,11 +1,11 @@ manifest-version = 1 [component] -name = "example-ts-consent-management-component" +name = "example-ts-edge-function-component" version = "1.0.0" -category = "consent-management" -subcategory = "consent-mapping" -description = "Example TypeScript component for consent management" +category = "edge-function" +subcategory = "wasm-function" +description = "Example TypeScript component for edge function" documentation = "https://github.com/edgee-cloud/example-ts-component" repository = "https://github.com/edgee-cloud/example-ts-component" language = "TypeScript" @@ -13,7 +13,7 @@ wit-version = "1.0.0" [component.build] command = "npm install && npm run generate && npm run build" -output_path = "./example-ts-component.wasm" +output_path = "./example-ts-edge-function-component.wasm" [component.settings.example] title = "Example Config Field" diff --git a/consent-management/eslint.config.mjs b/edge-function/eslint.config.mjs similarity index 100% rename from consent-management/eslint.config.mjs rename to edge-function/eslint.config.mjs diff --git a/consent-management/package-lock.json b/edge-function/package-lock.json similarity index 99% rename from consent-management/package-lock.json rename to edge-function/package-lock.json index b054314..8f372ce 100644 --- a/consent-management/package-lock.json +++ b/edge-function/package-lock.json @@ -1,10 +1,10 @@ { - "name": "example-ts-component", + "name": "example-ts-edge-function-component", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "example-ts-component", + "name": "example-ts-edge-function-component", "devDependencies": { "@eslint/js": "^9.20.0", "@types/chai": "^5.0.1", diff --git a/consent-management/package.json b/edge-function/package.json similarity index 83% rename from consent-management/package.json rename to edge-function/package.json index 3b6a254..eb51eba 100644 --- a/consent-management/package.json +++ b/edge-function/package.json @@ -1,12 +1,12 @@ { - "name": "example-ts-component", + "name": "example-ts-edge-function-component", "type": "module", "main": "src/index.ts", "types": "types/wit.d.ts", "scripts": { "generate": "npx @bytecodealliance/jco types .edgee/wit -o types", "compile": "npx tsc", - "build": "npm run compile && npx @bytecodealliance/jco componentize src/index.js --wit .edgee/wit -o example-ts-component.wasm -n consent-management -d all", + "build": "npm run compile && npx @bytecodealliance/jco componentize src/index.js --wit .edgee/wit -o example-ts-edge-function-component.wasm -n edge-function", "lint": "npx eslint", "test": "mocha", "coverage": "c8 --src js --all -r text -r text-summary npm test" diff --git a/edge-function/src/index.ts b/edge-function/src/index.ts new file mode 100644 index 0000000..6f94095 --- /dev/null +++ b/edge-function/src/index.ts @@ -0,0 +1,86 @@ +import { + ResponseOutparam, + OutgoingBody, + OutgoingResponse, + Fields, + InputStream, + IncomingRequest, +} from "wasi:http/types@0.2.0"; + +const index = ` + + + + + + Coming Soon + + + +
+

Coming Soon

+

We're working hard to launch something awesome. Stay tuned!

+ +
+ + +` + +function handle(req: IncomingRequest, resp: ResponseOutparam) { + const outgoingResponse = new OutgoingResponse(new Fields()); + outgoingResponse.setStatusCode(200); + + let outgoingBody = outgoingResponse.body(); + { + let outputStream = outgoingBody.write(); + outputStream.write( + new Uint8Array(new TextEncoder().encode(index)) + ); + outputStream.flush(); + // @ts-ignore: need to drop the stream according to WASI spec + outputStream[Symbol.dispose](); + } + + OutgoingBody.finish(outgoingBody, undefined); + ResponseOutparam.set(resp, { tag: 'ok', val: outgoingResponse }); +} + +export const incomingHandler = { + handle, +}; diff --git a/edge-function/test/index.spec.ts b/edge-function/test/index.spec.ts new file mode 100644 index 0000000..e69de29 diff --git a/consent-management/test/register-loader.js b/edge-function/test/register-loader.js similarity index 100% rename from consent-management/test/register-loader.js rename to edge-function/test/register-loader.js diff --git a/consent-management/tsconfig.json b/edge-function/tsconfig.json similarity index 65% rename from consent-management/tsconfig.json rename to edge-function/tsconfig.json index f0834de..112ccd2 100644 --- a/consent-management/tsconfig.json +++ b/edge-function/tsconfig.json @@ -8,5 +8,8 @@ "strict": true, "noImplicitAny": true, "skipLibCheck": true, + "paths": { + "wasi:http/types@0.2.0": [ "./types/interfaces/wasi-http-types.d.ts" ] + }, } } diff --git a/edge-function/types/interfaces/wasi-clocks-monotonic-clock.d.ts b/edge-function/types/interfaces/wasi-clocks-monotonic-clock.d.ts new file mode 100644 index 0000000..6227d29 --- /dev/null +++ b/edge-function/types/interfaces/wasi-clocks-monotonic-clock.d.ts @@ -0,0 +1,35 @@ +/** @module Interface wasi:clocks/monotonic-clock@0.2.0 **/ +/** + * Read the current value of the clock. + * + * The clock is monotonic, therefore calling this function repeatedly will + * produce a sequence of non-decreasing values. + */ +export function now(): Instant; +/** + * Query the resolution of the clock. Returns the duration of time + * corresponding to a clock tick. + */ +export function resolution(): Duration; +/** + * Create a `pollable` which will resolve once the specified instant + * occured. + */ +export function subscribeInstant(when: Instant): Pollable; +/** + * Create a `pollable` which will resolve once the given duration has + * elapsed, starting at the time at which this function was called. + * occured. + */ +export function subscribeDuration(when: Duration): Pollable; +export type Pollable = import('./wasi-io-poll.js').Pollable; +/** + * An instant in time, in nanoseconds. An instant is relative to an + * unspecified initial value, and can only be compared to instances from + * the same monotonic-clock. + */ +export type Instant = bigint; +/** + * A duration of time, in nanoseconds. + */ +export type Duration = bigint; diff --git a/edge-function/types/interfaces/wasi-http-incoming-handler.d.ts b/edge-function/types/interfaces/wasi-http-incoming-handler.d.ts new file mode 100644 index 0000000..8e49865 --- /dev/null +++ b/edge-function/types/interfaces/wasi-http-incoming-handler.d.ts @@ -0,0 +1,16 @@ +/** @module Interface wasi:http/incoming-handler@0.2.0 **/ +/** + * 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.js').IncomingRequest; +export type ResponseOutparam = import('./wasi-http-types.js').ResponseOutparam; diff --git a/edge-function/types/interfaces/wasi-http-outgoing-handler.d.ts b/edge-function/types/interfaces/wasi-http-outgoing-handler.d.ts new file mode 100644 index 0000000..5180d1d --- /dev/null +++ b/edge-function/types/interfaces/wasi-http-outgoing-handler.d.ts @@ -0,0 +1,18 @@ +/** @module Interface wasi:http/outgoing-handler@0.2.0 **/ +/** + * 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`. + */ +export function handle(request: OutgoingRequest, options: RequestOptions | undefined): FutureIncomingResponse; +export type OutgoingRequest = import('./wasi-http-types.js').OutgoingRequest; +export type RequestOptions = import('./wasi-http-types.js').RequestOptions; +export type FutureIncomingResponse = import('./wasi-http-types.js').FutureIncomingResponse; +export type ErrorCode = import('./wasi-http-types.js').ErrorCode; diff --git a/edge-function/types/interfaces/wasi-http-types.d.ts b/edge-function/types/interfaces/wasi-http-types.d.ts new file mode 100644 index 0000000..7a1af70 --- /dev/null +++ b/edge-function/types/interfaces/wasi-http-types.d.ts @@ -0,0 +1,724 @@ +/** @module Interface wasi:http/types@0.2.0 **/ +/** + * 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.js').Duration; +export type InputStream = import('./wasi-io-streams.js').InputStream; +export type OutputStream = import('./wasi-io-streams.js').OutputStream; +export type IoError = import('./wasi-io-error.js').Error; +export type Pollable = import('./wasi-io-poll.js').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: + * https://www.iana.org/assignments/http-proxy-status/http-proxy-status.xhtml#table-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-key` 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-key` 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. + */ +export type FieldKey = string; +/** + * 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 key-value pair in the Fields. Keys + * which have multiple values are represented by multiple entries in this + * list with the same key. + * + * The tuple is a pair of the field key, represented as a string, and + * Value, represented as a list of bytes. In a valid Fields, all keys + * and values are valid UTF-8 strings. However, values are not always + * well-formed, so they are represented as a raw list of bytes. + * + * An error result will be returned if any header or value was + * syntactically invalid, or if a header was forbidden. + */ + static fromList(entries: Array<[FieldKey, FieldValue]>): Fields; + /** + * Get all of the values corresponding to a key. If the key is not present + * in this `fields`, an empty list is returned. However, if the key is + * present but empty, this is represented by a list with one or more + * empty field-values present. + */ + get(name: FieldKey): Array; + /** + * Returns `true` when the key is present in this `fields`. If the key is + * syntactically invalid, `false` is returned. + */ + has(name: FieldKey): boolean; + /** + * Set all of the values for a key. Clears any existing values for that + * key, if they have been set. + * + * Fails with `header-error.immutable` if the `fields` are immutable. + */ + set(name: FieldKey, value: Array): void; + /** + * Delete all values for a key. Does nothing if no values for the key + * exist. + * + * Fails with `header-error.immutable` if the `fields` are immutable. + */ + 'delete'(name: FieldKey): void; + /** + * Append a value for a key. Does not change or delete any existing + * values for that key. + * + * Fails with `header-error.immutable` if the `fields` are immutable. + */ + append(name: FieldKey, value: FieldValue): void; + /** + * Retrieve the full set of keys and values in the Fields. Like the + * constructor, the list represents each key-value pair. + * + * The outer list represents each key-value pair in the Fields. Keys + * which have multiple values are represented by multiple entries in this + * list with the same key. + */ + entries(): Array<[FieldKey, FieldValue]>; + /** + * Make a deep copy of the Fields. Equivelant 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 occured. 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 recieved successfully, or that an error + * occured. 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 occured. When this pollable is ready, + * the `get` method will return `some`. + */ + subscribe(): Pollable; + /** + * Returns the contents of the trailers, or an error which occured, + * 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 occured 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 from the request, if it was 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 HTTP Authority for the Request. 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 HTTP Authority for the Request. 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 transfered 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 transfered 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/edge-function/types/interfaces/wasi-io-error.d.ts b/edge-function/types/interfaces/wasi-io-error.d.ts new file mode 100644 index 0000000..9384745 --- /dev/null +++ b/edge-function/types/interfaces/wasi-io-error.d.ts @@ -0,0 +1,18 @@ +/** @module Interface wasi:io/error@0.2.0 **/ + +export class Error { + /** + * This type does not have a public constructor. + */ + private constructor(); + /** + * Returns a string that is suitable to assist humans in debugging + * this error. + * + * WARNING: The returned string should not be consumed mechanically! + * It may change across platforms, hosts, or other implementation + * details. Parsing this string is a major platform-compatibility + * hazard. + */ + toDebugString(): string; +} diff --git a/edge-function/types/interfaces/wasi-io-poll.d.ts b/edge-function/types/interfaces/wasi-io-poll.d.ts new file mode 100644 index 0000000..7520ed7 --- /dev/null +++ b/edge-function/types/interfaces/wasi-io-poll.d.ts @@ -0,0 +1,43 @@ +/** @module Interface wasi:io/poll@0.2.0 **/ +/** + * Poll for completion on a set of pollables. + * + * This function takes a list of pollables, which identify I/O sources of + * interest, and waits until one or more of the events is ready for I/O. + * + * The result `list` contains one or more indices of handles in the + * argument list that is ready for I/O. + * + * If the list contains more elements than can be indexed with a `u32` + * value, this function traps. + * + * A timeout can be implemented by adding a pollable from the + * wasi-clocks API to the list. + * + * This function does not return a `result`; polling in itself does not + * do any I/O so it doesn't fail. If any of the I/O sources identified by + * the pollables has an error, it is indicated by marking the source as + * being reaedy for I/O. + */ +export function poll(in_: Array): Uint32Array; + +export class Pollable { + /** + * This type does not have a public constructor. + */ + private constructor(); + /** + * Return the readiness of a pollable. This function never blocks. + * + * Returns `true` when the pollable is ready, and `false` otherwise. + */ + ready(): boolean; + /** + * `block` returns immediately if the pollable is ready, and otherwise + * blocks until ready. + * + * This function is equivalent to calling `poll.poll` on a list + * containing only this pollable. + */ + block(): void; +} diff --git a/edge-function/types/interfaces/wasi-io-streams.d.ts b/edge-function/types/interfaces/wasi-io-streams.d.ts new file mode 100644 index 0000000..2950db2 --- /dev/null +++ b/edge-function/types/interfaces/wasi-io-streams.d.ts @@ -0,0 +1,240 @@ +/** @module Interface wasi:io/streams@0.2.0 **/ +export type Error = import('./wasi-io-error.js').Error; +export type Pollable = import('./wasi-io-poll.js').Pollable; +/** + * An error for input-stream and output-stream operations. + */ +export type StreamError = StreamErrorLastOperationFailed | StreamErrorClosed; +/** + * The last operation (a write or flush) failed before completion. + * + * More information is available in the `error` payload. + */ +export interface StreamErrorLastOperationFailed { + tag: 'last-operation-failed', + val: Error, +} +/** + * The stream is closed: no more input will be accepted by the + * stream. A closed output-stream will return this error on all + * future operations. + */ +export interface StreamErrorClosed { + tag: 'closed', +} + +export class InputStream { + /** + * This type does not have a public constructor. + */ + private constructor(); + /** + * Perform a non-blocking read from the stream. + * + * When the source of a `read` is binary data, the bytes from the source + * are returned verbatim. When the source of a `read` is known to the + * implementation to be text, bytes containing the UTF-8 encoding of the + * text are returned. + * + * This function returns a list of bytes containing the read data, + * when successful. The returned list will contain up to `len` bytes; + * it may return fewer than requested, but not more. The list is + * empty when no bytes are available for reading at this time. The + * pollable given by `subscribe` will be ready when more bytes are + * available. + * + * This function fails with a `stream-error` when the operation + * encounters an error, giving `last-operation-failed`, or when the + * stream is closed, giving `closed`. + * + * When the caller gives a `len` of 0, it represents a request to + * read 0 bytes. If the stream is still open, this call should + * succeed and return an empty list, or otherwise fail with `closed`. + * + * The `len` parameter is a `u64`, which could represent a list of u8 which + * is not possible to allocate in wasm32, or not desirable to allocate as + * as a return value by the callee. The callee may return a list of bytes + * less than `len` in size while more bytes are available for reading. + */ + read(len: bigint): Uint8Array; + /** + * Read bytes from a stream, after blocking until at least one byte can + * be read. Except for blocking, behavior is identical to `read`. + */ + blockingRead(len: bigint): Uint8Array; + /** + * Skip bytes from a stream. Returns number of bytes skipped. + * + * Behaves identical to `read`, except instead of returning a list + * of bytes, returns the number of bytes consumed from the stream. + */ + skip(len: bigint): bigint; + /** + * Skip bytes from a stream, after blocking until at least one byte + * can be skipped. Except for blocking behavior, identical to `skip`. + */ + blockingSkip(len: bigint): bigint; + /** + * Create a `pollable` which will resolve once either the specified stream + * has bytes available to read or the other end of the stream has been + * closed. + * The created `pollable` is a child resource of the `input-stream`. + * Implementations may trap if the `input-stream` is dropped before + * all derived `pollable`s created with this function are dropped. + */ + subscribe(): Pollable; +} + +export class OutputStream { + /** + * This type does not have a public constructor. + */ + private constructor(); + /** + * Check readiness for writing. This function never blocks. + * + * Returns the number of bytes permitted for the next call to `write`, + * or an error. Calling `write` with more bytes than this function has + * permitted will trap. + * + * When this function returns 0 bytes, the `subscribe` pollable will + * become ready when this function will report at least 1 byte, or an + * error. + */ + checkWrite(): bigint; + /** + * Perform a write. This function never blocks. + * + * When the destination of a `write` is binary data, the bytes from + * `contents` are written verbatim. When the destination of a `write` is + * known to the implementation to be text, the bytes of `contents` are + * transcoded from UTF-8 into the encoding of the destination and then + * written. + * + * Precondition: check-write gave permit of Ok(n) and contents has a + * length of less than or equal to n. Otherwise, this function will trap. + * + * returns Err(closed) without writing if the stream has closed since + * the last call to check-write provided a permit. + */ + write(contents: Uint8Array): void; + /** + * Perform a write of up to 4096 bytes, and then flush the stream. Block + * until all of these operations are complete, or an error occurs. + * + * This is a convenience wrapper around the use of `check-write`, + * `subscribe`, `write`, and `flush`, and is implemented with the + * following pseudo-code: + * + * ```text + * let pollable = this.subscribe(); + * while !contents.is_empty() { + * // Wait for the stream to become writable + * pollable.block(); + * let Ok(n) = this.check-write(); // eliding error handling + * let len = min(n, contents.len()); + * let (chunk, rest) = contents.split_at(len); + * this.write(chunk ); // eliding error handling + * contents = rest; + * } + * this.flush(); + * // Wait for completion of `flush` + * pollable.block(); + * // Check for any errors that arose during `flush` + * let _ = this.check-write(); // eliding error handling + * ``` + */ + blockingWriteAndFlush(contents: Uint8Array): void; + /** + * Request to flush buffered output. This function never blocks. + * + * This tells the output-stream that the caller intends any buffered + * output to be flushed. the output which is expected to be flushed + * is all that has been passed to `write` prior to this call. + * + * Upon calling this function, the `output-stream` will not accept any + * writes (`check-write` will return `ok(0)`) until the flush has + * completed. The `subscribe` pollable will become ready when the + * flush has completed and the stream can accept more writes. + */ + flush(): void; + /** + * Request to flush buffered output, and block until flush completes + * and stream is ready for writing again. + */ + blockingFlush(): void; + /** + * Create a `pollable` which will resolve once the output-stream + * is ready for more writing, or an error has occured. When this + * pollable is ready, `check-write` will return `ok(n)` with n>0, or an + * error. + * + * If the stream is closed, this pollable is always ready immediately. + * + * The created `pollable` is a child resource of the `output-stream`. + * Implementations may trap if the `output-stream` is dropped before + * all derived `pollable`s created with this function are dropped. + */ + subscribe(): Pollable; + /** + * Write zeroes to a stream. + * + * This should be used precisely like `write` with the exact same + * preconditions (must use check-write first), but instead of + * passing a list of bytes, you simply pass the number of zero-bytes + * that should be written. + */ + writeZeroes(len: bigint): void; + /** + * Perform a write of up to 4096 zeroes, and then flush the stream. + * Block until all of these operations are complete, or an error + * occurs. + * + * This is a convenience wrapper around the use of `check-write`, + * `subscribe`, `write-zeroes`, and `flush`, and is implemented with + * the following pseudo-code: + * + * ```text + * let pollable = this.subscribe(); + * while num_zeroes != 0 { + * // Wait for the stream to become writable + * pollable.block(); + * let Ok(n) = this.check-write(); // eliding error handling + * let len = min(n, num_zeroes); + * this.write-zeroes(len); // eliding error handling + * num_zeroes -= len; + * } + * this.flush(); + * // Wait for completion of `flush` + * pollable.block(); + * // Check for any errors that arose during `flush` + * let _ = this.check-write(); // eliding error handling + * ``` + */ + blockingWriteZeroesAndFlush(len: bigint): void; + /** + * Read from one stream and write to another. + * + * The behavior of splice is equivelant to: + * 1. calling `check-write` on the `output-stream` + * 2. calling `read` on the `input-stream` with the smaller of the + * `check-write` permitted length and the `len` provided to `splice` + * 3. calling `write` on the `output-stream` with that read data. + * + * Any error reported by the call to `check-write`, `read`, or + * `write` ends the splice and reports that error. + * + * This function returns the number of bytes transferred; it may be less + * than `len`. + */ + splice(src: InputStream, len: bigint): bigint; + /** + * Read from one stream and write to another, with blocking. + * + * This is similar to `splice`, except that it blocks until the + * `output-stream` is ready for writing, and the `input-stream` + * is ready for reading, before performing the `splice`. + */ + blockingSplice(src: InputStream, len: bigint): bigint; + } + \ No newline at end of file diff --git a/edge-function/types/wit.d.ts b/edge-function/types/wit.d.ts new file mode 100644 index 0000000..aa5d449 --- /dev/null +++ b/edge-function/types/wit.d.ts @@ -0,0 +1,8 @@ +// world edgee:native/edge-function +export type * as WasiClocksMonotonicClock020 from './interfaces/wasi-clocks-monotonic-clock.js'; // import wasi:clocks/monotonic-clock@0.2.0 +export type * as WasiHttpOutgoingHandler020 from './interfaces/wasi-http-outgoing-handler.js'; // import wasi:http/outgoing-handler@0.2.0 +export type * as WasiHttpTypes020 from './interfaces/wasi-http-types.js'; // import wasi:http/types@0.2.0 +export type * as WasiIoError020 from './interfaces/wasi-io-error.js'; // import wasi:io/error@0.2.0 +export type * as WasiIoPoll020 from './interfaces/wasi-io-poll.js'; // import wasi:io/poll@0.2.0 +export type * as WasiIoStreams020 from './interfaces/wasi-io-streams.js'; // import wasi:io/streams@0.2.0 +export * as incomingHandler from './interfaces/wasi-http-incoming-handler.js'; // export wasi:http/incoming-handler@0.2.0