@bytecodealliance/preview2-shim 0.17.7 → 0.17.8

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.
@@ -1,3 +1,6 @@
1
+ import { poll } from './io.js';
2
+ const { Pollable } = poll;
3
+
1
4
  export const monotonicClock = {
2
5
  resolution() {
3
6
  // usually we dont get sub-millisecond accuracy in the browser
@@ -10,15 +13,16 @@ export const monotonicClock = {
10
13
  },
11
14
  subscribeInstant(instant) {
12
15
  instant = BigInt(instant);
13
- const now = this.now();
16
+ const now = monotonicClock.now();
14
17
  if (instant <= now) {
15
- return this.subscribeDuration(0);
18
+ return new Pollable(new Promise(resolve => setTimeout(resolve, 0)));
16
19
  }
17
- return this.subscribeDuration(instant - now);
20
+ return monotonicClock.subscribeDuration(instant - now);
18
21
  },
19
- subscribeDuration(_duration) {
20
- _duration = BigInt(_duration);
21
- console.log(`[monotonic-clock] subscribe`);
22
+ subscribeDuration(duration) {
23
+ duration = BigInt(duration);
24
+ const ms = duration <= 0n ? 0 : Number(duration / 1_000_000n);
25
+ return new Pollable(new Promise(resolve => setTimeout(resolve, ms)));
22
26
  },
23
27
  };
24
28
 
@@ -1,155 +1,692 @@
1
- /**
2
- * @param {import("../../types/interfaces/wasi-http-types").Request} req
3
- * @returns {string}
4
- */
5
- export function send(req) {
6
- console.log(`[http] Send (browser) ${req.uri}`);
7
- try {
8
- const xhr = new XMLHttpRequest();
9
- xhr.open(req.method.toString(), req.uri, false);
10
- const requestHeaders = new Headers(req.headers);
11
- for (let [name, value] of requestHeaders.entries()) {
12
- if (name !== 'user-agent' && name !== 'host') {
13
- xhr.setRequestHeader(name, value);
1
+ import { streams, poll } from './io.js';
2
+
3
+ const { InputStream, OutputStream } = streams;
4
+ const { Pollable } = poll;
5
+
6
+ const symbolDispose = Symbol.dispose || Symbol.for('dispose');
7
+ const utf8Decoder = new TextDecoder();
8
+ const forbiddenHeaders = new Set(['connection', 'keep-alive', 'host']);
9
+ const DEFAULT_HTTP_TIMEOUT_NS = 600_000_000_000n;
10
+
11
+ // RFC 9110 compliant header validation
12
+ const TOKEN_RE = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
13
+ const FIELD_VALUE_RE = /^[\t\x20-\x7E\x80-\xFF]*$/;
14
+
15
+ function validateHeaderName(name) {
16
+ if (!TOKEN_RE.test(name)) {
17
+ throw { tag: 'invalid-syntax' };
18
+ }
19
+ }
20
+
21
+ function validateHeaderValue(value) {
22
+ const str = typeof value === 'string' ? value : utf8Decoder.decode(value);
23
+ if (!FIELD_VALUE_RE.test(str)) {
24
+ throw { tag: 'invalid-syntax' };
25
+ }
26
+ }
27
+
28
+ class Fields {
29
+ #immutable = false;
30
+ /** @type {[string, Uint8Array][]} */ #entries = [];
31
+ /** @type {Map<string, [string, Uint8Array][]>} */ #table = new Map();
32
+
33
+ static fromList(entries) {
34
+ const fields = new Fields();
35
+ for (const [key, value] of entries) {
36
+ fields.append(key, value);
37
+ }
38
+ return fields;
39
+ }
40
+
41
+ get(name) {
42
+ const tableEntries = this.#table.get(name.toLowerCase());
43
+ if (!tableEntries) {
44
+ return [];
45
+ }
46
+ return tableEntries.map(([, v]) => v);
47
+ }
48
+
49
+ set(name, values) {
50
+ if (this.#immutable) {
51
+ throw { tag: 'immutable' };
52
+ }
53
+ validateHeaderName(name);
54
+ for (const value of values) {
55
+ validateHeaderValue(value);
56
+ }
57
+ const lowercased = name.toLowerCase();
58
+ if (forbiddenHeaders.has(lowercased)) {
59
+ throw { tag: 'forbidden' };
60
+ }
61
+ const tableEntries = this.#table.get(lowercased);
62
+ if (tableEntries) {
63
+ this.#entries = this.#entries.filter(
64
+ (entry) => !tableEntries.includes(entry)
65
+ );
66
+ tableEntries.splice(0, tableEntries.length);
67
+ } else {
68
+ this.#table.set(lowercased, []);
69
+ }
70
+ const newTableEntries = this.#table.get(lowercased);
71
+ for (const value of values) {
72
+ const entry = [name, value];
73
+ this.#entries.push(entry);
74
+ newTableEntries.push(entry);
75
+ }
76
+ }
77
+
78
+ has(name) {
79
+ return this.#table.has(name.toLowerCase());
80
+ }
81
+
82
+ delete(name) {
83
+ if (this.#immutable) {
84
+ throw { tag: 'immutable' };
85
+ }
86
+ const lowercased = name.toLowerCase();
87
+ const tableEntries = this.#table.get(lowercased);
88
+ if (tableEntries) {
89
+ this.#entries = this.#entries.filter(
90
+ (entry) => !tableEntries.includes(entry)
91
+ );
92
+ this.#table.delete(lowercased);
93
+ }
94
+ }
95
+
96
+ append(name, value) {
97
+ if (this.#immutable) {
98
+ throw { tag: 'immutable' };
99
+ }
100
+ validateHeaderName(name);
101
+ validateHeaderValue(value);
102
+ const lowercased = name.toLowerCase();
103
+ if (forbiddenHeaders.has(lowercased)) {
104
+ throw { tag: 'forbidden' };
105
+ }
106
+ const entry = [name, value];
107
+ this.#entries.push(entry);
108
+ const tableEntries = this.#table.get(lowercased);
109
+ if (tableEntries) {
110
+ tableEntries.push(entry);
111
+ } else {
112
+ this.#table.set(lowercased, [entry]);
113
+ }
114
+ }
115
+
116
+ entries() {
117
+ return this.#entries;
118
+ }
119
+
120
+ clone() {
121
+ return fieldsFromEntriesChecked(this.#entries);
122
+ }
123
+
124
+ static _lock(fields) {
125
+ fields.#immutable = true;
126
+ return fields;
127
+ }
128
+
129
+ static _fromEntriesChecked(entries) {
130
+ const fields = new Fields();
131
+ fields.#entries = entries;
132
+ for (const entry of entries) {
133
+ const lowercase = entry[0].toLowerCase();
134
+ const existing = fields.#table.get(lowercase);
135
+ if (existing) {
136
+ existing.push(entry);
137
+ } else {
138
+ fields.#table.set(lowercase, [entry]);
14
139
  }
15
140
  }
16
- xhr.send(req.body && req.body.length > 0 ? req.body : null);
17
- const body = xhr.response
18
- ? new TextEncoder().encode(xhr.response)
19
- : undefined;
20
- const headers = [];
21
- xhr.getAllResponseHeaders()
22
- .trim()
23
- .split(/[\r\n]+/)
24
- .forEach((line) => {
25
- var parts = line.split(': ');
26
- var key = parts.shift();
27
- var value = parts.join(': ');
28
- headers.push([key, value]);
29
- });
141
+ return fields;
142
+ }
143
+ }
144
+ const fieldsLock = Fields._lock;
145
+ delete Fields._lock;
146
+ const fieldsFromEntriesChecked = Fields._fromEntriesChecked;
147
+ delete Fields._fromEntriesChecked;
148
+
149
+ class RequestOptions {
150
+ #connectTimeout = DEFAULT_HTTP_TIMEOUT_NS;
151
+ #firstByteTimeout = DEFAULT_HTTP_TIMEOUT_NS;
152
+ #betweenBytesTimeout = DEFAULT_HTTP_TIMEOUT_NS;
153
+ connectTimeout() {
154
+ return this.#connectTimeout;
155
+ }
156
+ setConnectTimeout(duration) {
157
+ if (duration < 0n) {
158
+ throw new Error('duration must not be negative');
159
+ }
160
+ this.#connectTimeout = duration;
161
+ }
162
+ firstByteTimeout() {
163
+ return this.#firstByteTimeout;
164
+ }
165
+ setFirstByteTimeout(duration) {
166
+ if (duration < 0n) {
167
+ throw new Error('duration must not be negative');
168
+ }
169
+ this.#firstByteTimeout = duration;
170
+ }
171
+ betweenBytesTimeout() {
172
+ return this.#betweenBytesTimeout;
173
+ }
174
+ setBetweenBytesTimeout(duration) {
175
+ if (duration < 0n) {
176
+ throw new Error('duration must not be negative');
177
+ }
178
+ this.#betweenBytesTimeout = duration;
179
+ }
180
+ }
181
+
182
+ class OutgoingBody {
183
+ #outputStream = null;
184
+ #chunks = [];
185
+ #finished = false;
186
+
187
+ write() {
188
+ const outputStream = this.#outputStream;
189
+ if (outputStream === null) {
190
+ throw undefined;
191
+ }
192
+ this.#outputStream = null;
193
+ return outputStream;
194
+ }
195
+
196
+ static finish(body, trailers) {
197
+ if (trailers) {
198
+ throw { tag: 'internal-error', val: 'trailers unsupported' };
199
+ }
200
+ if (body.#finished) {
201
+ throw { tag: 'internal-error', val: 'body already finished' };
202
+ }
203
+ body.#finished = true;
204
+ }
205
+
206
+ static _bodyData(outgoingBody) {
207
+ if (outgoingBody.#chunks.length === 0) {
208
+ return null;
209
+ }
210
+ let totalLen = 0;
211
+ for (const chunk of outgoingBody.#chunks) {
212
+ totalLen += chunk.byteLength;
213
+ }
214
+ const result = new Uint8Array(totalLen);
215
+ let offset = 0;
216
+ for (const chunk of outgoingBody.#chunks) {
217
+ result.set(chunk, offset);
218
+ offset += chunk.byteLength;
219
+ }
220
+ return result;
221
+ }
222
+
223
+ static _create() {
224
+ const outgoingBody = new OutgoingBody();
225
+ const chunks = outgoingBody.#chunks;
226
+ outgoingBody.#outputStream = new OutputStream({
227
+ write(buf) {
228
+ chunks.push(new Uint8Array(buf));
229
+ },
230
+ flush() {},
231
+ blockingFlush() {},
232
+ subscribe() {
233
+ return new Pollable();
234
+ },
235
+ });
236
+ return outgoingBody;
237
+ }
238
+
239
+ [symbolDispose]() {}
240
+ }
241
+ const outgoingBodyCreate = OutgoingBody._create;
242
+ delete OutgoingBody._create;
243
+ const outgoingBodyData = OutgoingBody._bodyData;
244
+ delete OutgoingBody._bodyData;
245
+
246
+ class OutgoingRequest {
247
+ /** @type {{ tag: string, val?: string }} */ #method = { tag: 'get' };
248
+ /** @type {{ tag: string, val?: string } | undefined} */ #scheme = undefined;
249
+ /** @type {string | undefined} */ #pathWithQuery = undefined;
250
+ /** @type {string | undefined} */ #authority = undefined;
251
+ /** @type {Fields} */ #headers;
252
+ /** @type {OutgoingBody} */ #body;
253
+ #bodyRequested = false;
254
+
255
+ constructor(headers) {
256
+ fieldsLock(headers);
257
+ this.#headers = headers;
258
+ this.#body = outgoingBodyCreate();
259
+ }
260
+
261
+ body() {
262
+ if (this.#bodyRequested) {
263
+ throw new Error('Body already requested');
264
+ }
265
+ this.#bodyRequested = true;
266
+ return this.#body;
267
+ }
268
+
269
+ method() {
270
+ return this.#method;
271
+ }
272
+
273
+ setMethod(method) {
274
+ if (method.tag === 'other' && !method.val.match(/^[a-zA-Z-]+$/)) {
275
+ throw undefined;
276
+ }
277
+ this.#method = method;
278
+ }
279
+
280
+ pathWithQuery() {
281
+ return this.#pathWithQuery;
282
+ }
283
+
284
+ setPathWithQuery(pathWithQuery) {
285
+ if (
286
+ pathWithQuery &&
287
+ !pathWithQuery.match(/^[a-zA-Z0-9.\-_~!$&'()*+,;=:@%?/]+$/)
288
+ ) {
289
+ throw undefined;
290
+ }
291
+ this.#pathWithQuery = pathWithQuery;
292
+ }
293
+
294
+ scheme() {
295
+ return this.#scheme;
296
+ }
297
+
298
+ setScheme(scheme) {
299
+ if (scheme?.tag === 'other' && !scheme.val.match(/^[a-zA-Z]+$/)) {
300
+ throw undefined;
301
+ }
302
+ this.#scheme = scheme;
303
+ }
304
+
305
+ authority() {
306
+ return this.#authority;
307
+ }
308
+
309
+ setAuthority(authority) {
310
+ if (authority) {
311
+ const [host, port, ...extra] = authority.split(':');
312
+ const portNum = Number(port);
313
+ if (
314
+ extra.length ||
315
+ (port !== undefined &&
316
+ (portNum.toString() !== port || portNum > 65535)) ||
317
+ !host.match(/^[a-zA-Z0-9-.]+$/)
318
+ ) {
319
+ throw undefined;
320
+ }
321
+ }
322
+ this.#authority = authority;
323
+ }
324
+
325
+ headers() {
326
+ return this.#headers;
327
+ }
328
+
329
+ [symbolDispose]() {}
330
+
331
+ static _handle(request, options) {
332
+ const scheme = schemeString(request.#scheme);
333
+ const method = request.#method.val || request.#method.tag;
334
+
335
+ if (!request.#pathWithQuery) {
336
+ throw { tag: 'HTTP-request-URI-invalid' };
337
+ }
338
+
339
+ const url = `${scheme}//${request.#authority || ''}${request.#pathWithQuery}`;
340
+
341
+ const headers = new Headers();
342
+ for (const [key, value] of request.#headers.entries()) {
343
+ const lowerKey = key.toLowerCase();
344
+ if (!forbiddenHeaders.has(lowerKey)) {
345
+ headers.set(key, utf8Decoder.decode(value));
346
+ }
347
+ }
348
+
349
+ const bodyData = outgoingBodyData(request.#body);
350
+
351
+ let timeoutMs = Number(DEFAULT_HTTP_TIMEOUT_NS / 1_000_000n);
352
+ if (options) {
353
+ const ct = options.connectTimeout?.() ?? DEFAULT_HTTP_TIMEOUT_NS;
354
+ const fbt = options.firstByteTimeout?.() ?? DEFAULT_HTTP_TIMEOUT_NS;
355
+ const minTimeout = ct < fbt ? ct : fbt;
356
+ timeoutMs = Number(minTimeout / 1_000_000n);
357
+ }
358
+
359
+ return futureIncomingResponseCreate(
360
+ url, method.toUpperCase(), headers, bodyData, timeoutMs
361
+ );
362
+ }
363
+ }
364
+ const outgoingRequestHandle = OutgoingRequest._handle;
365
+ delete OutgoingRequest._handle;
366
+
367
+ class IncomingBody {
368
+ #finished = false;
369
+ #stream = undefined;
370
+
371
+ stream() {
372
+ if (!this.#stream) {
373
+ throw undefined;
374
+ }
375
+ const stream = this.#stream;
376
+ this.#stream = null;
377
+ return stream;
378
+ }
379
+
380
+ static finish(incomingBody) {
381
+ if (incomingBody.#finished) {
382
+ throw new Error('incoming body already finished');
383
+ }
384
+ incomingBody.#finished = true;
385
+ return futureTrailersCreate();
386
+ }
387
+
388
+ [symbolDispose]() {}
389
+
390
+ static _create(fetchResponse) {
391
+ const incomingBody = new IncomingBody();
392
+ let buffer = null;
393
+ let bufferOffset = 0;
394
+ let done = false;
395
+ let reader = null;
396
+ let readPromise = null;
397
+
398
+ function ensureReader() {
399
+ if (!reader && fetchResponse.body) {
400
+ reader = fetchResponse.body.getReader();
401
+ }
402
+ }
403
+
404
+ function startRead() {
405
+ if (readPromise || done) { return; }
406
+ ensureReader();
407
+ if (!reader) {
408
+ done = true;
409
+ return;
410
+ }
411
+ readPromise = reader.read().then(
412
+ (result) => {
413
+ readPromise = null;
414
+ if (result.done) {
415
+ done = true;
416
+ } else {
417
+ buffer = result.value;
418
+ bufferOffset = 0;
419
+ }
420
+ },
421
+ () => {
422
+ readPromise = null;
423
+ done = true;
424
+ }
425
+ );
426
+ }
427
+
428
+ incomingBody.#stream = new InputStream({
429
+ read(len) {
430
+ if (done && (buffer === null || bufferOffset >= buffer.byteLength)) {
431
+ throw { tag: 'closed' };
432
+ }
433
+ if (buffer !== null && bufferOffset < buffer.byteLength) {
434
+ const available = buffer.byteLength - bufferOffset;
435
+ const toRead = Math.min(Number(len), available);
436
+ const slice = buffer.slice(bufferOffset, bufferOffset + toRead);
437
+ bufferOffset += toRead;
438
+ if (bufferOffset >= buffer.byteLength) {
439
+ buffer = null;
440
+ bufferOffset = 0;
441
+ if (!done) { startRead(); }
442
+ }
443
+ return slice;
444
+ }
445
+ throw { tag: 'would-block' };
446
+ },
447
+ blockingRead(len) {
448
+ if (done && (buffer === null || bufferOffset >= buffer.byteLength)) {
449
+ throw { tag: 'closed' };
450
+ }
451
+ if (buffer !== null && bufferOffset < buffer.byteLength) {
452
+ const available = buffer.byteLength - bufferOffset;
453
+ const toRead = Math.min(Number(len), available);
454
+ const slice = buffer.slice(bufferOffset, bufferOffset + toRead);
455
+ bufferOffset += toRead;
456
+ if (bufferOffset >= buffer.byteLength) {
457
+ buffer = null;
458
+ bufferOffset = 0;
459
+ if (!done) { startRead(); }
460
+ }
461
+ return slice;
462
+ }
463
+ startRead();
464
+ const waitFor = readPromise || Promise.resolve();
465
+ return waitFor.then(() => {
466
+ if (done && (buffer === null || bufferOffset >= buffer.byteLength)) {
467
+ throw { tag: 'closed' };
468
+ }
469
+ if (buffer !== null && bufferOffset < buffer.byteLength) {
470
+ const available = buffer.byteLength - bufferOffset;
471
+ const toRead = Math.min(Number(len), available);
472
+ const slice = buffer.slice(bufferOffset, bufferOffset + toRead);
473
+ bufferOffset += toRead;
474
+ if (bufferOffset >= buffer.byteLength) {
475
+ buffer = null;
476
+ bufferOffset = 0;
477
+ if (!done) { startRead(); }
478
+ }
479
+ return slice;
480
+ }
481
+ throw { tag: 'closed' };
482
+ });
483
+ },
484
+ subscribe() {
485
+ if (done || (buffer !== null && bufferOffset < buffer.byteLength)) {
486
+ return new Pollable();
487
+ }
488
+ startRead();
489
+ if (readPromise) {
490
+ return new Pollable(readPromise);
491
+ }
492
+ return new Pollable();
493
+ },
494
+ });
495
+
496
+ startRead();
497
+ return incomingBody;
498
+ }
499
+ }
500
+ const incomingBodyCreate = IncomingBody._create;
501
+ delete IncomingBody._create;
502
+
503
+ class IncomingResponse {
504
+ /** @type {Fields} */ #headers = undefined;
505
+ #status = 0;
506
+ /** @type {IncomingBody} */ #body;
507
+
508
+ status() {
509
+ return this.#status;
510
+ }
511
+
512
+ headers() {
513
+ return this.#headers;
514
+ }
515
+
516
+ consume() {
517
+ if (this.#body === undefined) {
518
+ throw undefined;
519
+ }
520
+ const body = this.#body;
521
+ this.#body = undefined;
522
+ return body;
523
+ }
524
+
525
+ [symbolDispose]() {}
526
+
527
+ static _create(fetchResponse) {
528
+ const res = new IncomingResponse();
529
+ res.#status = fetchResponse.status;
530
+
531
+ const headerEntries = [];
532
+ const encoder = new TextEncoder();
533
+ fetchResponse.headers.forEach((value, key) => {
534
+ headerEntries.push([key, encoder.encode(value)]);
535
+ });
536
+ res.#headers = fieldsLock(fieldsFromEntriesChecked(headerEntries));
537
+ res.#body = incomingBodyCreate(fetchResponse);
538
+ return res;
539
+ }
540
+ }
541
+ const incomingResponseCreate = IncomingResponse._create;
542
+ delete IncomingResponse._create;
543
+
544
+ class FutureTrailers {
545
+ #requested = false;
546
+ subscribe() {
547
+ return new Pollable();
548
+ }
549
+ get() {
550
+ if (this.#requested) {
551
+ return { tag: 'err' };
552
+ }
553
+ this.#requested = true;
30
554
  return {
31
- status: xhr.status,
555
+ tag: 'ok',
556
+ val: {
557
+ tag: 'ok',
558
+ val: undefined,
559
+ },
560
+ };
561
+ }
562
+ static _create() {
563
+ return new FutureTrailers();
564
+ }
565
+ }
566
+ const futureTrailersCreate = FutureTrailers._create;
567
+ delete FutureTrailers._create;
568
+
569
+ function mapFetchError(err) {
570
+ if (err.name === 'AbortError') {
571
+ return { tag: 'connection-timeout' };
572
+ }
573
+ if (err.name === 'TypeError') {
574
+ return { tag: 'connection-refused' };
575
+ }
576
+ return { tag: 'internal-error', val: err.message };
577
+ }
578
+
579
+ class FutureIncomingResponse {
580
+ #result = undefined;
581
+ #promise = null;
582
+
583
+ subscribe() {
584
+ return new Pollable(this.#promise);
585
+ }
586
+
587
+ get() {
588
+ if (this.#result === undefined) {
589
+ return undefined;
590
+ }
591
+ const result = this.#result;
592
+ this.#result = { tag: 'err' };
593
+ return result;
594
+ }
595
+
596
+ [symbolDispose]() {
597
+ this.#promise = null;
598
+ }
599
+
600
+ static _create(url, method, headers, bodyData, timeoutMs) {
601
+ const future = new FutureIncomingResponse();
602
+
603
+ const controller = new AbortController();
604
+ let timer;
605
+ if (timeoutMs < Infinity) {
606
+ timer = setTimeout(() => controller.abort(), timeoutMs);
607
+ }
608
+
609
+ const init = {
610
+ method,
32
611
  headers,
33
- body,
612
+ signal: controller.signal,
34
613
  };
35
- } catch (err) {
36
- throw new Error(err.message);
614
+ if (bodyData && method !== 'GET' && method !== 'HEAD') {
615
+ init.body = bodyData;
616
+ }
617
+
618
+ future.#promise = fetch(url, init).then(
619
+ (response) => {
620
+ if (timer) { clearTimeout(timer); }
621
+ future.#result = {
622
+ tag: 'ok',
623
+ val: {
624
+ tag: 'ok',
625
+ val: incomingResponseCreate(response),
626
+ },
627
+ };
628
+ },
629
+ (err) => {
630
+ if (timer) { clearTimeout(timer); }
631
+ future.#result = {
632
+ tag: 'ok',
633
+ val: {
634
+ tag: 'err',
635
+ val: mapFetchError(err),
636
+ },
637
+ };
638
+ }
639
+ );
640
+
641
+ return future;
37
642
  }
38
643
  }
644
+ const futureIncomingResponseCreate = FutureIncomingResponse._create;
645
+ delete FutureIncomingResponse._create;
39
646
 
40
- export const incomingHandler = {
41
- handle() {},
42
- };
647
+ function schemeString(scheme) {
648
+ if (!scheme) {
649
+ return 'https:';
650
+ }
651
+ switch (scheme.tag) {
652
+ case 'HTTP':
653
+ return 'http:';
654
+ case 'HTTPS':
655
+ return 'https:';
656
+ case 'other':
657
+ return scheme.val.toLowerCase() + ':';
658
+ }
659
+ }
660
+
661
+ function httpErrorCode(err) {
662
+ if (err.payload) {
663
+ return err.payload;
664
+ }
665
+ return {
666
+ tag: 'internal-error',
667
+ val: err.message,
668
+ };
669
+ }
43
670
 
44
671
  export const outgoingHandler = {
672
+ handle: outgoingRequestHandle,
673
+ };
674
+
675
+ export const incomingHandler = {
45
676
  handle() {},
46
677
  };
47
678
 
48
679
  export const types = {
49
- dropFields(_fields) {
50
- console.log('[types] Drop fields');
51
- },
52
- newFields(_entries) {
53
- console.log('[types] New fields');
54
- },
55
- fieldsGet(_fields, _name) {
56
- console.log('[types] Fields get');
57
- },
58
- fieldsSet(_fields, _name, _value) {
59
- console.log('[types] Fields set');
60
- },
61
- fieldsDelete(_fields, _name) {
62
- console.log('[types] Fields delete');
63
- },
64
- fieldsAppend(_fields, _name, _value) {
65
- console.log('[types] Fields append');
66
- },
67
- fieldsEntries(_fields) {
68
- console.log('[types] Fields entries');
69
- },
70
- fieldsClone(_fields) {
71
- console.log('[types] Fields clone');
72
- },
73
- finishIncomingStream(s) {
74
- console.log(`[types] Finish incoming stream ${s}`);
75
- },
76
- finishOutgoingStream(s, _trailers) {
77
- console.log(`[types] Finish outgoing stream ${s}`);
78
- },
79
- dropIncomingRequest(_req) {
80
- console.log('[types] Drop incoming request');
81
- },
82
- dropOutgoingRequest(_req) {
83
- console.log('[types] Drop outgoing request');
84
- },
85
- incomingRequestMethod(_req) {
86
- console.log('[types] Incoming request method');
87
- },
88
- incomingRequestPathWithQuery(_req) {
89
- console.log('[types] Incoming request path with query');
90
- },
91
- incomingRequestScheme(_req) {
92
- console.log('[types] Incoming request scheme');
93
- },
94
- incomingRequestAuthority(_req) {
95
- console.log('[types] Incoming request authority');
96
- },
97
- incomingRequestHeaders(_req) {
98
- console.log('[types] Incoming request headers');
99
- },
100
- incomingRequestConsume(_req) {
101
- console.log('[types] Incoming request consume');
102
- },
103
- newOutgoingRequest(_method, _pathWithQuery, _scheme, _authority, _headers) {
104
- console.log('[types] New outgoing request');
105
- },
106
- outgoingRequestWrite(_req) {
107
- console.log('[types] Outgoing request write');
108
- },
109
- dropResponseOutparam(_res) {
110
- console.log('[types] Drop response outparam');
111
- },
112
- setResponseOutparam(_response) {
113
- console.log('[types] Drop fields');
114
- },
115
- dropIncomingResponse(_res) {
116
- console.log('[types] Drop incoming response');
117
- },
118
- dropOutgoingResponse(_res) {
119
- console.log('[types] Drop outgoing response');
120
- },
121
- incomingResponseStatus(_res) {
122
- console.log('[types] Incoming response status');
123
- },
124
- incomingResponseHeaders(_res) {
125
- console.log('[types] Incoming response headers');
126
- },
127
- incomingResponseConsume(_res) {
128
- console.log('[types] Incoming response consume');
129
- },
130
- newOutgoingResponse(_statusCode, _headers) {
131
- console.log('[types] New outgoing response');
132
- },
133
- outgoingResponseWrite(_res) {
134
- console.log('[types] Outgoing response write');
135
- },
136
- dropFutureIncomingResponse(_f) {
137
- console.log('[types] Drop future incoming response');
138
- },
139
- futureIncomingResponseGet(_f) {
140
- console.log('[types] Future incoming response get');
141
- },
142
- listenToFutureIncomingResponse(_f) {
143
- console.log('[types] Listen to future incoming response');
144
- },
145
- Fields: class Fields {},
146
- FutureIncomingResponse: new class FutureIncomingResponse {},
147
- IncomingBody: new class IncomingBody {},
148
- IncomingRequest: new class IncomingRequest {},
149
- IncomingResponse: new class IncomingResponse {},
150
- OutgoingBody: new class OutgoingBody {},
151
- OutgoingRequest: new class OutgoingRequest {},
152
- OutgoingResponse: new class OutgoingResponse {},
153
- RequestOptions: new class RequestOptions {},
154
- ResponseOutparam: new class ResponseOutparam {},
680
+ Fields,
681
+ FutureIncomingResponse,
682
+ FutureTrailers,
683
+ IncomingBody,
684
+ IncomingRequest: class IncomingRequest {},
685
+ IncomingResponse,
686
+ OutgoingBody,
687
+ OutgoingRequest,
688
+ OutgoingResponse: class OutgoingResponse {},
689
+ ResponseOutparam: class ResponseOutparam {},
690
+ RequestOptions,
691
+ httpErrorCode,
155
692
  };
package/lib/browser/io.js CHANGED
@@ -77,7 +77,9 @@ class InputStream {
77
77
  return BigInt(bytes.byteLength);
78
78
  }
79
79
  subscribe() {
80
- console.log(`[streams] Subscribe to input stream ${this.id}`);
80
+ if (this.handler.subscribe) {
81
+ return this.handler.subscribe();
82
+ }
81
83
  return new Pollable();
82
84
  }
83
85
  [symbolDispose]() {
@@ -168,7 +170,9 @@ class OutputStream {
168
170
  console.log(`[streams] Forward ${this.id}`);
169
171
  }
170
172
  subscribe() {
171
- console.log(`[streams] Subscribe to output stream ${this.id}`);
173
+ if (this.handler.subscribe) {
174
+ return this.handler.subscribe();
175
+ }
172
176
  return new Pollable();
173
177
  }
174
178
  [symbolDispose]() {}
@@ -178,19 +182,75 @@ export const error = { Error: IoError };
178
182
 
179
183
  export const streams = { InputStream, OutputStream };
180
184
 
181
- class Pollable {}
185
+ class Pollable {
186
+ #ready = false;
187
+ #promise = null;
188
+
189
+ constructor(promise) {
190
+ if (!promise) {
191
+ this.#ready = true;
192
+ } else {
193
+ this.#promise = promise.then(
194
+ () => { this.#ready = true; },
195
+ () => { this.#ready = true; }
196
+ );
197
+ }
198
+ }
199
+
200
+ ready() {
201
+ return this.#ready;
202
+ }
203
+
204
+ block() {
205
+ if (this.#ready) {
206
+ return Promise.resolve();
207
+ }
208
+ return this.#promise;
209
+ }
182
210
 
183
- function pollList(_list) {
184
- // TODO
211
+ [symbolDispose]() {
212
+ this.#promise = null;
213
+ }
214
+ }
215
+
216
+ function pollList(list) {
217
+ if (list.length === 0) {
218
+ throw new Error('poll list must not be empty');
219
+ }
220
+ if (list.length > 0xFFFFFFFF) {
221
+ throw new Error('poll list length exceeds u32 index range');
222
+ }
223
+ const ready = [];
224
+ for (let i = 0; i < list.length; i++) {
225
+ if (list[i].ready()) {
226
+ ready.push(i);
227
+ }
228
+ }
229
+ if (ready.length > 0) {
230
+ return new Uint32Array(ready);
231
+ }
232
+ // None ready synchronously. Wait for the first to resolve via Promise.race,
233
+ // then sweep for any others that became ready concurrently.
234
+ return Promise.race(
235
+ list.map((p, i) => p.block().then(() => {
236
+ const result = [i];
237
+ for (let j = 0; j < list.length; j++) {
238
+ if (j !== i && list[j].ready()) {
239
+ result.push(j);
240
+ }
241
+ }
242
+ return new Uint32Array(result);
243
+ }))
244
+ );
185
245
  }
186
246
 
187
- function pollOne(_poll) {
188
- // TODO
247
+ function pollOne(poll) {
248
+ return poll.block();
189
249
  }
190
250
 
191
251
  export const poll = {
192
252
  Pollable,
193
253
  pollList,
194
254
  pollOne,
195
- poll: pollOne,
255
+ poll: pollList,
196
256
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bytecodealliance/preview2-shim",
3
- "version": "0.17.7",
3
+ "version": "0.17.8",
4
4
  "description": "WASI Preview2 shim for JS environments",
5
5
  "author": "Guy Bedford, Eduardo Rodrigues<16357187+eduardomourar@users.noreply.github.com>",
6
6
  "contributors": [