@bytecodealliance/preview3-shim 0.1.0-rc.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 (67) hide show
  1. package/LICENSE +220 -0
  2. package/README.md +14 -0
  3. package/lib/nodejs/cli.js +127 -0
  4. package/lib/nodejs/clocks.js +100 -0
  5. package/lib/nodejs/filesystem/descriptor.js +997 -0
  6. package/lib/nodejs/filesystem/error.js +92 -0
  7. package/lib/nodejs/filesystem/utils.js +19 -0
  8. package/lib/nodejs/filesystem.js +2 -0
  9. package/lib/nodejs/finalization.js +33 -0
  10. package/lib/nodejs/future.js +187 -0
  11. package/lib/nodejs/http/client.js +146 -0
  12. package/lib/nodejs/http/error.js +71 -0
  13. package/lib/nodejs/http/fields.js +359 -0
  14. package/lib/nodejs/http/request.js +589 -0
  15. package/lib/nodejs/http/response.js +212 -0
  16. package/lib/nodejs/http/server.js +157 -0
  17. package/lib/nodejs/http.js +34 -0
  18. package/lib/nodejs/index.js +8 -0
  19. package/lib/nodejs/random.js +14 -0
  20. package/lib/nodejs/sockets/address.js +146 -0
  21. package/lib/nodejs/sockets/error.js +123 -0
  22. package/lib/nodejs/sockets/ip-name-lookup.js +93 -0
  23. package/lib/nodejs/sockets/tcp.js +776 -0
  24. package/lib/nodejs/sockets/udp.js +516 -0
  25. package/lib/nodejs/sockets.js +14 -0
  26. package/lib/nodejs/stream.js +423 -0
  27. package/lib/nodejs/workers/cli-worker.js +49 -0
  28. package/lib/nodejs/workers/filesystem-worker.js +119 -0
  29. package/lib/nodejs/workers/http-worker.js +387 -0
  30. package/lib/nodejs/workers/resource-worker.js +151 -0
  31. package/lib/nodejs/workers/tcp-worker.js +283 -0
  32. package/lib/nodejs/workers/udp-worker.js +194 -0
  33. package/package.json +57 -0
  34. package/types/cli.d.ts +12 -0
  35. package/types/clocks.d.ts +2 -0
  36. package/types/filesystem.d.ts +2 -0
  37. package/types/http.d.ts +4 -0
  38. package/types/index.d.ts +6 -0
  39. package/types/interfaces/wasi-cli-environment.d.ts +21 -0
  40. package/types/interfaces/wasi-cli-exit.d.ts +6 -0
  41. package/types/interfaces/wasi-cli-run.d.ts +5 -0
  42. package/types/interfaces/wasi-cli-stderr.d.ts +14 -0
  43. package/types/interfaces/wasi-cli-stdin.d.ts +19 -0
  44. package/types/interfaces/wasi-cli-stdout.d.ts +14 -0
  45. package/types/interfaces/wasi-cli-terminal-input.d.ts +8 -0
  46. package/types/interfaces/wasi-cli-terminal-output.d.ts +8 -0
  47. package/types/interfaces/wasi-cli-terminal-stderr.d.ts +7 -0
  48. package/types/interfaces/wasi-cli-terminal-stdin.d.ts +7 -0
  49. package/types/interfaces/wasi-cli-terminal-stdout.d.ts +7 -0
  50. package/types/interfaces/wasi-cli-types.d.ts +15 -0
  51. package/types/interfaces/wasi-clocks-monotonic-clock.d.ts +33 -0
  52. package/types/interfaces/wasi-clocks-system-clock.d.ts +38 -0
  53. package/types/interfaces/wasi-clocks-types.d.ts +5 -0
  54. package/types/interfaces/wasi-filesystem-preopens.d.ts +6 -0
  55. package/types/interfaces/wasi-filesystem-types.d.ts +780 -0
  56. package/types/interfaces/wasi-http-client.d.ts +9 -0
  57. package/types/interfaces/wasi-http-handler.d.ts +9 -0
  58. package/types/interfaces/wasi-http-types.d.ts +619 -0
  59. package/types/interfaces/wasi-random-insecure-seed.d.ts +21 -0
  60. package/types/interfaces/wasi-random-insecure.d.ts +26 -0
  61. package/types/interfaces/wasi-random-random.d.ts +31 -0
  62. package/types/interfaces/wasi-sockets-ip-name-lookup.d.ts +77 -0
  63. package/types/interfaces/wasi-sockets-types.d.ts +830 -0
  64. package/types/random.d.ts +3 -0
  65. package/types/sockets.d.ts +2 -0
  66. package/types/wasi-cli-command.d.ts +23 -0
  67. package/types/wasi-http-service.d.ts +14 -0
