@bytecodealliance/preview3-shim 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/nodejs/cli.d.ts +51 -0
- package/dist/nodejs/cli.js +106 -0
- package/dist/nodejs/clocks.d.ts +55 -0
- package/dist/nodejs/clocks.js +83 -0
- package/dist/nodejs/filesystem/descriptor.d.ts +443 -0
- package/dist/nodejs/filesystem/descriptor.js +1010 -0
- package/dist/nodejs/filesystem/error.d.ts +61 -0
- package/dist/nodejs/filesystem/error.js +86 -0
- package/dist/nodejs/filesystem/utils.d.ts +8 -0
- package/dist/nodejs/filesystem/utils.js +25 -0
- package/dist/nodejs/filesystem.js +2 -0
- package/dist/nodejs/finalization.d.ts +2 -0
- package/{lib → dist}/nodejs/finalization.js +15 -21
- package/dist/nodejs/future.d.ts +98 -0
- package/dist/nodejs/future.js +176 -0
- package/dist/nodejs/http/client.d.ts +22 -0
- package/dist/nodejs/http/client.js +138 -0
- package/dist/nodejs/http/error.d.ts +18 -0
- package/dist/nodejs/http/error.js +62 -0
- package/dist/nodejs/http/fields.d.ts +144 -0
- package/dist/nodejs/http/fields.js +327 -0
- package/dist/nodejs/http/request.d.ts +256 -0
- package/dist/nodejs/http/request.js +606 -0
- package/dist/nodejs/http/response.d.ts +82 -0
- package/dist/nodejs/http/response.js +202 -0
- package/dist/nodejs/http/server.d.ts +13 -0
- package/dist/nodejs/http/server.js +127 -0
- package/dist/nodejs/http.d.ts +19 -0
- package/{lib → dist}/nodejs/http.js +13 -18
- package/dist/nodejs/index.js +8 -0
- package/dist/nodejs/random.d.ts +5 -0
- package/{lib → dist}/nodejs/random.js +2 -8
- package/dist/nodejs/sockets/address.d.ts +78 -0
- package/dist/nodejs/sockets/address.js +120 -0
- package/dist/nodejs/sockets/error.d.ts +83 -0
- package/dist/nodejs/sockets/error.js +120 -0
- package/dist/nodejs/sockets/ip-name-lookup.d.ts +18 -0
- package/dist/nodejs/sockets/ip-name-lookup.js +84 -0
- package/dist/nodejs/sockets/tcp.d.ts +333 -0
- package/dist/nodejs/sockets/tcp.js +723 -0
- package/dist/nodejs/sockets/udp.d.ts +224 -0
- package/dist/nodejs/sockets/udp.js +475 -0
- package/dist/nodejs/sockets.d.ts +11 -0
- package/{lib → dist}/nodejs/sockets.js +2 -5
- package/dist/nodejs/stream.d.ts +161 -0
- package/dist/nodejs/stream.js +388 -0
- package/dist/nodejs/workers/cli-worker.d.ts +1 -0
- package/dist/nodejs/workers/cli-worker.js +41 -0
- package/dist/nodejs/workers/filesystem-worker.d.ts +1 -0
- package/dist/nodejs/workers/filesystem-worker.js +102 -0
- package/dist/nodejs/workers/http-worker.d.ts +1 -0
- package/dist/nodejs/workers/http-worker.js +355 -0
- package/dist/nodejs/workers/resource-worker.d.ts +13 -0
- package/dist/nodejs/workers/resource-worker.js +152 -0
- package/dist/nodejs/workers/tcp-worker.d.ts +2 -0
- package/dist/nodejs/workers/tcp-worker.js +318 -0
- package/dist/nodejs/workers/udp-worker.d.ts +1 -0
- package/dist/nodejs/workers/udp-worker.js +156 -0
- package/package.json +20 -12
- package/types/interfaces/wasi-cli-environment.d.ts +3 -3
- package/types/interfaces/wasi-cli-exit.d.ts +12 -1
- package/types/interfaces/wasi-cli-run.d.ts +1 -1
- package/types/interfaces/wasi-cli-stderr.d.ts +3 -3
- package/types/interfaces/wasi-cli-stdin.d.ts +5 -5
- package/types/interfaces/wasi-cli-stdout.d.ts +3 -3
- package/types/interfaces/wasi-cli-terminal-input.d.ts +1 -1
- package/types/interfaces/wasi-cli-terminal-output.d.ts +1 -1
- package/types/interfaces/wasi-cli-terminal-stderr.d.ts +1 -1
- package/types/interfaces/wasi-cli-terminal-stdin.d.ts +1 -1
- package/types/interfaces/wasi-cli-terminal-stdout.d.ts +1 -1
- package/types/interfaces/wasi-cli-types.d.ts +5 -5
- package/types/interfaces/wasi-clocks-monotonic-clock.d.ts +3 -3
- package/types/interfaces/wasi-clocks-system-clock.d.ts +6 -6
- package/types/interfaces/wasi-clocks-types.d.ts +1 -1
- package/types/interfaces/wasi-filesystem-preopens.d.ts +1 -1
- package/types/interfaces/wasi-filesystem-types.d.ts +74 -74
- package/types/interfaces/wasi-http-client.d.ts +1 -1
- package/types/interfaces/wasi-http-handler.d.ts +1 -1
- package/types/interfaces/wasi-http-types.d.ts +34 -34
- package/types/interfaces/wasi-random-insecure-seed.d.ts +5 -5
- package/types/interfaces/wasi-random-insecure.d.ts +5 -5
- package/types/interfaces/wasi-random-random.d.ts +5 -5
- package/types/interfaces/wasi-sockets-ip-name-lookup.d.ts +11 -11
- package/types/interfaces/wasi-sockets-types.d.ts +121 -121
- package/types/wasi-cli-command.d.ts +23 -23
- package/types/wasi-http-service.d.ts +14 -14
- package/lib/nodejs/cli.js +0 -127
- package/lib/nodejs/clocks.js +0 -98
- package/lib/nodejs/filesystem/descriptor.js +0 -1093
- package/lib/nodejs/filesystem/error.js +0 -92
- package/lib/nodejs/filesystem/utils.js +0 -19
- package/lib/nodejs/future.js +0 -190
- package/lib/nodejs/http/client.js +0 -161
- package/lib/nodejs/http/error.js +0 -71
- package/lib/nodejs/http/fields.js +0 -373
- package/lib/nodejs/http/request.js +0 -691
- package/lib/nodejs/http/response.js +0 -227
- package/lib/nodejs/http/server.js +0 -157
- package/lib/nodejs/sockets/address.js +0 -146
- package/lib/nodejs/sockets/error.js +0 -123
- package/lib/nodejs/sockets/ip-name-lookup.js +0 -93
- package/lib/nodejs/sockets/tcp.js +0 -801
- package/lib/nodejs/sockets/udp.js +0 -516
- package/lib/nodejs/stream.js +0 -437
- package/lib/nodejs/workers/cli-worker.js +0 -49
- package/lib/nodejs/workers/filesystem-worker.js +0 -119
- package/lib/nodejs/workers/http-worker.js +0 -417
- package/lib/nodejs/workers/resource-worker.js +0 -195
- package/lib/nodejs/workers/tcp-worker.js +0 -370
- package/lib/nodejs/workers/udp-worker.js +0 -194
- /package/{lib/nodejs/filesystem.js → dist/nodejs/filesystem.d.ts} +0 -0
- /package/{lib/nodejs/index.js → dist/nodejs/index.d.ts} +0 -0
|
@@ -0,0 +1,606 @@
|
|
|
1
|
+
import { HttpError } from "./error.js";
|
|
2
|
+
import { _fieldsLock, Fields } from "./fields.js";
|
|
3
|
+
import { FutureReader, future } from "../future.js";
|
|
4
|
+
import { DEFAULT_BYTE_STREAM_CHUNK_SIZE, StreamReader, _byteChunk, readableByteStreamFromReader, } from "../stream.js";
|
|
5
|
+
const symbolDispose = Symbol.dispose || Symbol.for("dispose");
|
|
6
|
+
const SUPPORTED_METHODS = [
|
|
7
|
+
"get",
|
|
8
|
+
"head",
|
|
9
|
+
"post",
|
|
10
|
+
"put",
|
|
11
|
+
"delete",
|
|
12
|
+
"connect",
|
|
13
|
+
"options",
|
|
14
|
+
"trace",
|
|
15
|
+
"patch",
|
|
16
|
+
];
|
|
17
|
+
let REQUEST_CREATE_TOKEN = null;
|
|
18
|
+
function token() {
|
|
19
|
+
return (REQUEST_CREATE_TOKEN ??= Symbol("RequestCreateToken"));
|
|
20
|
+
}
|
|
21
|
+
export class RequestOptions {
|
|
22
|
+
#connectTimeoutNs = undefined;
|
|
23
|
+
#firstByteTimeoutNs = undefined;
|
|
24
|
+
#betweenBytesTimeoutNs = undefined;
|
|
25
|
+
#immutable = false;
|
|
26
|
+
/**
|
|
27
|
+
* Construct a default ~RequestOptions~ value.
|
|
28
|
+
* WIT:
|
|
29
|
+
* ```
|
|
30
|
+
* constructor()
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
constructor() { }
|
|
34
|
+
/**
|
|
35
|
+
* The timeout for the initial connect to the HTTP Server.
|
|
36
|
+
* WIT:
|
|
37
|
+
* ```
|
|
38
|
+
* connect-timeout: func() -> option<duration>
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
* @returns {?number} Duration in milliseconds, or `null` if not set
|
|
42
|
+
*/
|
|
43
|
+
getConnectTimeout() {
|
|
44
|
+
return this.#connectTimeoutNs;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Set the timeout for the initial connect to the HTTP Server.
|
|
48
|
+
* WIT:
|
|
49
|
+
* ```
|
|
50
|
+
* set-connect-timeout: func(duration: option<duration>) -> result<_, request-options-error>
|
|
51
|
+
* ```
|
|
52
|
+
*
|
|
53
|
+
* @param {?bigint} duration Duration in nanoseconds, or `null` to clear
|
|
54
|
+
* @throws {HttpError} with payload.tag 'immutable' if this handle is immutable
|
|
55
|
+
*/
|
|
56
|
+
setConnectTimeout(duration) {
|
|
57
|
+
this._ensureMutable();
|
|
58
|
+
this.#connectTimeoutNs = duration ?? undefined;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* The timeout for receiving the first byte of the Response body.
|
|
62
|
+
* WIT:
|
|
63
|
+
* ```
|
|
64
|
+
* first-byte-timeout: func() -> option<duration>
|
|
65
|
+
* ```
|
|
66
|
+
*
|
|
67
|
+
* @returns {?bigint} Duration in nanoseconds, or `null` if not set
|
|
68
|
+
*/
|
|
69
|
+
getFirstByteTimeout() {
|
|
70
|
+
return this.#firstByteTimeoutNs;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Set the timeout for receiving the first byte of the Response body.
|
|
74
|
+
* WIT:
|
|
75
|
+
* ```
|
|
76
|
+
* set-first-byte-timeout: func(duration: option<duration>) -> result<_, request-options-error>
|
|
77
|
+
* ```
|
|
78
|
+
*
|
|
79
|
+
* @param {?bigint} duration Duration in nanoseconds, or `null` to clear
|
|
80
|
+
* @throws {HttpError} with payload.tag 'immutable' if this handle is immutable
|
|
81
|
+
*/
|
|
82
|
+
setFirstByteTimeout(duration) {
|
|
83
|
+
this._ensureMutable();
|
|
84
|
+
this.#firstByteTimeoutNs = duration ?? undefined;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* The timeout for receiving subsequent chunks of bytes in the Response body.
|
|
88
|
+
* WIT:
|
|
89
|
+
* ```
|
|
90
|
+
* between-bytes-timeout: func() -> option<duration>
|
|
91
|
+
* ```
|
|
92
|
+
*
|
|
93
|
+
* @returns {?bigint} Duration in nanoseconds, or `null` if not set
|
|
94
|
+
*/
|
|
95
|
+
getBetweenBytesTimeout() {
|
|
96
|
+
return this.#betweenBytesTimeoutNs;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Set the timeout for receiving subsequent chunks of bytes in the Response body.
|
|
100
|
+
* WIT:
|
|
101
|
+
* ```
|
|
102
|
+
* set-between-bytes-timeout: func(duration: option<duration>) -> result<_, request-options-error>
|
|
103
|
+
* ```
|
|
104
|
+
*
|
|
105
|
+
* @param {?bigint} duration Duration in nanoseconds, or `null` to clear
|
|
106
|
+
* @throws {HttpError} with payload.tag 'immutable' if this handle is immutable
|
|
107
|
+
*/
|
|
108
|
+
setBetweenBytesTimeout(duration) {
|
|
109
|
+
this._ensureMutable();
|
|
110
|
+
this.#betweenBytesTimeoutNs = duration ?? undefined;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Make a deep copy of the ~RequestOptions~.
|
|
114
|
+
* WIT:
|
|
115
|
+
* ```
|
|
116
|
+
* clone: func() -> request-options
|
|
117
|
+
* ```
|
|
118
|
+
*
|
|
119
|
+
* @returns {RequestOptions} A new mutable copy
|
|
120
|
+
*/
|
|
121
|
+
clone() {
|
|
122
|
+
const cloned = new RequestOptions();
|
|
123
|
+
cloned.#connectTimeoutNs = this.#connectTimeoutNs;
|
|
124
|
+
cloned.#firstByteTimeoutNs = this.#firstByteTimeoutNs;
|
|
125
|
+
cloned.#betweenBytesTimeoutNs = this.#betweenBytesTimeoutNs;
|
|
126
|
+
return cloned;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Mark options as immutable
|
|
130
|
+
*
|
|
131
|
+
* @param {RequestOptions} opts - The RequestOptions instance to mark as immutable
|
|
132
|
+
* @returns {RequestOptions} The same instance, now immutable
|
|
133
|
+
*/
|
|
134
|
+
static _lock(fields) {
|
|
135
|
+
if (fields) {
|
|
136
|
+
fields.#immutable = true;
|
|
137
|
+
}
|
|
138
|
+
return fields;
|
|
139
|
+
}
|
|
140
|
+
_ensureMutable() {
|
|
141
|
+
if (this.#immutable) {
|
|
142
|
+
throw new HttpError("immutable", "Cannot modify immutable RequestOptions");
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
export function _optionsLock(fields) {
|
|
147
|
+
return RequestOptions._lock(fields);
|
|
148
|
+
}
|
|
149
|
+
export class Request {
|
|
150
|
+
#method = { tag: "get" };
|
|
151
|
+
#headers = null;
|
|
152
|
+
#contents = null;
|
|
153
|
+
#options = null;
|
|
154
|
+
#scheme = undefined;
|
|
155
|
+
#authority = undefined;
|
|
156
|
+
#pathWithQuery = undefined;
|
|
157
|
+
#trailersFuture = null;
|
|
158
|
+
#requestFuture = null;
|
|
159
|
+
#contentsDispose = null;
|
|
160
|
+
#bodyOpen = false;
|
|
161
|
+
#bodyEnded = false;
|
|
162
|
+
constructor(t) {
|
|
163
|
+
if (t !== token()) {
|
|
164
|
+
throw new Error("Use Request.new(...) to create a Request");
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Construct a new Request with default values.
|
|
169
|
+
*
|
|
170
|
+
* WIT:
|
|
171
|
+
* ```
|
|
172
|
+
* new: static func(
|
|
173
|
+
* headers: headers,
|
|
174
|
+
* contents: option<stream<u8>>,
|
|
175
|
+
* trailers: future<result<option<trailers>, error-code>>,
|
|
176
|
+
* options: option<request-options>
|
|
177
|
+
* ) -> tuple<request, future<result<_, error-code>>>;
|
|
178
|
+
* ```
|
|
179
|
+
*
|
|
180
|
+
* @param {Fields} headers immutable headers resource
|
|
181
|
+
* @param {?StreamReader|object} contents optional body stream
|
|
182
|
+
* @param {FutureReader|Promise} trailers future for trailers
|
|
183
|
+
* @param {?RequestOptions} options optional RequestOptions
|
|
184
|
+
* @returns {[Request, FutureReader]}
|
|
185
|
+
* @throws {HttpError} with payload.tag 'invalid-argument' for invalid arguments
|
|
186
|
+
*
|
|
187
|
+
*/
|
|
188
|
+
static new(headers, contents, trailers, options) {
|
|
189
|
+
if (options != null && !(options instanceof RequestOptions)) {
|
|
190
|
+
throw new HttpError("invalid-argument", "options must be RequestOptions");
|
|
191
|
+
}
|
|
192
|
+
if (!(headers instanceof Fields)) {
|
|
193
|
+
throw new HttpError("invalid-argument", "headers must be Fields");
|
|
194
|
+
}
|
|
195
|
+
if (!(trailers instanceof FutureReader) && (!trailers || typeof trailers.then !== "function")) {
|
|
196
|
+
throw new HttpError("invalid-argument", "trailers must be FutureReader or Promise");
|
|
197
|
+
}
|
|
198
|
+
let dispose = null;
|
|
199
|
+
if (contents != null && !(contents instanceof StreamReader)) {
|
|
200
|
+
try {
|
|
201
|
+
dispose = contents[symbolDispose]?.bind(contents);
|
|
202
|
+
const inner = readableContentsStream(contents, headers);
|
|
203
|
+
contents = new StreamReader(inner);
|
|
204
|
+
}
|
|
205
|
+
catch (err) {
|
|
206
|
+
throw new HttpError("invalid-argument", err.message);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
// Generated P3 futures are lazy thenables so we want to observe them now so early error paths don't hang.
|
|
210
|
+
if (!(trailers instanceof FutureReader)) {
|
|
211
|
+
const promise = Promise.resolve(trailers);
|
|
212
|
+
trailers = new FutureReader(promise);
|
|
213
|
+
}
|
|
214
|
+
let request = new Request(token());
|
|
215
|
+
request.#headers = headers;
|
|
216
|
+
request.#contents = contents;
|
|
217
|
+
request.#contentsDispose = dispose;
|
|
218
|
+
request.#options = options;
|
|
219
|
+
request.#trailersFuture = trailers;
|
|
220
|
+
request.#pathWithQuery = undefined;
|
|
221
|
+
request.#scheme = undefined;
|
|
222
|
+
request.#authority = undefined;
|
|
223
|
+
_fieldsLock(request.#headers);
|
|
224
|
+
_optionsLock(request.#options);
|
|
225
|
+
const { tx, rx } = future();
|
|
226
|
+
request.#requestFuture = tx;
|
|
227
|
+
return [request, rx];
|
|
228
|
+
}
|
|
229
|
+
[symbolDispose]() {
|
|
230
|
+
if (this.#contents && !this.#bodyOpen && !this.#bodyEnded) {
|
|
231
|
+
this.#contentsDispose?.();
|
|
232
|
+
this.#contents.close();
|
|
233
|
+
}
|
|
234
|
+
this.#contents = null;
|
|
235
|
+
this.#contentsDispose = null;
|
|
236
|
+
this.#bodyOpen = false;
|
|
237
|
+
this.#bodyEnded = true;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Get the Method for the Request.
|
|
241
|
+
*
|
|
242
|
+
* WIT:
|
|
243
|
+
* ```
|
|
244
|
+
* method: func() -> method;
|
|
245
|
+
* ```
|
|
246
|
+
*
|
|
247
|
+
* @returns {method}
|
|
248
|
+
*/
|
|
249
|
+
getMethod() {
|
|
250
|
+
return this.#method;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Set the Method for the Request.
|
|
254
|
+
*
|
|
255
|
+
* WIT:
|
|
256
|
+
* ```
|
|
257
|
+
* set-method: func(method: method) -> result;
|
|
258
|
+
* ```
|
|
259
|
+
*
|
|
260
|
+
* @param {method} method
|
|
261
|
+
* @throws {HttpError} with payload.tag 'invalid-syntax' if method is invalid
|
|
262
|
+
*/
|
|
263
|
+
setMethod(method) {
|
|
264
|
+
this.#method = normalizeMethod(method);
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Get the HTTP Path and Query.
|
|
268
|
+
*
|
|
269
|
+
* WIT:
|
|
270
|
+
* ```
|
|
271
|
+
* path-with-query: func() -> option<string>;
|
|
272
|
+
* ```
|
|
273
|
+
*
|
|
274
|
+
* @returns {?string}
|
|
275
|
+
*/
|
|
276
|
+
getPathWithQuery() {
|
|
277
|
+
return this.#pathWithQuery;
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Set the HTTP Path and Query.
|
|
281
|
+
*
|
|
282
|
+
* WIT:
|
|
283
|
+
* ```
|
|
284
|
+
* set-path-with-query: func(path-with-query: option<string>) -> result;
|
|
285
|
+
* ```
|
|
286
|
+
*
|
|
287
|
+
* @param {?string} pathWithQuery
|
|
288
|
+
* @throws {HttpError} with payload.tag 'invalid-syntax' if invalid URI component
|
|
289
|
+
*/
|
|
290
|
+
setPathWithQuery(pathWithQuery) {
|
|
291
|
+
validateUrlPart(pathWithQuery, UrlPart.PATH_WITH_QUERY);
|
|
292
|
+
this.#pathWithQuery = pathWithQuery === "" ? "/" : (pathWithQuery ?? undefined);
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Get the HTTP Scheme.
|
|
296
|
+
*
|
|
297
|
+
* WIT:
|
|
298
|
+
* ```
|
|
299
|
+
* scheme: func() -> option<scheme>;
|
|
300
|
+
* ```
|
|
301
|
+
*
|
|
302
|
+
* @returns {?scheme}
|
|
303
|
+
*/
|
|
304
|
+
getScheme() {
|
|
305
|
+
return this.#scheme;
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Set the HTTP Scheme.
|
|
309
|
+
*
|
|
310
|
+
* WIT:
|
|
311
|
+
* ```
|
|
312
|
+
* set-scheme: func(scheme: option<scheme>) -> result;
|
|
313
|
+
* ```
|
|
314
|
+
*
|
|
315
|
+
* @param {?scheme} scheme
|
|
316
|
+
* @throws {HttpError} with payload.tag 'invalid-syntax' if invalid scheme
|
|
317
|
+
*/
|
|
318
|
+
setScheme(scheme) {
|
|
319
|
+
this.#scheme = normalizeScheme(scheme);
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Get the authority for the target URI.
|
|
323
|
+
*
|
|
324
|
+
* WIT:
|
|
325
|
+
* ```
|
|
326
|
+
* authority: func() -> option<string>;
|
|
327
|
+
* ```
|
|
328
|
+
*
|
|
329
|
+
* @returns {?string}
|
|
330
|
+
*/
|
|
331
|
+
getAuthority() {
|
|
332
|
+
return this.#authority;
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Set the authority for the target URI.
|
|
336
|
+
*
|
|
337
|
+
* WIT:
|
|
338
|
+
* ```
|
|
339
|
+
* set-authority: func(authority: option<string>) -> result;
|
|
340
|
+
* ```
|
|
341
|
+
*
|
|
342
|
+
* @param {?string} authority
|
|
343
|
+
* @throws {HttpError} with payload.tag 'invalid-syntax' if invalid authority
|
|
344
|
+
*/
|
|
345
|
+
setAuthority(authority) {
|
|
346
|
+
validateUrlPart(authority, UrlPart.AUTHORITY);
|
|
347
|
+
this.#authority = authority ?? undefined;
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Get the associated request-options resource.
|
|
351
|
+
*
|
|
352
|
+
* WIT:
|
|
353
|
+
* ```
|
|
354
|
+
* options: func() -> option<request-options>;
|
|
355
|
+
* ```
|
|
356
|
+
*
|
|
357
|
+
* @returns {?RequestOptions}
|
|
358
|
+
*/
|
|
359
|
+
getOptions() {
|
|
360
|
+
return this.#options;
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Get the headers resource (immutable).
|
|
364
|
+
*
|
|
365
|
+
* WIT:
|
|
366
|
+
* ```
|
|
367
|
+
* headers: func() -> headers;
|
|
368
|
+
* ```
|
|
369
|
+
*
|
|
370
|
+
* @returns {Fields}
|
|
371
|
+
*/
|
|
372
|
+
getHeaders() {
|
|
373
|
+
return this.#headers;
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Get body stream and trailers future, consuming the resource.
|
|
377
|
+
*
|
|
378
|
+
* WIT:
|
|
379
|
+
* ```
|
|
380
|
+
* consume-body: static func(this: request, res: future<result<_, error-code>>)
|
|
381
|
+
* -> tuple<stream<u8>, future<result<option<trailers>, error-code>>>;
|
|
382
|
+
* ```
|
|
383
|
+
*
|
|
384
|
+
* @param {Request} request - The request to consume.
|
|
385
|
+
* @param {FutureReader} res - A future for communicating errors.
|
|
386
|
+
* @returns {[StreamReader, FutureReader]} A tuple of [body stream, trailers future].
|
|
387
|
+
* @throws {HttpError} with payload.tag 'invalid-state' if body already open or consumed.
|
|
388
|
+
*/
|
|
389
|
+
static consumeBody(request, res) {
|
|
390
|
+
if (res && typeof res.then === "function") {
|
|
391
|
+
// TODO: Use res to abort/close the body transport on errors.
|
|
392
|
+
void Promise.resolve(res).catch(() => { });
|
|
393
|
+
}
|
|
394
|
+
if (request.#bodyOpen) {
|
|
395
|
+
throw new HttpError("invalid-state", "body already called and not yet closed");
|
|
396
|
+
}
|
|
397
|
+
if (request.#bodyEnded) {
|
|
398
|
+
throw new HttpError("invalid-state", "body has already been consumed");
|
|
399
|
+
}
|
|
400
|
+
if (!request.#contents) {
|
|
401
|
+
request.#bodyEnded = true;
|
|
402
|
+
return [request.#contents, request.#trailersFuture];
|
|
403
|
+
}
|
|
404
|
+
const reader = request.#contents;
|
|
405
|
+
request.#bodyOpen = true;
|
|
406
|
+
const readFn = reader.read.bind(reader);
|
|
407
|
+
reader.read = async (...args) => {
|
|
408
|
+
const chunk = await readFn(...args);
|
|
409
|
+
if (chunk === null) {
|
|
410
|
+
request.#bodyEnded = true;
|
|
411
|
+
request.#bodyOpen = false;
|
|
412
|
+
}
|
|
413
|
+
return chunk;
|
|
414
|
+
};
|
|
415
|
+
const closedFn = reader.close.bind(reader);
|
|
416
|
+
reader.close = () => {
|
|
417
|
+
closedFn();
|
|
418
|
+
request.#bodyEnded = true;
|
|
419
|
+
request.#bodyOpen = false;
|
|
420
|
+
};
|
|
421
|
+
return [request.#contents, request.#trailersFuture];
|
|
422
|
+
}
|
|
423
|
+
// TODO: placeholder
|
|
424
|
+
_resolve(result) {
|
|
425
|
+
if (this.#requestFuture) {
|
|
426
|
+
this.#requestFuture.write(result);
|
|
427
|
+
this.#requestFuture = null;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
function readableContentsStream(reader, headers) {
|
|
432
|
+
const expectedLength = contentLength(headers.copyAll());
|
|
433
|
+
if (expectedLength === null) {
|
|
434
|
+
return readableByteStreamFromReader(reader, { name: "contents" });
|
|
435
|
+
}
|
|
436
|
+
const source = typeof reader?.read === "function"
|
|
437
|
+
? reader
|
|
438
|
+
: new StreamReader(readableByteStreamFromReader(reader, { name: "contents" }));
|
|
439
|
+
return readableByteStreamFromReader(contentLengthReader(source, expectedLength), {
|
|
440
|
+
name: "contents",
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
function contentLengthReader(reader, expectedLength) {
|
|
444
|
+
let sent = 0n;
|
|
445
|
+
return {
|
|
446
|
+
async read() {
|
|
447
|
+
const result = await reader.read(readOpts(expectedLength - sent));
|
|
448
|
+
if (isIteratorResult(result) && result.rejectedLength !== undefined) {
|
|
449
|
+
throw requestBodySizeError(sent + BigInt(result.rejectedLength));
|
|
450
|
+
}
|
|
451
|
+
if (isIteratorResult(result) && result.done) {
|
|
452
|
+
if (sent < expectedLength) {
|
|
453
|
+
throw requestBodySizeError(sent);
|
|
454
|
+
}
|
|
455
|
+
return result;
|
|
456
|
+
}
|
|
457
|
+
const value = isIteratorResult(result) ? result.value : result;
|
|
458
|
+
const chunk = _byteChunk(value);
|
|
459
|
+
sent += BigInt(chunk.byteLength);
|
|
460
|
+
if (sent > expectedLength) {
|
|
461
|
+
throw requestBodySizeError(sent);
|
|
462
|
+
}
|
|
463
|
+
return { value: chunk, done: false };
|
|
464
|
+
},
|
|
465
|
+
cancel(reason) {
|
|
466
|
+
if (typeof reader.cancel === "function") {
|
|
467
|
+
return reader.cancel(reason);
|
|
468
|
+
}
|
|
469
|
+
if (typeof reader.close === "function") {
|
|
470
|
+
return reader.close();
|
|
471
|
+
}
|
|
472
|
+
return reader[symbolDispose]?.();
|
|
473
|
+
},
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
function readOpts(remaining) {
|
|
477
|
+
if (remaining <= 0n) {
|
|
478
|
+
return { count: 0, rejectLength: 0 };
|
|
479
|
+
}
|
|
480
|
+
const count = Number(remaining <= BigInt(DEFAULT_BYTE_STREAM_CHUNK_SIZE)
|
|
481
|
+
? remaining
|
|
482
|
+
: BigInt(DEFAULT_BYTE_STREAM_CHUNK_SIZE));
|
|
483
|
+
const opts = { count };
|
|
484
|
+
if (remaining <= BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
485
|
+
opts.rejectLength = Number(remaining);
|
|
486
|
+
}
|
|
487
|
+
return opts;
|
|
488
|
+
}
|
|
489
|
+
function requestBodySizeError(bytes) {
|
|
490
|
+
return { tag: "HTTP-request-body-size", val: bytes };
|
|
491
|
+
}
|
|
492
|
+
function isIteratorResult(value) {
|
|
493
|
+
return value != null && typeof value === "object" && typeof value.done === "boolean";
|
|
494
|
+
}
|
|
495
|
+
const decoder = new TextDecoder();
|
|
496
|
+
const contentLength = (entries) => {
|
|
497
|
+
const entry = entries.findLast(([name]) => name.toLowerCase() === "content-length");
|
|
498
|
+
if (!entry) {
|
|
499
|
+
return null;
|
|
500
|
+
}
|
|
501
|
+
const value = decoder.decode(entry[1]);
|
|
502
|
+
return /^\d+$/.test(value) ? BigInt(value) : null;
|
|
503
|
+
};
|
|
504
|
+
const UrlPart = {
|
|
505
|
+
PATH_WITH_QUERY: "pathWithQuery",
|
|
506
|
+
SCHEME: "scheme",
|
|
507
|
+
AUTHORITY: "authority",
|
|
508
|
+
};
|
|
509
|
+
function normalizeMethod(method) {
|
|
510
|
+
const VALUE_TOKEN_RE = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
|
|
511
|
+
if (typeof method === "string") {
|
|
512
|
+
if (!VALUE_TOKEN_RE.test(method)) {
|
|
513
|
+
throw new HttpError("invalid-syntax");
|
|
514
|
+
}
|
|
515
|
+
const lowercase = method.toLowerCase();
|
|
516
|
+
return SUPPORTED_METHODS.includes(lowercase)
|
|
517
|
+
? { tag: lowercase }
|
|
518
|
+
: { tag: "other", val: lowercase };
|
|
519
|
+
}
|
|
520
|
+
if (!method || typeof method !== "object" || typeof method.tag !== "string") {
|
|
521
|
+
throw new HttpError("invalid-syntax");
|
|
522
|
+
}
|
|
523
|
+
if (SUPPORTED_METHODS.includes(method.tag)) {
|
|
524
|
+
return { tag: method.tag };
|
|
525
|
+
}
|
|
526
|
+
if (method.tag === "other" && typeof method.val === "string" && VALUE_TOKEN_RE.test(method.val)) {
|
|
527
|
+
const standard = method.val.toLowerCase();
|
|
528
|
+
return SUPPORTED_METHODS.includes(standard) && method.val === method.val.toUpperCase()
|
|
529
|
+
? { tag: standard }
|
|
530
|
+
: { tag: "other", val: method.val };
|
|
531
|
+
}
|
|
532
|
+
throw new HttpError("invalid-syntax");
|
|
533
|
+
}
|
|
534
|
+
function normalizeScheme(scheme) {
|
|
535
|
+
if (scheme == null) {
|
|
536
|
+
return undefined;
|
|
537
|
+
}
|
|
538
|
+
if (typeof scheme === "string") {
|
|
539
|
+
validateUrlPart(scheme, UrlPart.SCHEME);
|
|
540
|
+
const uppercase = scheme.toUpperCase();
|
|
541
|
+
if (uppercase === "HTTP" || uppercase === "HTTPS") {
|
|
542
|
+
return { tag: uppercase };
|
|
543
|
+
}
|
|
544
|
+
return { tag: "other", val: scheme };
|
|
545
|
+
}
|
|
546
|
+
if (typeof scheme !== "object" || typeof scheme.tag !== "string") {
|
|
547
|
+
throw new HttpError("invalid-syntax");
|
|
548
|
+
}
|
|
549
|
+
if (scheme.tag === "HTTP" || scheme.tag === "HTTPS") {
|
|
550
|
+
return { tag: scheme.tag };
|
|
551
|
+
}
|
|
552
|
+
if (scheme.tag === "other" && typeof scheme.val === "string") {
|
|
553
|
+
validateUrlPart(scheme.val, UrlPart.SCHEME);
|
|
554
|
+
const uppercase = scheme.val.toUpperCase();
|
|
555
|
+
if (uppercase === "HTTP" || uppercase === "HTTPS") {
|
|
556
|
+
return { tag: uppercase };
|
|
557
|
+
}
|
|
558
|
+
return { tag: "other", val: scheme.val };
|
|
559
|
+
}
|
|
560
|
+
throw new HttpError("invalid-syntax");
|
|
561
|
+
}
|
|
562
|
+
export function _schemeToString(scheme) {
|
|
563
|
+
if (scheme === undefined) {
|
|
564
|
+
return undefined;
|
|
565
|
+
}
|
|
566
|
+
switch (scheme.tag) {
|
|
567
|
+
case "HTTP":
|
|
568
|
+
return "http";
|
|
569
|
+
case "HTTPS":
|
|
570
|
+
return "https";
|
|
571
|
+
case "other":
|
|
572
|
+
return scheme.val;
|
|
573
|
+
default:
|
|
574
|
+
throw new HttpError("invalid-syntax", `Invalid scheme: ${scheme.tag}`);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
function validateUrlPart(value, part) {
|
|
578
|
+
if (value == null) {
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
// URL parsing may normalize some raw control characters but wasi treats them as invalid syntax.
|
|
582
|
+
if (/[\u0000-\u001f\u007f]/.test(value)) {
|
|
583
|
+
throw new HttpError("invalid-syntax", `Invalid ${part}: ${value}`);
|
|
584
|
+
}
|
|
585
|
+
if (part === UrlPart.PATH_WITH_QUERY && /[ <>`]/.test(value)) {
|
|
586
|
+
throw new HttpError("invalid-syntax", `Invalid ${part}: ${value}`);
|
|
587
|
+
}
|
|
588
|
+
try {
|
|
589
|
+
switch (part) {
|
|
590
|
+
case UrlPart.PATH_WITH_QUERY:
|
|
591
|
+
new URL(value, "http://example");
|
|
592
|
+
break;
|
|
593
|
+
case UrlPart.SCHEME:
|
|
594
|
+
new URL(`${value}://example`);
|
|
595
|
+
break;
|
|
596
|
+
case UrlPart.AUTHORITY:
|
|
597
|
+
new URL(`http://${value}`);
|
|
598
|
+
break;
|
|
599
|
+
default:
|
|
600
|
+
throw new Error(`Unknown URL part: ${part}`);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
catch {
|
|
604
|
+
throw new HttpError("invalid-syntax", `Invalid ${part}: ${value}`);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { FutureReader } from "../future.js";
|
|
2
|
+
declare const symbolDispose: symbol;
|
|
3
|
+
export declare class Response {
|
|
4
|
+
#private;
|
|
5
|
+
[symbolDispose]: () => void;
|
|
6
|
+
/**
|
|
7
|
+
* @private
|
|
8
|
+
* Use Response.new(...) to create an instance
|
|
9
|
+
*/
|
|
10
|
+
constructor(t: any);
|
|
11
|
+
/**
|
|
12
|
+
* Construct a new Response with default status-code 200.
|
|
13
|
+
*
|
|
14
|
+
* WIT:
|
|
15
|
+
* ```
|
|
16
|
+
* new: static func(
|
|
17
|
+
* headers: headers,
|
|
18
|
+
* contents: option<stream<u8>>,
|
|
19
|
+
* trailers: future<result<option<trailers>, error-code>>
|
|
20
|
+
* ) -> tuple<response, future<result<_, error-code>>>;
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* @param {Fields} headers immutable headers resource
|
|
24
|
+
* @param {?StreamReader|object} contents optional body stream
|
|
25
|
+
* @param {FutureReader|Promise} trailers future for trailers
|
|
26
|
+
* @returns {{ res: Response, future: FutureReader }}
|
|
27
|
+
* @throws {HttpError} with payload.tag 'invalid-argument' for invalid arguments
|
|
28
|
+
*/
|
|
29
|
+
static new(headers: any, contents: any, trailers: any): [Response, FutureReader];
|
|
30
|
+
/**
|
|
31
|
+
* Get the HTTP Status Code for the Response.
|
|
32
|
+
*
|
|
33
|
+
* WIT:
|
|
34
|
+
* ```
|
|
35
|
+
* status-code: func() -> status-code;
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* @returns {number}
|
|
39
|
+
*/
|
|
40
|
+
getStatusCode(): number;
|
|
41
|
+
/**
|
|
42
|
+
* Set the HTTP Status Code for the Response.
|
|
43
|
+
*
|
|
44
|
+
* WIT:
|
|
45
|
+
* ```
|
|
46
|
+
* set-status-code: func(status-code: status-code) -> result;
|
|
47
|
+
* ```
|
|
48
|
+
*
|
|
49
|
+
* @param {number} code
|
|
50
|
+
* @throws {HttpError} with payload.tag 'invalid-argument'
|
|
51
|
+
* if code is not an integer in [100,599]
|
|
52
|
+
*/
|
|
53
|
+
setStatusCode(code: any): void;
|
|
54
|
+
/**
|
|
55
|
+
* Get the headers associated with the Response.
|
|
56
|
+
*
|
|
57
|
+
* WIT:
|
|
58
|
+
* ```
|
|
59
|
+
* headers: func() -> headers;
|
|
60
|
+
* ```
|
|
61
|
+
*
|
|
62
|
+
* @returns {Fields}
|
|
63
|
+
*/
|
|
64
|
+
getHeaders(): any;
|
|
65
|
+
/**
|
|
66
|
+
* Get body stream and trailers future, consuming the resource.
|
|
67
|
+
*
|
|
68
|
+
* WIT:
|
|
69
|
+
* ```
|
|
70
|
+
* consume-body: static func(this: response, res: future<result<_, error-code>>)
|
|
71
|
+
* -> tuple<stream<u8>, future<result<option<trailers>, error-code>>>;
|
|
72
|
+
* ```
|
|
73
|
+
*
|
|
74
|
+
* @param {Response} response - The response to consume.
|
|
75
|
+
* @param {FutureReader} res - A future for communicating errors.
|
|
76
|
+
* @returns {[StreamReader, FutureReader]} A tuple of [body stream, trailers future].
|
|
77
|
+
* @throws {HttpError} with payload.tag 'invalid-state' if body has already been opened or consumed.
|
|
78
|
+
*/
|
|
79
|
+
static consumeBody(response: any, res: any): any[];
|
|
80
|
+
_resolve(result: any): void;
|
|
81
|
+
}
|
|
82
|
+
export {};
|