@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.
Files changed (112) hide show
  1. package/dist/nodejs/cli.d.ts +51 -0
  2. package/dist/nodejs/cli.js +106 -0
  3. package/dist/nodejs/clocks.d.ts +55 -0
  4. package/dist/nodejs/clocks.js +83 -0
  5. package/dist/nodejs/filesystem/descriptor.d.ts +443 -0
  6. package/dist/nodejs/filesystem/descriptor.js +1010 -0
  7. package/dist/nodejs/filesystem/error.d.ts +61 -0
  8. package/dist/nodejs/filesystem/error.js +86 -0
  9. package/dist/nodejs/filesystem/utils.d.ts +8 -0
  10. package/dist/nodejs/filesystem/utils.js +25 -0
  11. package/dist/nodejs/filesystem.js +2 -0
  12. package/dist/nodejs/finalization.d.ts +2 -0
  13. package/{lib → dist}/nodejs/finalization.js +15 -21
  14. package/dist/nodejs/future.d.ts +98 -0
  15. package/dist/nodejs/future.js +176 -0
  16. package/dist/nodejs/http/client.d.ts +22 -0
  17. package/dist/nodejs/http/client.js +138 -0
  18. package/dist/nodejs/http/error.d.ts +18 -0
  19. package/dist/nodejs/http/error.js +62 -0
  20. package/dist/nodejs/http/fields.d.ts +144 -0
  21. package/dist/nodejs/http/fields.js +327 -0
  22. package/dist/nodejs/http/request.d.ts +256 -0
  23. package/dist/nodejs/http/request.js +606 -0
  24. package/dist/nodejs/http/response.d.ts +82 -0
  25. package/dist/nodejs/http/response.js +202 -0
  26. package/dist/nodejs/http/server.d.ts +13 -0
  27. package/dist/nodejs/http/server.js +127 -0
  28. package/dist/nodejs/http.d.ts +19 -0
  29. package/{lib → dist}/nodejs/http.js +13 -18
  30. package/dist/nodejs/index.js +8 -0
  31. package/dist/nodejs/random.d.ts +5 -0
  32. package/{lib → dist}/nodejs/random.js +2 -8
  33. package/dist/nodejs/sockets/address.d.ts +78 -0
  34. package/dist/nodejs/sockets/address.js +120 -0
  35. package/dist/nodejs/sockets/error.d.ts +83 -0
  36. package/dist/nodejs/sockets/error.js +120 -0
  37. package/dist/nodejs/sockets/ip-name-lookup.d.ts +18 -0
  38. package/dist/nodejs/sockets/ip-name-lookup.js +84 -0
  39. package/dist/nodejs/sockets/tcp.d.ts +333 -0
  40. package/dist/nodejs/sockets/tcp.js +723 -0
  41. package/dist/nodejs/sockets/udp.d.ts +224 -0
  42. package/dist/nodejs/sockets/udp.js +475 -0
  43. package/dist/nodejs/sockets.d.ts +11 -0
  44. package/{lib → dist}/nodejs/sockets.js +2 -5
  45. package/dist/nodejs/stream.d.ts +161 -0
  46. package/dist/nodejs/stream.js +388 -0
  47. package/dist/nodejs/workers/cli-worker.d.ts +1 -0
  48. package/dist/nodejs/workers/cli-worker.js +41 -0
  49. package/dist/nodejs/workers/filesystem-worker.d.ts +1 -0
  50. package/dist/nodejs/workers/filesystem-worker.js +102 -0
  51. package/dist/nodejs/workers/http-worker.d.ts +1 -0
  52. package/dist/nodejs/workers/http-worker.js +355 -0
  53. package/dist/nodejs/workers/resource-worker.d.ts +13 -0
  54. package/dist/nodejs/workers/resource-worker.js +152 -0
  55. package/dist/nodejs/workers/tcp-worker.d.ts +2 -0
  56. package/dist/nodejs/workers/tcp-worker.js +318 -0
  57. package/dist/nodejs/workers/udp-worker.d.ts +1 -0
  58. package/dist/nodejs/workers/udp-worker.js +156 -0
  59. package/package.json +20 -12
  60. package/types/interfaces/wasi-cli-environment.d.ts +3 -3
  61. package/types/interfaces/wasi-cli-exit.d.ts +12 -1
  62. package/types/interfaces/wasi-cli-run.d.ts +1 -1
  63. package/types/interfaces/wasi-cli-stderr.d.ts +3 -3
  64. package/types/interfaces/wasi-cli-stdin.d.ts +5 -5
  65. package/types/interfaces/wasi-cli-stdout.d.ts +3 -3
  66. package/types/interfaces/wasi-cli-terminal-input.d.ts +1 -1
  67. package/types/interfaces/wasi-cli-terminal-output.d.ts +1 -1
  68. package/types/interfaces/wasi-cli-terminal-stderr.d.ts +1 -1
  69. package/types/interfaces/wasi-cli-terminal-stdin.d.ts +1 -1
  70. package/types/interfaces/wasi-cli-terminal-stdout.d.ts +1 -1
  71. package/types/interfaces/wasi-cli-types.d.ts +5 -5
  72. package/types/interfaces/wasi-clocks-monotonic-clock.d.ts +3 -3
  73. package/types/interfaces/wasi-clocks-system-clock.d.ts +6 -6
  74. package/types/interfaces/wasi-clocks-types.d.ts +1 -1
  75. package/types/interfaces/wasi-filesystem-preopens.d.ts +1 -1
  76. package/types/interfaces/wasi-filesystem-types.d.ts +74 -74
  77. package/types/interfaces/wasi-http-client.d.ts +1 -1
  78. package/types/interfaces/wasi-http-handler.d.ts +1 -1
  79. package/types/interfaces/wasi-http-types.d.ts +34 -34
  80. package/types/interfaces/wasi-random-insecure-seed.d.ts +5 -5
  81. package/types/interfaces/wasi-random-insecure.d.ts +5 -5
  82. package/types/interfaces/wasi-random-random.d.ts +5 -5
  83. package/types/interfaces/wasi-sockets-ip-name-lookup.d.ts +11 -11
  84. package/types/interfaces/wasi-sockets-types.d.ts +121 -121
  85. package/types/wasi-cli-command.d.ts +23 -23
  86. package/types/wasi-http-service.d.ts +14 -14
  87. package/lib/nodejs/cli.js +0 -127
  88. package/lib/nodejs/clocks.js +0 -98
  89. package/lib/nodejs/filesystem/descriptor.js +0 -1093
  90. package/lib/nodejs/filesystem/error.js +0 -92
  91. package/lib/nodejs/filesystem/utils.js +0 -19
  92. package/lib/nodejs/future.js +0 -190
  93. package/lib/nodejs/http/client.js +0 -161
  94. package/lib/nodejs/http/error.js +0 -71
  95. package/lib/nodejs/http/fields.js +0 -373
  96. package/lib/nodejs/http/request.js +0 -691
  97. package/lib/nodejs/http/response.js +0 -227
  98. package/lib/nodejs/http/server.js +0 -157
  99. package/lib/nodejs/sockets/address.js +0 -146
  100. package/lib/nodejs/sockets/error.js +0 -123
  101. package/lib/nodejs/sockets/ip-name-lookup.js +0 -93
  102. package/lib/nodejs/sockets/tcp.js +0 -801
  103. package/lib/nodejs/sockets/udp.js +0 -516
  104. package/lib/nodejs/stream.js +0 -437
  105. package/lib/nodejs/workers/cli-worker.js +0 -49
  106. package/lib/nodejs/workers/filesystem-worker.js +0 -119
  107. package/lib/nodejs/workers/http-worker.js +0 -417
  108. package/lib/nodejs/workers/resource-worker.js +0 -195
  109. package/lib/nodejs/workers/tcp-worker.js +0 -370
  110. package/lib/nodejs/workers/udp-worker.js +0 -194
  111. /package/{lib/nodejs/filesystem.js → dist/nodejs/filesystem.d.ts} +0 -0
  112. /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,8 @@
1
+ /** Map fs.Dirent -> WASI type */
2
+ export declare function wasiTypeFromDirent(obj: any): {
3
+ tag: string;
4
+ val?: undefined;
5
+ } | {
6
+ tag: string;
7
+ val: any;
8
+ };
@@ -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
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./filesystem/error.js";
2
+ export * from "./filesystem/descriptor.js";
@@ -0,0 +1,2 @@
1
+ export declare function registerDispose(resource: any, parent: any, id: any, disposeFn: any): () => void;
2
+ export declare function earlyDispose(finalizer: () => void): void;
@@ -1,33 +1,27 @@
1
1
  let REGISTRY = null;
2
-
3
2
  function getRegistry() {
4
- if (!REGISTRY) {
5
- REGISTRY = new FinalizationRegistry((dispose) => dispose());
6
- }
7
- return REGISTRY;
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
- const dummySymbol = Symbol();
18
-
19
- const finalizer = () => {
20
- if (parent?.[dummySymbol]) {
21
- return;
22
- }
23
- disposeFn(id);
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
- getRegistry().unregister(finalizer);
32
- finalizer();
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
+ }