@@ -0,0 +1,359 @@
1
+ import { HttpError } from "./error.js";
2
+
3
+ import * as http from "node:http";
4
+ const { validateHeaderName = () => {}, validateHeaderValue = () => {} } = http;
5
+
6
+ let FORBIDDEN_HEADERS = null;
7
+ export const _forbiddenHeaders = {
8
+ get value() {
9
+ return (FORBIDDEN_HEADERS ??= new Set([
10
+ "connection",
11
+ "keep-alive",
12
+ "host",
13
+ "custom-forbidden-header",
14
+ ]));
15
+ },
16
+ };
17
+
18
+ /**
19
+ * Implements the WASI Preview3 fields resource for HTTP headers and trailers
20
+ */
21
+ export class Fields {
22
+ #immutable = false;
23
+ /** @type {[string, Uint8Array][]} */
24
+ #entries = [];
25
+ /** @type {Map<string, [string, Uint8Array[]][]>} */
26
+ #table = new Map();
27
+
28
+ /**
29
+ * Constructs an empty HTTP Fields
30
+ * WIT:
31
+ * ```
32
+ * constructor();
33
+ * ```
34
+ */
35
+ constructor() {}
36
+
37
+ /**
38
+ * Constructs a Fields instance from a list of name-value pairs
39
+ * WIT:
40
+ * ```
41
+ * from-list: static func(entries: list<tuple<field-name,field-value>>) -> result<fields, header-error>;
42
+ * ```
43
+ *
44
+ * @param {[string, Uint8Array[]][]} entries - List of field name and value pairs
45
+ * @returns {Fields} A new Fields instance
46
+ * @throws {HttpError} With payload.tag 'invalid-syntax' if any name or value is invalid
47
+ * @throws {HttpError} With payload.tag 'forbidden' if any name is forbidden
48
+ */
49
+ static fromList(entries) {
50
+ const fields = new Fields();
51
+
52
+ for (const [name, values] of entries) {
53
+ for (const value of Array.isArray(values) ? values : [values]) {
54
+ fields.append(name, value);
55
+ }
56
+ }
57
+ return fields;
58
+ }
59
+
60
+ /**
61
+ * Get all values for a given field name
62
+ * WIT:
63
+ * ```
64
+ * get: func(name: field-name) -> list<field-value>;
65
+ * ```
66
+ *
67
+ * @param {string} name - The field name to get values for
68
+ * @returns {Uint8Array[]} List of values for the field
69
+ */
70
+ get(name) {
71
+ const tableEntries = this.#table.get(name.toLowerCase());
72
+ if (!tableEntries) {
73
+ return [];
74
+ }
75
+ return tableEntries.flatMap(([, v]) => v);
76
+ }
77
+
78
+ /**
79
+ * Check if a field name exists in the collection
80
+ * WIT:
81
+ * ```
82
+ * has: func(name: field-name) -> bool;
83
+ * ```
84
+ *
85
+ * @param {string} name - The field name to check
86
+ * @returns {boolean} True if the field exists
87
+ */
88
+ has(name) {
89
+ return this.#table.has(name.toLowerCase());
90
+ }
91
+
92
+ /**
93
+ * Set values for a field name, replacing any existing values
94
+ * WIT:
95
+ * ```
96
+ * set: func(name: field-name, value: list<field-value>) -> result<_, header-error>;
97
+ * ```
98
+ *
99
+ * @param {string} name - The field name
100
+ * @param {Uint8Array[]} values - The values to set
101
+ * @throws {HttpError} With payload.tag 'immutable' if the fields are immutable
102
+ * @throws {HttpError} With payload.tag 'invalid-syntax' if name or values are invalid
103
+ * @throws {HttpError} With payload.tag 'forbidden' if name is forbidden
104
+ */
105
+ set(name, values) {
106
+ this.#ensureMutable();
107
+
108
+ this.#validateName(name);
109
+ for (const value of values) {
110
+ this.#validateValue(name, value);
111
+ }
112
+
113
+ const lowercased = name.toLowerCase();
114
+
115
+ let bucket = this.#table.get(lowercased);
116
+ if (bucket) {
117
+ this.#entries = this.#entries.filter((e) => !bucket.includes(e));
118
+ bucket.splice(0, bucket.length);
119
+ } else {
120
+ bucket = [];
121
+ this.#table.set(lowercased, bucket);
122
+ }
123
+
124
+ for (const value of values) {
125
+ const entry = [name, value];
126
+ this.#entries.push(entry);
127
+ this.#table.get(lowercased).push(entry);
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Delete all values for a field name
133
+ * WIT:
134
+ * ```
135
+ * delete: func(name: field-name) -> result<_, header-error>;
136
+ * ```
137
+ *
138
+ * @param {string} name - The field name to delete
139
+ * @throws {HttpError} With payload.tag 'immutable' if the fields are immutable
140
+ */
141
+ delete(name) {
142
+ this.#ensureMutable();
143
+ const lowercased = name.toLowerCase();
144
+ const tableEntries = this.#table.get(lowercased);
145
+
146
+ if (tableEntries) {
147
+ this.#entries = this.#entries.filter((entry) => !tableEntries.includes(entry));
148
+ this.#table.delete(lowercased);
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Get all values for a field name and then delete them
154
+ * WIT:
155
+ * ```
156
+ * get-and-delete: func(name: field-name) -> result<list<field-value>, header-error>;
157
+ * ```
158
+ *
159
+ * @param {string} name - The field name to get and delete
160
+ * @returns {Uint8Array[]} List of values that were deleted
161
+ * @throws {HttpError} With payload.tag 'immutable' if the fields are immutable
162
+ */
163
+ getAndDelete(name) {
164
+ this.#ensureMutable();
165
+
166
+ const values = this.get(name);
167
+ this.delete(name);
168
+ return values;
169
+ }
170
+
171
+ /**
172
+ * Append a value to a field name
173
+ * WIT:
174
+ * ```
175
+ * append: func(name: field-name, value: field-value) -> result<_, header-error>;
176
+ * ```
177
+ *
178
+ * @param {string} name - The field name
179
+ * @param {Uint8Array} value - The value to append
180
+ * @throws {HttpError} With payload.tag 'immutable' if the fields are immutable
181
+ * @throws {HttpError} With payload.tag 'invalid-syntax' if name or value is invalid
182
+ * @throws {HttpError} With payload.tag 'forbidden' if name is forbidden
183
+ */
184
+ append(name, value) {
185
+ this.#ensureMutable();
186
+
187
+ this.#validateName(name);
188
+ this.#validateValue(name, value);
189
+
190
+ const lowercased = name.toLowerCase();
191
+ const entry = [name, value];
192
+ this.#entries.push(entry);
193
+
194
+ const tableEntries = this.#table.get(lowercased);
195
+ if (tableEntries) {
196
+ tableEntries.push(entry);
197
+ } else {
198
+ this.#table.set(lowercased, [entry]);
199
+ }
200
+ }
201
+
202
+ /**
203
+ * Get all entries as a list of name-value pairs
204
+ * WIT:
205
+ * ```
206
+ * copy-all: func() -> list<tuple<field-name,field-value>>;
207
+ * ```
208
+ *
209
+ * @returns {[string, Uint8Array][]} List of all entries
210
+ */
211
+ copyAll() {
212
+ return this.#entries.slice();
213
+ }
214
+
215
+ /**
216
+ * Create a deep copy of this Fields instance
217
+ * WIT:
218
+ * ```
219
+ * clone: func() -> fields;
220
+ * ```
221
+ *
222
+ * @returns {Fields} A new mutable Fields instance with the same entries
223
+ */
224
+ clone() {
225
+ const entries = this.#entries.slice();
226
+ return _fieldsFromEntriesChecked(entries);
227
+ }
228
+
229
+ /**
230
+ * Mark fields as immutable
231
+ *
232
+ * @param {Fields} fields - The Fields instance to mark as immutable
233
+ * @returns {Fields} The same instance, now immutable
234
+ */
235
+ static _lock(fields) {
236
+ fields.#immutable = true;
237
+ return fields;
238
+ }
239
+
240
+ /**
241
+ * Create a Fields instance from pre-validated entries
242
+ *
243
+ * @param {[string, Uint8Array][]} entries - Validated entries to use
244
+ * @returns {Fields} A new Fields instance with the provided entries
245
+ */
246
+ static _fromEntriesChecked(entries) {
247
+ const fields = new Fields();
248
+ fields.#entries = entries;
249
+
250
+ for (const entry of entries) {
251
+ const lowercase = entry[0].toLowerCase();
252
+ const existing = fields.#table.get(lowercase);
253
+
254
+ if (existing) {
255
+ existing.push(entry);
256
+ } else {
257
+ fields.#table.set(lowercase, [entry]);
258
+ }
259
+ }
260
+ return fields;
261
+ }
262
+
263
+ #validateName(name) {
264
+ try {
265
+ validateHeaderName(name);
266
+ } catch {
267
+ throw new HttpError("invalid-syntax", `Invalid header name: ${name}`);
268
+ }
269
+
270
+ if (_forbiddenHeaders.value.has(name.toLowerCase())) {
271
+ throw new HttpError("forbidden", `Header ${name} is forbidden`);
272
+ }
273
+ }
274
+
275
+ #validateValue(name, value) {
276
+ try {
277
+ validateHeaderValue(name, new TextDecoder().decode(value));
278
+ } catch {
279
+ throw new HttpError("invalid-syntax", `Invalid header value for ${name}`);
280
+ }
281
+ }
282
+
283
+ #ensureMutable() {
284
+ if (this.#immutable) {
285
+ throw new HttpError("immutable", "Cannot modify immutable fields");
286
+ }
287
+ }
288
+ }
289
+
290
+ export function _fieldsLock(fields) {
291
+ return Fields._lock(fields);
292
+ }
293
+
294
+ export function _fieldsFromEntriesChecked(entries) {
295
+ return Fields._fromEntriesChecked(entries);
296
+ }
297
+
298
+ export async function _readTrailersForTransport(trailers) {
299
+ const fields = _unwrapTrailersResult(await trailers.read());
300
+
301
+ if (fields == null) {
302
+ return undefined;
303
+ }
304
+
305
+ if (fields instanceof Fields) {
306
+ return fields.copyAll();
307
+ }
308
+
309
+ if (Array.isArray(fields)) {
310
+ return fields;
311
+ }
312
+
313
+ throw new HttpError("invalid-argument", "trailers must be Fields or a list of entries");
314
+ }
315
+
316
+ export function _trailerResultFromEntries(entries) {
317
+ return {
318
+ tag: "ok",
319
+ val: entries && entries.length ? _fieldsFromEntriesChecked(entries) : null,
320
+ };
321
+ }
322
+
323
+ function _unwrapTrailersResult(result) {
324
+ if (result == null) {
325
+ return undefined;
326
+ }
327
+
328
+ if (result instanceof Fields || Array.isArray(result)) {
329
+ return result;
330
+ }
331
+
332
+ if (typeof result === "object") {
333
+ if (result.tag === "ok") {
334
+ return _unwrapTrailersResult(result.val);
335
+ }
336
+
337
+ if (result.tag === "some") {
338
+ return _unwrapTrailersResult(result.val);
339
+ }
340
+
341
+ if (result.tag === "none") {
342
+ return undefined;
343
+ }
344
+
345
+ if (result.tag === "err") {
346
+ throw HttpError.from(result.val);
347
+ }
348
+
349
+ if (Object.hasOwn(result, "ok")) {
350
+ return _unwrapTrailersResult(result.ok);
351
+ }
352
+
353
+ if (Object.hasOwn(result, "err")) {
354
+ throw HttpError.from(result.err);
355
+ }
356
+ }
357
+
358
+ return result;
359
+ }