@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,61 @@
|
|
|
1
|
+
export declare const ERROR_MAP: {
|
|
2
|
+
EACCES: string;
|
|
3
|
+
EALREADY: string;
|
|
4
|
+
EBADF: string;
|
|
5
|
+
EBUSY: string;
|
|
6
|
+
EDEADLK: string;
|
|
7
|
+
EDQUOT: string;
|
|
8
|
+
EEXIST: string;
|
|
9
|
+
EFBIG: string;
|
|
10
|
+
EILSEQ: string;
|
|
11
|
+
EINPROGRESS: string;
|
|
12
|
+
EINTR: string;
|
|
13
|
+
EINVAL: string;
|
|
14
|
+
EIO: string;
|
|
15
|
+
EISDIR: string;
|
|
16
|
+
ELOOP: string;
|
|
17
|
+
EMLINK: string;
|
|
18
|
+
EMSGSIZE: string;
|
|
19
|
+
ENAMETOOLONG: string;
|
|
20
|
+
ENODEV: string;
|
|
21
|
+
ENOENT: string;
|
|
22
|
+
ENOLCK: string;
|
|
23
|
+
ENOMEM: string;
|
|
24
|
+
ENOSPC: string;
|
|
25
|
+
ENOTDIR: string;
|
|
26
|
+
ERR_FS_EISDIR: string;
|
|
27
|
+
ENOTEMPTY: string;
|
|
28
|
+
ENOTRECOVERABLE: string;
|
|
29
|
+
ENOTSUP: string;
|
|
30
|
+
ENOTTY: string;
|
|
31
|
+
ENXIO: string;
|
|
32
|
+
EOVERFLOW: string;
|
|
33
|
+
EPERM: string;
|
|
34
|
+
EPIPE: string;
|
|
35
|
+
EROFS: string;
|
|
36
|
+
ESPIPE: string;
|
|
37
|
+
ETXTBSY: string;
|
|
38
|
+
EXDEV: string;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Custom error for File operations with JCO compatible payload.
|
|
42
|
+
*/
|
|
43
|
+
export declare class FSError extends Error {
|
|
44
|
+
payload: any;
|
|
45
|
+
/**
|
|
46
|
+
* @param {string} tag – machine‐readable error tag
|
|
47
|
+
* @param {string} [message] – human‐readable message
|
|
48
|
+
* @param {any} [val] – optional extra data
|
|
49
|
+
* @param {object} [opts]
|
|
50
|
+
* @param {Error} [opts.cause]
|
|
51
|
+
*/
|
|
52
|
+
constructor(tag: any, message?: any, val?: any, opts?: {
|
|
53
|
+
cause?: unknown;
|
|
54
|
+
});
|
|
55
|
+
/**
|
|
56
|
+
* Create or rewrap an error into FSError
|
|
57
|
+
* @param {any} err – number, string, Error, or FSError
|
|
58
|
+
*/
|
|
59
|
+
static from(err: any): FSError;
|
|
60
|
+
}
|
|
61
|
+
export declare function filesystemErrorCode(err: any): any;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
export const ERROR_MAP = {
|
|
2
|
+
EACCES: "access",
|
|
3
|
+
EALREADY: "already",
|
|
4
|
+
EBADF: "bad-descriptor",
|
|
5
|
+
EBUSY: "busy",
|
|
6
|
+
EDEADLK: "deadlock",
|
|
7
|
+
EDQUOT: "quota",
|
|
8
|
+
EEXIST: "exist",
|
|
9
|
+
EFBIG: "file-too-large",
|
|
10
|
+
EILSEQ: "illegal-byte-sequence",
|
|
11
|
+
EINPROGRESS: "in-progress",
|
|
12
|
+
EINTR: "interrupted",
|
|
13
|
+
EINVAL: "invalid",
|
|
14
|
+
EIO: "io",
|
|
15
|
+
EISDIR: "is-directory",
|
|
16
|
+
ELOOP: "loop",
|
|
17
|
+
EMLINK: "too-many-links",
|
|
18
|
+
EMSGSIZE: "message-size",
|
|
19
|
+
ENAMETOOLONG: "name-too-long",
|
|
20
|
+
ENODEV: "no-device",
|
|
21
|
+
ENOENT: "no-entry",
|
|
22
|
+
ENOLCK: "no-lock",
|
|
23
|
+
ENOMEM: "insufficient-memory",
|
|
24
|
+
ENOSPC: "insufficient-space",
|
|
25
|
+
ENOTDIR: "not-directory",
|
|
26
|
+
ERR_FS_EISDIR: "not-directory",
|
|
27
|
+
ENOTEMPTY: "not-empty",
|
|
28
|
+
ENOTRECOVERABLE: "not-recoverable",
|
|
29
|
+
ENOTSUP: "unsupported",
|
|
30
|
+
ENOTTY: "no-tty",
|
|
31
|
+
ENXIO: "no-such-device",
|
|
32
|
+
EOVERFLOW: "overflow",
|
|
33
|
+
EPERM: "not-permitted",
|
|
34
|
+
EPIPE: "pipe",
|
|
35
|
+
EROFS: "read-only",
|
|
36
|
+
ESPIPE: "invalid-seek",
|
|
37
|
+
ETXTBSY: "text-file-busy",
|
|
38
|
+
EXDEV: "cross-device",
|
|
39
|
+
};
|
|
40
|
+
function getErrorTag(e) {
|
|
41
|
+
if (e.code in ERROR_MAP) {
|
|
42
|
+
return ERROR_MAP[e.code];
|
|
43
|
+
}
|
|
44
|
+
// Windows gives this error for badly structured `//` reads, this seems like
|
|
45
|
+
// a slightly better error than unknown given that it's a common footgun.
|
|
46
|
+
if (e.code === -4094 || (e.code === "UNKNOWN" && e.errno === -4094)) {
|
|
47
|
+
return "no-such-device";
|
|
48
|
+
}
|
|
49
|
+
throw e;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Custom error for File operations with JCO compatible payload.
|
|
53
|
+
*/
|
|
54
|
+
export class FSError extends Error {
|
|
55
|
+
payload;
|
|
56
|
+
/**
|
|
57
|
+
* @param {string} tag – machine‐readable error tag
|
|
58
|
+
* @param {string} [message] – human‐readable message
|
|
59
|
+
* @param {any} [val] – optional extra data
|
|
60
|
+
* @param {object} [opts]
|
|
61
|
+
* @param {Error} [opts.cause]
|
|
62
|
+
*/
|
|
63
|
+
constructor(tag, message = undefined, val = undefined, opts = {}) {
|
|
64
|
+
super(message ?? `Error: ${tag}`, { cause: opts.cause });
|
|
65
|
+
this.name = "FSError";
|
|
66
|
+
this.payload = val !== undefined ? { tag, val } : { tag };
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Create or rewrap an error into FSError
|
|
70
|
+
* @param {any} err – number, string, Error, or FSError
|
|
71
|
+
*/
|
|
72
|
+
static from(err) {
|
|
73
|
+
if (err instanceof FSError) {
|
|
74
|
+
return err;
|
|
75
|
+
}
|
|
76
|
+
const tag = getErrorTag(err);
|
|
77
|
+
const message = err?.message;
|
|
78
|
+
return new FSError(tag, message, undefined, { cause: err });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
export function filesystemErrorCode(err) {
|
|
82
|
+
if (err?.payload?.tag) {
|
|
83
|
+
return err.payload;
|
|
84
|
+
}
|
|
85
|
+
return { tag: getErrorTag(err) };
|
|
86
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** Map fs.Dirent -> WASI type */
|
|
2
|
+
export function wasiTypeFromDirent(obj) {
|
|
3
|
+
if (obj.isFile()) {
|
|
4
|
+
return { tag: "regular-file" };
|
|
5
|
+
}
|
|
6
|
+
else if (obj.isSocket()) {
|
|
7
|
+
return { tag: "socket" };
|
|
8
|
+
}
|
|
9
|
+
else if (obj.isSymbolicLink()) {
|
|
10
|
+
return { tag: "symbolic-link" };
|
|
11
|
+
}
|
|
12
|
+
else if (obj.isFIFO()) {
|
|
13
|
+
return { tag: "fifo" };
|
|
14
|
+
}
|
|
15
|
+
else if (obj.isDirectory()) {
|
|
16
|
+
return { tag: "directory" };
|
|
17
|
+
}
|
|
18
|
+
else if (obj.isCharacterDevice()) {
|
|
19
|
+
return { tag: "character-device" };
|
|
20
|
+
}
|
|
21
|
+
else if (obj.isBlockDevice()) {
|
|
22
|
+
return { tag: "block-device" };
|
|
23
|
+
}
|
|
24
|
+
return { tag: "other", val: undefined };
|
|
25
|
+
}
|
|
@@ -1,33 +1,27 @@
|
|
|
1
1
|
let REGISTRY = null;
|
|
2
|
-
|
|
3
2
|
function getRegistry() {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
if (!REGISTRY) {
|
|
4
|
+
REGISTRY = new FinalizationRegistry((dispose) => dispose());
|
|
5
|
+
}
|
|
6
|
+
return REGISTRY;
|
|
8
7
|
}
|
|
9
|
-
|
|
10
8
|
// While strictly speaking all components should handle their disposal,
|
|
11
9
|
// this acts as a last-resort to catch all missed drops through the JS GC.
|
|
12
10
|
// Mainly for two cases - (1) components which are long lived, that get shut
|
|
13
11
|
// down and (2) users that interface with low-level WASI APIs directly in JS
|
|
14
12
|
// for various reasons may end up leaning on JS GC inadvertantly.
|
|
15
|
-
|
|
16
13
|
export function registerDispose(resource, parent = null, id, disposeFn) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
getRegistry().register(resource, finalizer, finalizer);
|
|
27
|
-
return finalizer;
|
|
14
|
+
const dummySymbol = Symbol();
|
|
15
|
+
const finalizer = () => {
|
|
16
|
+
if (parent?.[dummySymbol]) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
disposeFn(id);
|
|
20
|
+
};
|
|
21
|
+
getRegistry().register(resource, finalizer, finalizer);
|
|
22
|
+
return finalizer;
|
|
28
23
|
}
|
|
29
|
-
|
|
30
24
|
export function earlyDispose(finalizer) {
|
|
31
|
-
|
|
32
|
-
|
|
25
|
+
getRegistry().unregister(finalizer);
|
|
26
|
+
finalizer();
|
|
33
27
|
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a future with a reader and writer.
|
|
3
|
+
*
|
|
4
|
+
* @returns {Object} An object containing:
|
|
5
|
+
* - `tx` {FutureWriter}: The writer for the future.
|
|
6
|
+
* - `rx` {FutureReader}: The reader for the future.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* // Basic usage - creating a communication channel
|
|
10
|
+
* const { tx, rx } = future();
|
|
11
|
+
*
|
|
12
|
+
* const message = 'Hello world!';
|
|
13
|
+
* await tx.write(message);
|
|
14
|
+
*
|
|
15
|
+
* // first read yields the message
|
|
16
|
+
* const value = await rx.read();
|
|
17
|
+
* expect(value).toBe(message);
|
|
18
|
+
*
|
|
19
|
+
* // subsequent reads yield null
|
|
20
|
+
* const noValue = await rx.read();
|
|
21
|
+
* expect(noValue).toBeNull();
|
|
22
|
+
*/
|
|
23
|
+
export declare function future(): {
|
|
24
|
+
tx: FutureWriter;
|
|
25
|
+
rx: FutureReader;
|
|
26
|
+
};
|
|
27
|
+
export declare class FutureReader {
|
|
28
|
+
#private;
|
|
29
|
+
/**
|
|
30
|
+
* Constructs a FutureReader.
|
|
31
|
+
*
|
|
32
|
+
* @param {Promise} promise - The promise to be read.
|
|
33
|
+
* @throws {Error} If the provided argument is not a promise.
|
|
34
|
+
*/
|
|
35
|
+
constructor(promise: any);
|
|
36
|
+
/**
|
|
37
|
+
* Reads the value from the promise. Can only be called once.
|
|
38
|
+
*
|
|
39
|
+
* @returns {Promise<any>} Resolves with the value of the promise or null if already consumed.
|
|
40
|
+
* @throws {Error} If the promise is rejected.
|
|
41
|
+
*/
|
|
42
|
+
read(): Promise<any>;
|
|
43
|
+
then(resolve: any, reject: any): Promise<any>;
|
|
44
|
+
/**
|
|
45
|
+
* Cancels the future. Not supported for FutureReader.
|
|
46
|
+
*/
|
|
47
|
+
cancel(): Promise<void>;
|
|
48
|
+
/**
|
|
49
|
+
* Closes the reader. No resources are released in this implementation.
|
|
50
|
+
*/
|
|
51
|
+
close(): void;
|
|
52
|
+
/**
|
|
53
|
+
* Converts the reader back into a promise.
|
|
54
|
+
*
|
|
55
|
+
* @returns {Promise} The original promise.
|
|
56
|
+
*/
|
|
57
|
+
intoPromise(): any;
|
|
58
|
+
}
|
|
59
|
+
export declare class FutureWriter {
|
|
60
|
+
#private;
|
|
61
|
+
/**
|
|
62
|
+
* Constructs a FutureWriter.
|
|
63
|
+
*
|
|
64
|
+
* @param {Object} deferred - A deferred object with `resolve` and `reject` methods.
|
|
65
|
+
* @throws {Error} If the deferred object is invalid.
|
|
66
|
+
*/
|
|
67
|
+
constructor(deferred: any);
|
|
68
|
+
/**
|
|
69
|
+
* Resolves the promise with the given value.
|
|
70
|
+
*
|
|
71
|
+
* @param {any} value - The value to resolve the promise with.
|
|
72
|
+
* @throws {Error} If the writer is already closed.
|
|
73
|
+
*/
|
|
74
|
+
write(value: any): Promise<void>;
|
|
75
|
+
/**
|
|
76
|
+
* Rejects the promise with the given reason.
|
|
77
|
+
*
|
|
78
|
+
* @param {any} reason - The reason for rejecting the promise.
|
|
79
|
+
* @throws {Error} If the writer is already closed.
|
|
80
|
+
*/
|
|
81
|
+
abort(reason: any): Promise<void>;
|
|
82
|
+
/**
|
|
83
|
+
* Resolves the promise with null and closes the writer.
|
|
84
|
+
*/
|
|
85
|
+
close(): Promise<void>;
|
|
86
|
+
/**
|
|
87
|
+
* Rejects the promise with the given error and closes the writer.
|
|
88
|
+
*
|
|
89
|
+
* @param {Error} error - The error to reject the promise with.
|
|
90
|
+
*/
|
|
91
|
+
closeWithError(error: any): Promise<void>;
|
|
92
|
+
/**
|
|
93
|
+
* Converts the writer back into a promise.
|
|
94
|
+
*
|
|
95
|
+
* @returns {Promise} The original promise.
|
|
96
|
+
*/
|
|
97
|
+
intoPromise(): any;
|
|
98
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a future with a reader and writer.
|
|
3
|
+
*
|
|
4
|
+
* @returns {Object} An object containing:
|
|
5
|
+
* - `tx` {FutureWriter}: The writer for the future.
|
|
6
|
+
* - `rx` {FutureReader}: The reader for the future.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* // Basic usage - creating a communication channel
|
|
10
|
+
* const { tx, rx } = future();
|
|
11
|
+
*
|
|
12
|
+
* const message = 'Hello world!';
|
|
13
|
+
* await tx.write(message);
|
|
14
|
+
*
|
|
15
|
+
* // first read yields the message
|
|
16
|
+
* const value = await rx.read();
|
|
17
|
+
* expect(value).toBe(message);
|
|
18
|
+
*
|
|
19
|
+
* // subsequent reads yield null
|
|
20
|
+
* const noValue = await rx.read();
|
|
21
|
+
* expect(noValue).toBeNull();
|
|
22
|
+
*/
|
|
23
|
+
export function future() {
|
|
24
|
+
const def = deferred();
|
|
25
|
+
const tx = new FutureWriter(def);
|
|
26
|
+
const rx = new FutureReader(def.promise);
|
|
27
|
+
return { tx, rx };
|
|
28
|
+
}
|
|
29
|
+
function deferred() {
|
|
30
|
+
let resolve, reject;
|
|
31
|
+
const promise = new Promise((res, rej) => {
|
|
32
|
+
resolve = res;
|
|
33
|
+
reject = rej;
|
|
34
|
+
});
|
|
35
|
+
return { promise, resolve, reject };
|
|
36
|
+
}
|
|
37
|
+
export class FutureReader {
|
|
38
|
+
/** The underlying Promise to read from */
|
|
39
|
+
#promise;
|
|
40
|
+
/** Tracks if read() has been called (prevents multiple reads) */
|
|
41
|
+
#consumed = false;
|
|
42
|
+
/**
|
|
43
|
+
* Constructs a FutureReader.
|
|
44
|
+
*
|
|
45
|
+
* @param {Promise} promise - The promise to be read.
|
|
46
|
+
* @throws {Error} If the provided argument is not a promise.
|
|
47
|
+
*/
|
|
48
|
+
constructor(promise) {
|
|
49
|
+
if (!promise || typeof promise.then !== "function") {
|
|
50
|
+
throw new Error("Provided future must be a Promise");
|
|
51
|
+
}
|
|
52
|
+
if (promise instanceof Promise) {
|
|
53
|
+
promise.catch(() => { });
|
|
54
|
+
}
|
|
55
|
+
this.#promise = promise;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Reads the value from the promise. Can only be called once.
|
|
59
|
+
*
|
|
60
|
+
* @returns {Promise<any>} Resolves with the value of the promise or null if already consumed.
|
|
61
|
+
* @throws {Error} If the promise is rejected.
|
|
62
|
+
*/
|
|
63
|
+
async read() {
|
|
64
|
+
if (this.#consumed) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
return await this.#promise;
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
throw err;
|
|
72
|
+
}
|
|
73
|
+
finally {
|
|
74
|
+
this.#consumed = true;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
then(resolve, reject) {
|
|
78
|
+
return this.read().then(resolve, reject);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Cancels the future. Not supported for FutureReader.
|
|
82
|
+
*/
|
|
83
|
+
async cancel() {
|
|
84
|
+
throw new Error("FutureReader does not support cancel");
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Closes the reader. No resources are released in this implementation.
|
|
88
|
+
*/
|
|
89
|
+
close() {
|
|
90
|
+
// Nothing to release.
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Converts the reader back into a promise.
|
|
94
|
+
*
|
|
95
|
+
* @returns {Promise} The original promise.
|
|
96
|
+
*/
|
|
97
|
+
intoPromise() {
|
|
98
|
+
const promise = this.#promise;
|
|
99
|
+
this.#promise = null;
|
|
100
|
+
return promise;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
export class FutureWriter {
|
|
104
|
+
/** Deferred object with resolve/reject methods for controlling promise state */
|
|
105
|
+
#deferred;
|
|
106
|
+
/** Tracks if writer has been used */
|
|
107
|
+
#written = false;
|
|
108
|
+
/**
|
|
109
|
+
* Constructs a FutureWriter.
|
|
110
|
+
*
|
|
111
|
+
* @param {Object} deferred - A deferred object with `resolve` and `reject` methods.
|
|
112
|
+
* @throws {Error} If the deferred object is invalid.
|
|
113
|
+
*/
|
|
114
|
+
constructor(deferred) {
|
|
115
|
+
if (!deferred || typeof deferred.resolve !== "function") {
|
|
116
|
+
throw new Error("Provided deferred object must have a resolve method");
|
|
117
|
+
}
|
|
118
|
+
this.#deferred = deferred;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Resolves the promise with the given value.
|
|
122
|
+
*
|
|
123
|
+
* @param {any} value - The value to resolve the promise with.
|
|
124
|
+
* @throws {Error} If the writer is already closed.
|
|
125
|
+
*/
|
|
126
|
+
async write(value) {
|
|
127
|
+
this.#ensureState();
|
|
128
|
+
this.#written = true;
|
|
129
|
+
this.#deferred.resolve(value);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Rejects the promise with the given reason.
|
|
133
|
+
*
|
|
134
|
+
* @param {any} reason - The reason for rejecting the promise.
|
|
135
|
+
* @throws {Error} If the writer is already closed.
|
|
136
|
+
*/
|
|
137
|
+
async abort(reason) {
|
|
138
|
+
this.#ensureState();
|
|
139
|
+
this.#written = true;
|
|
140
|
+
this.#deferred.reject(reason);
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Resolves the promise with null and closes the writer.
|
|
144
|
+
*/
|
|
145
|
+
async close() {
|
|
146
|
+
this.#ensureState();
|
|
147
|
+
this.#written = true;
|
|
148
|
+
this.#deferred.resolve(null);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Rejects the promise with the given error and closes the writer.
|
|
152
|
+
*
|
|
153
|
+
* @param {Error} error - The error to reject the promise with.
|
|
154
|
+
*/
|
|
155
|
+
async closeWithError(error) {
|
|
156
|
+
this.#ensureState();
|
|
157
|
+
this.#written = true;
|
|
158
|
+
this.#deferred.reject(error);
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Converts the writer back into a promise.
|
|
162
|
+
*
|
|
163
|
+
* @returns {Promise} The original promise.
|
|
164
|
+
*/
|
|
165
|
+
intoPromise() {
|
|
166
|
+
this.close();
|
|
167
|
+
const promise = this.#deferred.promise;
|
|
168
|
+
this.#deferred = null;
|
|
169
|
+
return promise;
|
|
170
|
+
}
|
|
171
|
+
#ensureState() {
|
|
172
|
+
if (this.#written) {
|
|
173
|
+
throw new Error("FutureWriter is closed (already written)");
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Response } from "./response.js";
|
|
2
|
+
/**
|
|
3
|
+
* HTTP client interface.
|
|
4
|
+
*
|
|
5
|
+
* WIT:
|
|
6
|
+
* ```
|
|
7
|
+
* interface client {
|
|
8
|
+
* use types.{request, response, error-code};
|
|
9
|
+
* send: async func(request) -> result<response, error-code>;
|
|
10
|
+
* }
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
export declare const client: {
|
|
14
|
+
/**
|
|
15
|
+
* Send an HTTP request and return a response.
|
|
16
|
+
*
|
|
17
|
+
* @param {Request} req
|
|
18
|
+
* @returns {Promise<Response>}
|
|
19
|
+
* @throws {HttpError}
|
|
20
|
+
*/
|
|
21
|
+
send(req: any): Promise<Response>;
|
|
22
|
+
};
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { MessageChannel } from "node:worker_threads";
|
|
2
|
+
import { ResourceWorker } from "../workers/resource-worker.js";
|
|
3
|
+
import { StreamReader, readableByteStreamFromReader } from "../stream.js";
|
|
4
|
+
import { FutureReader, future } from "../future.js";
|
|
5
|
+
import { HttpError } from "./error.js";
|
|
6
|
+
import { _schemeToString, Request } from "./request.js";
|
|
7
|
+
import { Response } from "./response.js";
|
|
8
|
+
import { _fieldsFromEntriesChecked, _readTrailersForTransport, _trailerResultFromEntries, } from "./fields.js";
|
|
9
|
+
let WORKER = null;
|
|
10
|
+
function worker() {
|
|
11
|
+
return (WORKER ??= new ResourceWorker(new URL("../workers/http-worker.js", import.meta.url)));
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* HTTP client interface.
|
|
15
|
+
*
|
|
16
|
+
* WIT:
|
|
17
|
+
* ```
|
|
18
|
+
* interface client {
|
|
19
|
+
* use types.{request, response, error-code};
|
|
20
|
+
* send: async func(request) -> result<response, error-code>;
|
|
21
|
+
* }
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export const client = {
|
|
25
|
+
/**
|
|
26
|
+
* Send an HTTP request and return a response.
|
|
27
|
+
*
|
|
28
|
+
* @param {Request} req
|
|
29
|
+
* @returns {Promise<Response>}
|
|
30
|
+
* @throws {HttpError}
|
|
31
|
+
*/
|
|
32
|
+
async send(req) {
|
|
33
|
+
const scheme = _schemeToString(req.getScheme()) ?? "http";
|
|
34
|
+
const authority = req.getAuthority();
|
|
35
|
+
if (!authority) {
|
|
36
|
+
const err = new HttpError("internal-error", "Request.authority must be set for client.send");
|
|
37
|
+
req._resolve({ tag: "err", val: err.payload });
|
|
38
|
+
throw err;
|
|
39
|
+
}
|
|
40
|
+
const path = req.getPathWithQuery();
|
|
41
|
+
if (path == null) {
|
|
42
|
+
const err = new HttpError("HTTP-request-URI-invalid", "Request.pathWithQuery must be set");
|
|
43
|
+
req._resolve({ tag: "err", val: err.payload });
|
|
44
|
+
throw err;
|
|
45
|
+
}
|
|
46
|
+
const url = `${scheme}://${authority}${path}`;
|
|
47
|
+
const opts = req.getOptions();
|
|
48
|
+
const connectTimeoutNs = opts?.getConnectTimeout() ?? null;
|
|
49
|
+
const firstByteTimeoutNs = opts?.getFirstByteTimeout() ?? null;
|
|
50
|
+
const betweenBytesTimeoutNs = opts?.getBetweenBytesTimeout() ?? null;
|
|
51
|
+
const { rx: resRx } = future();
|
|
52
|
+
const [body, trailers] = Request.consumeBody(req, resRx);
|
|
53
|
+
const { port1: tx, port2: rx } = new MessageChannel();
|
|
54
|
+
const { port1: transmitRx, port2: transmitTx } = new MessageChannel();
|
|
55
|
+
const transfer = [rx, transmitTx];
|
|
56
|
+
const stream = body ? readableByteStreamFromReader(body, { name: "request body" }) : undefined;
|
|
57
|
+
if (stream) {
|
|
58
|
+
transfer.unshift(stream);
|
|
59
|
+
}
|
|
60
|
+
let transmitSettled = false;
|
|
61
|
+
const settleTransmit = (result) => {
|
|
62
|
+
if (transmitSettled) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
transmitSettled = true;
|
|
66
|
+
transmitRx.close();
|
|
67
|
+
req._resolve(result);
|
|
68
|
+
};
|
|
69
|
+
transmitRx.once("message", ({ val, err }) => {
|
|
70
|
+
if (err) {
|
|
71
|
+
settleTransmit({ tag: "err", val: err });
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
settleTransmit({ tag: "ok", val });
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
_readTrailersForTransport(trailers)
|
|
78
|
+
.then((val) => tx.postMessage({ val }))
|
|
79
|
+
.catch((err) => tx.postMessage({ err: HttpError.from(err).payload }))
|
|
80
|
+
.finally(() => tx.close());
|
|
81
|
+
try {
|
|
82
|
+
const method = req.getMethod();
|
|
83
|
+
const parts = await worker().run({
|
|
84
|
+
op: "client-request",
|
|
85
|
+
url,
|
|
86
|
+
method: method.tag === "other" ? method.val : method.tag,
|
|
87
|
+
headers: req.getHeaders().copyAll(),
|
|
88
|
+
timeouts: {
|
|
89
|
+
connectTimeoutNs,
|
|
90
|
+
firstByteTimeoutNs,
|
|
91
|
+
betweenBytesTimeoutNs,
|
|
92
|
+
},
|
|
93
|
+
trailers: rx,
|
|
94
|
+
transmit: transmitTx,
|
|
95
|
+
body: stream,
|
|
96
|
+
}, transfer);
|
|
97
|
+
return responseFromParts(parts);
|
|
98
|
+
}
|
|
99
|
+
catch (e) {
|
|
100
|
+
const err = HttpError.from(e);
|
|
101
|
+
settleTransmit({ tag: "err", val: err.payload });
|
|
102
|
+
throw err;
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
const responseFromParts = (parts) => {
|
|
107
|
+
const { headers, body, trailers, statusCode } = parts;
|
|
108
|
+
// Create a promise that resolves when the worker sends a message with trailers
|
|
109
|
+
const promise = new Promise((resolve, reject) => {
|
|
110
|
+
trailers.once("message", ({ val, err }) => {
|
|
111
|
+
trailers.close();
|
|
112
|
+
if (err) {
|
|
113
|
+
reject(err);
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
resolve(val);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
const future = new TrailerFutureReader(promise.then(_trailerResultFromEntries), trailers);
|
|
121
|
+
const contents = new StreamReader(body);
|
|
122
|
+
const fields = _fieldsFromEntriesChecked(headers);
|
|
123
|
+
const [res] = Response.new(fields, contents, future);
|
|
124
|
+
res.setStatusCode(statusCode);
|
|
125
|
+
return res;
|
|
126
|
+
};
|
|
127
|
+
class TrailerFutureReader extends FutureReader {
|
|
128
|
+
#trailers;
|
|
129
|
+
constructor(promise, trailers) {
|
|
130
|
+
super(promise);
|
|
131
|
+
this.#trailers = trailers;
|
|
132
|
+
}
|
|
133
|
+
close() {
|
|
134
|
+
this.#trailers?.close();
|
|
135
|
+
this.#trailers = null;
|
|
136
|
+
super.close();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom error class for WASI http interface that properly formats payload for Jco compatibility
|
|
3
|
+
*
|
|
4
|
+
* https://bytecodealliance.github.io/jco/wit-type-representations.html#result-considerations-idiomatic-js-errors-for-host-implementations
|
|
5
|
+
*/
|
|
6
|
+
export declare class HttpError extends Error {
|
|
7
|
+
payload: any;
|
|
8
|
+
/**
|
|
9
|
+
* Create a new WASI http error
|
|
10
|
+
* @param {string} tag - The error tag/type
|
|
11
|
+
* @param {string} [message] - Human-readable error message
|
|
12
|
+
* @param {any} [val] - Optional value/data for the error
|
|
13
|
+
*/
|
|
14
|
+
constructor(tag: any, message?: any, val?: any, opts?: {
|
|
15
|
+
cause?: unknown;
|
|
16
|
+
});
|
|
17
|
+
static from(err: any): HttpError;
|
|
18
|
+
}
|