@polyengine/wasi 0.1.0-pre.g633468a

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/esm/io.js ADDED
@@ -0,0 +1,643 @@
1
+ // `wasi:io@0.2` — error, poll, streams (contracts/embedder-api.md
2
+ // §"WASI examination").
3
+ //
4
+ // THE PARKING KERNEL. `pollable.block()`, `poll()` and `blocking-*` are
5
+ // sync WIT functions that must genuinely wait — the one p2 idiom that
6
+ // fights a JS host. This package used to ship always-ready stubs (the
7
+ // retired "three-tier strategy", grounded in C0 finding #6: no consumer
8
+ // leg ever called a pollable method) with real parking documented as
9
+ // "never (c) in this package". Both halves of that ruling expired:
10
+ //
11
+ // * the polymorph-iroh upstream-iroh consumer class (unmodified
12
+ // iroh/tokio) parks its reactor in `poll()` with timer + socket
13
+ // pollables — the always-ready stubs don't degrade for such a guest,
14
+ // they LIVELOCK it (block() no-ops, reads return empty, the frame
15
+ // never suspends, so the event loop never turns and no host pump can
16
+ // ever make progress);
17
+ // * the runtime's suspending-import machinery (embedder-api.md A1/A2)
18
+ // made real parking a per-declaration capability with graceful
19
+ // degradation, so the kernel is ALWAYS ON rather than an opt-in
20
+ // profile: on engines without JSPI, `chooseMode` falls back to plain
21
+ // and everything behaves like the old stubs until a guest genuinely
22
+ // parks — which then raises a clean `NeedsJspi` at the park site
23
+ // instead of livelocking. Embedders wanting guaranteed-plain
24
+ // instantiation pass `jspi: false`.
25
+ //
26
+ // Costs, deliberately confined: only the park-capable declarations are
27
+ // marked (`block`, `poll`) — hot-path `read`/`check-write` stay plain —
28
+ // and marking flips wasi-consuming components into jspi mode on JSPI
29
+ // engines (see the contract note on the narrowed zero-cost pin).
30
+ //
31
+ // INTEROP SEAM: `Pollable` is publicly constructible —
32
+ // `new Pollable(ready, wait)` — because external providers mint pollables
33
+ // this kernel must `poll()` uniformly. The known consumer's sockets glue
34
+ // (deliberately outside this package, per the delivery ruling) wires
35
+ // pollables to datagram queues exactly this way; the reference for the
36
+ // wake pattern is polymorph-iroh's shim (promise-swap edge triggering).
37
+ //
38
+ // Streams: `read`/`check-write` stay plain (sync, never park), but the
39
+ // `blocking-*` declarations are MARKED park-capable (amendment A14): the
40
+ // buffer-backed base impls below always take the sync fast path, while
41
+ // the genuinely-async impls — `FedInputStream`/`SinkOutputStream` below,
42
+ // serving cli-stdio's host stdin/stdout and filesystem-web's OPFS files,
43
+ // where "blocking" cannot be served from a buffer — return a Promise and
44
+ // park the frame. Marking follows the WIT declaration on the REGISTERED
45
+ // class's prototype (A2: instance-level overrides change behavior, not
46
+ // suspendability), which is what lets the duck-typed async streams park
47
+ // through the resource types registered here.
48
+ var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
49
+ var useValue = arguments.length > 2;
50
+ for (var i = 0; i < initializers.length; i++) {
51
+ value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
52
+ }
53
+ return useValue ? value : void 0;
54
+ };
55
+ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
56
+ function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
57
+ var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
58
+ var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
59
+ var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
60
+ var _, done = false;
61
+ for (var i = decorators.length - 1; i >= 0; i--) {
62
+ var context = {};
63
+ for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
64
+ for (var p in contextIn.access) context.access[p] = contextIn.access[p];
65
+ context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
66
+ var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
67
+ if (kind === "accessor") {
68
+ if (result === void 0) continue;
69
+ if (result === null || typeof result !== "object") throw new TypeError("Object expected");
70
+ if (_ = accept(result.get)) descriptor.get = _;
71
+ if (_ = accept(result.set)) descriptor.set = _;
72
+ if (_ = accept(result.init)) initializers.unshift(_);
73
+ }
74
+ else if (_ = accept(result)) {
75
+ if (kind === "field") initializers.unshift(_);
76
+ else descriptor[key] = _;
77
+ }
78
+ }
79
+ if (target) Object.defineProperty(target, contextIn.name, descriptor);
80
+ done = true;
81
+ };
82
+ import { defineBrand, POLLABLE } from "@polyengine/protocol";
83
+ import { suspending, ComponentException } from "@polyengine/runtime/embedder";
84
+ /** The engine setTimeout ceiling: delays above 2^31-1 ms are clamped to
85
+ * ~0 (node/Deno warn and fire at 1 ms). `Pollable.timer` sleeps in
86
+ * chunks of at most this and re-checks the clock at each chunk end. */
87
+ const TIMER_CHUNK_MAX_MS = 2 ** 31 - 1;
88
+ function closedError() {
89
+ return new ComponentException({ kind: "closed" });
90
+ }
91
+ /**
92
+ * `wasi:io/error.error` — the generic downcastable error resource
93
+ * (io.wit:23). This shim never produces one organically (streams fail with
94
+ * `closed` only, never `last-operation-failed`); the class exists so the
95
+ * resource *type* is a legal import target and so a future producer of one
96
+ * has somewhere to construct it.
97
+ */
98
+ export class IoError {
99
+ #message;
100
+ constructor(message = "I/O error") {
101
+ this.#message = message;
102
+ }
103
+ toDebugString() {
104
+ return this.#message;
105
+ }
106
+ }
107
+ /**
108
+ * A pollable over host-supplied readiness.
109
+ *
110
+ * WIT-facing surface: `ready()` and `block()` (the latter parks the
111
+ * calling wasm frame when unready — @suspending, embedder-api.md A2:
112
+ * the class prototype is the brand authority).
113
+ *
114
+ * Host-facing surface: the constructor and `waitPromise()`. `ready` must
115
+ * be cheap and side-effect-free; `wait` returns a promise that settles
116
+ * when readiness MAY have changed — block/poll re-check and re-wait in a
117
+ * loop, so spurious wakes are fine and `wait` is called repeatedly (return
118
+ * the CURRENT epoch's promise each call; the promise-swap pattern — settle
119
+ * and re-arm on every event — is the intended producer shape). The default
120
+ * (no arguments) is an always-ready pollable, the honest shape for
121
+ * type-only linkage and never-backpressured sinks.
122
+ */
123
+ let Pollable = (() => {
124
+ let _instanceExtraInitializers = [];
125
+ let _block_decorators;
126
+ return class Pollable {
127
+ static {
128
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
129
+ _block_decorators = [suspending];
130
+ __esDecorate(this, null, _block_decorators, { kind: "method", name: "block", static: false, private: false, access: { has: obj => "block" in obj, get: obj => obj.block }, metadata: _metadata }, null, _instanceExtraInitializers);
131
+ if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
132
+ }
133
+ #ready = __runInitializers(this, _instanceExtraInitializers);
134
+ #wait;
135
+ constructor(ready = () => true, wait = () => Promise.resolve()) {
136
+ this.#ready = ready;
137
+ this.#wait = wait;
138
+ }
139
+ /**
140
+ * A pollable that becomes ready at `deadline` (nanoseconds on the
141
+ * caller's clock). One in-flight sleep is shared by concurrent waiters
142
+ * and RE-ARMED after every settle with the delta recomputed:
143
+ * `ready()` consults the clock, so an early-firing sleep (timer slop,
144
+ * or the engine's setTimeout ceiling below) hands the wait loop a
145
+ * fresh sleep for the remainder instead of a permanently-resolved
146
+ * promise — the cached-forever arm was a hot microtask livelock for
147
+ * any deadline past the ceiling (block/poll re-check `ready()` and
148
+ * re-await; awaiting an already-settled promise never yields to the
149
+ * timer that would make it ready).
150
+ *
151
+ * Engines clamp setTimeout delays above 2^31-1 ms to ~0 (node/Deno
152
+ * warn and use 1 ms), so far deadlines sleep in ceiling-sized chunks;
153
+ * each chunk end re-checks the clock and re-arms.
154
+ */
155
+ static timer(deadlineNs, nowNs) {
156
+ let armed;
157
+ const wait = () => {
158
+ return armed ??= new Promise((resolve) => {
159
+ const deltaMs = Number(deadlineNs - nowNs()) / 1e6;
160
+ setTimeout(resolve, Math.min(Math.max(0, deltaMs), TIMER_CHUNK_MAX_MS));
161
+ }).then(() => {
162
+ armed = undefined;
163
+ });
164
+ };
165
+ return new Pollable(() => nowNs() >= deadlineNs, wait);
166
+ }
167
+ ready() {
168
+ return this.#ready();
169
+ }
170
+ /** Parks the calling wasm frame until ready (sync fast path when
171
+ * already ready — no suspension, per-declaration marking only adds the
172
+ * engine's continuation hop). */
173
+ block() {
174
+ if (this.#ready())
175
+ return;
176
+ return (async () => {
177
+ while (!this.#ready())
178
+ await this.#wait();
179
+ })();
180
+ }
181
+ /** Host-facing (not part of the WIT resource surface): the current
182
+ * epoch's wake promise, raced by `poll`. */
183
+ waitPromise() {
184
+ return this.#wait();
185
+ }
186
+ };
187
+ })();
188
+ export { Pollable };
189
+ // A9 brand (contracts/embedder-api.md §"Module identity"): pollables cross
190
+ // into host provider code, which may resolve a different @polyengine copy. The
191
+ // brand makes them recognizable there; same-copy `instanceof` is unchanged
192
+ // and stays the documented spelling (issue #83). `poll()` itself needs no
193
+ // predicate: it consumes pollables structurally (`ready`/`waitPromise`), so a
194
+ // foreign provider's pollable already works — the brand is for consumers that
195
+ // must CLASSIFY one.
196
+ defineBrand(Pollable.prototype, POLLABLE);
197
+ /**
198
+ * `wasi:io/poll.poll` — indices of the ready pollables, parking the
199
+ * calling frame until at least one is ready. Sync fast path: if anything
200
+ * is ready right now, the indices return without a suspension.
201
+ *
202
+ * The explicit annotation is JSR's no-slow-types rule (the `suspending`
203
+ * wrapper would otherwise leave this public symbol's type inferred).
204
+ */
205
+ export const poll = suspending((pollables) => {
206
+ // io.wit: "poll [...] traps if the list [...] is empty". An unbranded
207
+ // host throw is the embedder contract's spelling of a trap.
208
+ if (pollables.length === 0) {
209
+ throw new Error("wasi:io/poll.poll: empty pollable list");
210
+ }
211
+ const readyNow = () => {
212
+ const out = [];
213
+ for (let i = 0; i < pollables.length; i++) {
214
+ if (pollables[i].ready())
215
+ out.push(i);
216
+ }
217
+ return out;
218
+ };
219
+ const first = readyNow();
220
+ if (first.length > 0)
221
+ return first;
222
+ return (async () => {
223
+ for (;;) {
224
+ await Promise.race(pollables.map((p) => p.waitPromise()));
225
+ const ready = readyNow();
226
+ if (ready.length > 0)
227
+ return ready;
228
+ }
229
+ })();
230
+ });
231
+ /**
232
+ * Buffer-backed input stream: serves `read`/`blocking-read` synchronously
233
+ * from an in-memory buffer supplied at construction (default empty,
234
+ * matching stdin's default in this package). Blocking degenerates to the
235
+ * sync read because the buffer is always immediately available.
236
+ */
237
+ let InputStream = (() => {
238
+ let _instanceExtraInitializers = [];
239
+ let _blockingRead_decorators;
240
+ let _blockingSkip_decorators;
241
+ return class InputStream {
242
+ static {
243
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
244
+ __esDecorate(this, null, _blockingRead_decorators, { kind: "method", name: "blockingRead", static: false, private: false, access: { has: obj => "blockingRead" in obj, get: obj => obj.blockingRead }, metadata: _metadata }, null, _instanceExtraInitializers);
245
+ __esDecorate(this, null, _blockingSkip_decorators, { kind: "method", name: "blockingSkip", static: false, private: false, access: { has: obj => "blockingSkip" in obj, get: obj => obj.blockingSkip }, metadata: _metadata }, null, _instanceExtraInitializers);
246
+ if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
247
+ }
248
+ #buf = __runInitializers(this, _instanceExtraInitializers);
249
+ #pos = 0;
250
+ #closed = false;
251
+ constructor(buf = new Uint8Array(0)) {
252
+ this.#buf = buf;
253
+ }
254
+ read(len) {
255
+ if (this.#closed)
256
+ throw closedError();
257
+ const n = Math.max(0, Math.min(Number(len), this.#buf.length - this.#pos));
258
+ const out = this.#buf.slice(this.#pos, this.#pos + n);
259
+ this.#pos += n;
260
+ // Issue #178: a nonzero-len request against an already-drained buffer
261
+ // is EOF, and p2's `read` reports that as the `closed` stream-error,
262
+ // not an empty-forever list (the guest's read-until-closed loop would
263
+ // otherwise livelock). Matches SyncFileInputStream
264
+ // (fs_provider.ts:519-521: `if (n > 0 && bytes.length === 0) throw
265
+ // closed`) and FedInputStream (io.ts:418). The `len > 0` guard keeps a
266
+ // zero-length probe a no-op, same as both siblings. This also covers
267
+ // the zero-length-initial-buffer case: the very first nonzero read
268
+ // against an empty buffer yields `out.length === 0` and throws here.
269
+ if (Number(len) > 0 && out.length === 0) {
270
+ throw closedError();
271
+ }
272
+ return out;
273
+ }
274
+ /** Park-capable (A14): the buffer-backed base never parks. */
275
+ blockingRead(len) {
276
+ return this.read(len);
277
+ }
278
+ skip(len) {
279
+ return BigInt(this.read(len).length);
280
+ }
281
+ /** Park-capable (A14): the buffer-backed base never parks. */
282
+ blockingSkip(len) {
283
+ return this.skip(len);
284
+ }
285
+ subscribe() {
286
+ return new Pollable();
287
+ }
288
+ [(_blockingRead_decorators = [suspending], _blockingSkip_decorators = [suspending], Symbol.dispose)]() {
289
+ this.#closed = true;
290
+ }
291
+ };
292
+ })();
293
+ export { InputStream };
294
+ /**
295
+ * Buffer-backed output stream over a byte sink. `checkWrite` always
296
+ * reports a large permit (the sink never truly backs up), so the
297
+ * synchronous fast path is always taken and `blocking-*` methods
298
+ * degenerate to their non-blocking counterparts.
299
+ */
300
+ let OutputStream = (() => {
301
+ let _instanceExtraInitializers = [];
302
+ let _blockingWriteAndFlush_decorators;
303
+ let _blockingFlush_decorators;
304
+ let _blockingWriteZeroesAndFlush_decorators;
305
+ let _blockingSplice_decorators;
306
+ return class OutputStream {
307
+ static {
308
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
309
+ __esDecorate(this, null, _blockingWriteAndFlush_decorators, { kind: "method", name: "blockingWriteAndFlush", static: false, private: false, access: { has: obj => "blockingWriteAndFlush" in obj, get: obj => obj.blockingWriteAndFlush }, metadata: _metadata }, null, _instanceExtraInitializers);
310
+ __esDecorate(this, null, _blockingFlush_decorators, { kind: "method", name: "blockingFlush", static: false, private: false, access: { has: obj => "blockingFlush" in obj, get: obj => obj.blockingFlush }, metadata: _metadata }, null, _instanceExtraInitializers);
311
+ __esDecorate(this, null, _blockingWriteZeroesAndFlush_decorators, { kind: "method", name: "blockingWriteZeroesAndFlush", static: false, private: false, access: { has: obj => "blockingWriteZeroesAndFlush" in obj, get: obj => obj.blockingWriteZeroesAndFlush }, metadata: _metadata }, null, _instanceExtraInitializers);
312
+ __esDecorate(this, null, _blockingSplice_decorators, { kind: "method", name: "blockingSplice", static: false, private: false, access: { has: obj => "blockingSplice" in obj, get: obj => obj.blockingSplice }, metadata: _metadata }, null, _instanceExtraInitializers);
313
+ if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
314
+ }
315
+ #sink = __runInitializers(this, _instanceExtraInitializers);
316
+ #closed = false;
317
+ constructor(sink) {
318
+ this.#sink = sink;
319
+ }
320
+ checkWrite() {
321
+ if (this.#closed)
322
+ throw closedError();
323
+ return 65536n;
324
+ }
325
+ write(contents) {
326
+ if (this.#closed)
327
+ throw closedError();
328
+ this.#sink(contents);
329
+ }
330
+ /** Park-capable (A14): the never-backpressured base never parks. */
331
+ blockingWriteAndFlush(contents) {
332
+ this.write(contents);
333
+ }
334
+ flush() {
335
+ if (this.#closed)
336
+ throw closedError();
337
+ }
338
+ /** Park-capable (A14): the never-backpressured base never parks. */
339
+ blockingFlush() {
340
+ this.flush();
341
+ }
342
+ subscribe() {
343
+ return new Pollable();
344
+ }
345
+ writeZeroes(len) {
346
+ this.write(new Uint8Array(Number(len)));
347
+ }
348
+ /** Park-capable (A14): the never-backpressured base never parks. */
349
+ blockingWriteZeroesAndFlush(len) {
350
+ this.writeZeroes(len);
351
+ }
352
+ splice(src, len) {
353
+ const chunk = src.read(len);
354
+ this.write(chunk);
355
+ return BigInt(chunk.length);
356
+ }
357
+ /** Park-capable (A14): the never-backpressured base never parks. */
358
+ blockingSplice(src, len) {
359
+ return this.splice(src, len);
360
+ }
361
+ [(_blockingWriteAndFlush_decorators = [suspending], _blockingFlush_decorators = [suspending], _blockingWriteZeroesAndFlush_decorators = [suspending], _blockingSplice_decorators = [suspending], Symbol.dispose)]() {
362
+ this.#closed = true;
363
+ }
364
+ };
365
+ })();
366
+ export { OutputStream };
367
+ /** Default high-water mark for the async-backed streams below: how many
368
+ * buffered bytes pause a `FedInputStream`'s feed, and the byte budget a
369
+ * `SinkOutputStream`'s `check-write` reports. */
370
+ export const STREAM_HIGH_WATER = 65536;
371
+ /**
372
+ * The p2 `input-stream` surface over an asynchronously-fed buffer: the
373
+ * generic bridge from any `AsyncIterable<Uint8Array>` (host stdin, an
374
+ * OPFS file read) to p2 stream semantics. `read` on an empty open stream
375
+ * returns an empty list (p2's non-blocking contract), `blocking-read`
376
+ * parks until bytes or EOF (A14/A2 mark relay — duck-typed against the
377
+ * registered `InputStream`, the marks relay from that prototype), and
378
+ * EOF-with-drained-buffer is the `closed` stream-error. The feed pauses
379
+ * past the high-water mark (no unbounded buffering).
380
+ */
381
+ let FedInputStream = (() => {
382
+ let _instanceExtraInitializers = [];
383
+ let _blockingRead_decorators;
384
+ let _blockingSkip_decorators;
385
+ return class FedInputStream {
386
+ static {
387
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
388
+ __esDecorate(this, null, _blockingRead_decorators, { kind: "method", name: "blockingRead", static: false, private: false, access: { has: obj => "blockingRead" in obj, get: obj => obj.blockingRead }, metadata: _metadata }, null, _instanceExtraInitializers);
389
+ __esDecorate(this, null, _blockingSkip_decorators, { kind: "method", name: "blockingSkip", static: false, private: false, access: { has: obj => "blockingSkip" in obj, get: obj => obj.blockingSkip }, metadata: _metadata }, null, _instanceExtraInitializers);
390
+ if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
391
+ }
392
+ #buffer = (__runInitializers(this, _instanceExtraInitializers), []);
393
+ #buffered = 0;
394
+ #eof = false;
395
+ #failure;
396
+ #closed = false;
397
+ #highWater;
398
+ /** Wakes blocking readers and pollables (promise-swap producer shape). */
399
+ #wake = () => { };
400
+ #wakePromise;
401
+ /** Resumes a paused feed once the buffer drains. */
402
+ #resume = () => { };
403
+ constructor(source, highWater = STREAM_HIGH_WATER) {
404
+ this.#highWater = highWater;
405
+ this.#wakePromise = new Promise((r) => (this.#wake = r));
406
+ void this.#feed(source);
407
+ }
408
+ #signal() {
409
+ const wake = this.#wake;
410
+ this.#wakePromise = new Promise((r) => (this.#wake = r));
411
+ wake();
412
+ }
413
+ async #feed(source) {
414
+ try {
415
+ for await (const chunk of source) {
416
+ if (this.#closed)
417
+ return; // reader gone; stop pulling
418
+ if (chunk.length === 0)
419
+ continue;
420
+ this.#buffer.push(chunk);
421
+ this.#buffered += chunk.length;
422
+ this.#signal();
423
+ while (this.#buffered >= this.#highWater && !this.#closed) {
424
+ await new Promise((r) => (this.#resume = r));
425
+ }
426
+ }
427
+ this.#eof = true;
428
+ }
429
+ catch (e) {
430
+ this.#failure = e;
431
+ this.#eof = true;
432
+ }
433
+ this.#signal();
434
+ }
435
+ #take(len) {
436
+ const out = new Uint8Array(Math.min(len, this.#buffered));
437
+ let at = 0;
438
+ while (at < out.length) {
439
+ const head = this.#buffer[0];
440
+ const take = Math.min(head.length, out.length - at);
441
+ out.set(head.subarray(0, take), at);
442
+ at += take;
443
+ if (take === head.length)
444
+ this.#buffer.shift();
445
+ else
446
+ this.#buffer[0] = head.subarray(take);
447
+ }
448
+ this.#buffered -= out.length;
449
+ if (this.#buffered < this.#highWater)
450
+ this.#resume();
451
+ return out;
452
+ }
453
+ read(len) {
454
+ if (this.#closed)
455
+ throw closedError();
456
+ if (this.#buffered > 0)
457
+ return this.#take(Number(len)); // drain before failing
458
+ if (this.#failure !== undefined) {
459
+ // A SOURCE failure is an error, not a clean end: the
460
+ // `last-operation-failed` stream-error, carrying the io `error`
461
+ // resource (an IoError subclass from the feed — e.g. a socket
462
+ // provider's code-carrying error — is preserved for downcasts).
463
+ throw new ComponentException({
464
+ kind: "last-operation-failed",
465
+ value: this.#failure instanceof IoError ? this.#failure : new IoError(this.#failure instanceof Error ? this.#failure.message : String(this.#failure)),
466
+ });
467
+ }
468
+ if (this.#eof)
469
+ throw closedError(); // drained + ended = closed
470
+ return new Uint8Array(0); // open, nothing available: p2 non-blocking read
471
+ }
472
+ /** Parks (A14/A2 mark relay from the registered prototype). */
473
+ blockingRead(len) {
474
+ if (this.#buffered > 0 || this.#eof || this.#closed)
475
+ return this.read(len);
476
+ return (async () => {
477
+ while (this.#buffered === 0 && !this.#eof && !this.#closed) {
478
+ await this.#wakePromise;
479
+ }
480
+ return this.read(len);
481
+ })();
482
+ }
483
+ skip(len) {
484
+ return BigInt(this.read(len).length);
485
+ }
486
+ blockingSkip(len) {
487
+ const r = this.blockingRead(len);
488
+ if (r instanceof Uint8Array)
489
+ return BigInt(r.length);
490
+ return r.then((bytes) => BigInt(bytes.length));
491
+ }
492
+ subscribe() {
493
+ return new Pollable(() => this.#buffered > 0 || this.#eof || this.#closed, () => this.#wakePromise);
494
+ }
495
+ [(_blockingRead_decorators = [suspending], _blockingSkip_decorators = [suspending], Symbol.dispose)]() {
496
+ this.#closed = true;
497
+ this.#resume(); // let a parked feed observe the close
498
+ this.#signal();
499
+ }
500
+ };
501
+ })();
502
+ export { FedInputStream };
503
+ /**
504
+ * The p2 `output-stream` surface over an async sink, with a real byte
505
+ * budget: `check-write` reports the remaining permit (writing past it is
506
+ * the guest's contract violation and traps via unbranded throw),
507
+ * `blocking-flush`/`blocking-write-and-flush` park until the sink
508
+ * drained everything (A14/A2 mark relay), `subscribe` wakes when budget
509
+ * frees. A sink failure surfaces as the `last-operation-failed`
510
+ * stream-error carrying an `IoError`.
511
+ */
512
+ let SinkOutputStream = (() => {
513
+ let _instanceExtraInitializers = [];
514
+ let _blockingFlush_decorators;
515
+ let _blockingWriteAndFlush_decorators;
516
+ let _blockingWriteZeroesAndFlush_decorators;
517
+ let _blockingSplice_decorators;
518
+ return class SinkOutputStream {
519
+ static {
520
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
521
+ __esDecorate(this, null, _blockingFlush_decorators, { kind: "method", name: "blockingFlush", static: false, private: false, access: { has: obj => "blockingFlush" in obj, get: obj => obj.blockingFlush }, metadata: _metadata }, null, _instanceExtraInitializers);
522
+ __esDecorate(this, null, _blockingWriteAndFlush_decorators, { kind: "method", name: "blockingWriteAndFlush", static: false, private: false, access: { has: obj => "blockingWriteAndFlush" in obj, get: obj => obj.blockingWriteAndFlush }, metadata: _metadata }, null, _instanceExtraInitializers);
523
+ __esDecorate(this, null, _blockingWriteZeroesAndFlush_decorators, { kind: "method", name: "blockingWriteZeroesAndFlush", static: false, private: false, access: { has: obj => "blockingWriteZeroesAndFlush" in obj, get: obj => obj.blockingWriteZeroesAndFlush }, metadata: _metadata }, null, _instanceExtraInitializers);
524
+ __esDecorate(this, null, _blockingSplice_decorators, { kind: "method", name: "blockingSplice", static: false, private: false, access: { has: obj => "blockingSplice" in obj, get: obj => obj.blockingSplice }, metadata: _metadata }, null, _instanceExtraInitializers);
525
+ if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
526
+ }
527
+ #sink = __runInitializers(this, _instanceExtraInitializers);
528
+ #highWater;
529
+ #queued = 0;
530
+ #closed = false;
531
+ #failure;
532
+ /** The pump: a serialized chain of sink calls. */
533
+ #tail = Promise.resolve();
534
+ #wake = () => { };
535
+ #wakePromise;
536
+ constructor(sink, highWater = STREAM_HIGH_WATER) {
537
+ this.#sink = sink;
538
+ this.#highWater = highWater;
539
+ this.#wakePromise = new Promise((r) => (this.#wake = r));
540
+ }
541
+ #signal() {
542
+ const wake = this.#wake;
543
+ this.#wakePromise = new Promise((r) => (this.#wake = r));
544
+ wake();
545
+ }
546
+ #checkOpen() {
547
+ if (this.#closed)
548
+ throw closedError();
549
+ if (this.#failure !== undefined) {
550
+ // stream-error.last-operation-failed carries the io `error` RESOURCE.
551
+ // A sink that already threw an `IoError` (subclass) keeps it — that
552
+ // is how filesystem sinks smuggle an error-code to
553
+ // `filesystem-error-code`'s downcast.
554
+ throw new ComponentException({
555
+ kind: "last-operation-failed",
556
+ value: this.#failure instanceof IoError ? this.#failure : new IoError(this.#failure instanceof Error ? this.#failure.message : String(this.#failure)),
557
+ });
558
+ }
559
+ }
560
+ checkWrite() {
561
+ this.#checkOpen();
562
+ return BigInt(Math.max(0, this.#highWater - this.#queued));
563
+ }
564
+ write(contents) {
565
+ this.#checkOpen();
566
+ if (contents.length > this.#highWater - this.#queued) {
567
+ // Writing past the permit is the guest's contract violation: a
568
+ // trap (unbranded throw), not a stream-error.
569
+ throw new Error("wasi:io/streams.write: contents exceed the check-write permit");
570
+ }
571
+ this.#queued += contents.length;
572
+ this.#tail = this.#tail.then(async () => {
573
+ try {
574
+ if (this.#failure === undefined)
575
+ await this.#sink(contents);
576
+ }
577
+ catch (e) {
578
+ this.#failure = e;
579
+ }
580
+ finally {
581
+ this.#queued -= contents.length;
582
+ this.#signal();
583
+ }
584
+ });
585
+ }
586
+ flush() {
587
+ this.#checkOpen();
588
+ }
589
+ /** Parks until the sink drained everything (A14/A2 mark relay). */
590
+ blockingFlush() {
591
+ this.#checkOpen();
592
+ if (this.#queued === 0)
593
+ return;
594
+ return (async () => {
595
+ while (this.#queued > 0 && this.#failure === undefined) {
596
+ await this.#wakePromise;
597
+ }
598
+ this.#checkOpen();
599
+ })();
600
+ }
601
+ /** Parks until this write (and everything before it) drained. */
602
+ blockingWriteAndFlush(contents) {
603
+ this.write(contents);
604
+ return this.blockingFlush();
605
+ }
606
+ subscribe() {
607
+ return new Pollable(() => this.#closed || this.#failure !== undefined || this.#queued < this.#highWater, () => this.#wakePromise);
608
+ }
609
+ writeZeroes(len) {
610
+ this.write(new Uint8Array(Number(len)));
611
+ }
612
+ blockingWriteZeroesAndFlush(len) {
613
+ return this.blockingWriteAndFlush(new Uint8Array(Number(len)));
614
+ }
615
+ splice(src, len) {
616
+ const chunk = src.read(len);
617
+ this.write(chunk);
618
+ return BigInt(chunk.length);
619
+ }
620
+ blockingSplice(src, len) {
621
+ const n = this.splice(src, len);
622
+ const flushed = this.blockingFlush();
623
+ if (flushed === undefined)
624
+ return n;
625
+ return flushed.then(() => n);
626
+ }
627
+ [(_blockingFlush_decorators = [suspending], _blockingWriteAndFlush_decorators = [suspending], _blockingWriteZeroesAndFlush_decorators = [suspending], _blockingSplice_decorators = [suspending], Symbol.dispose)]() {
628
+ this.#closed = true;
629
+ this.#signal();
630
+ }
631
+ };
632
+ })();
633
+ export { SinkOutputStream };
634
+ /** `wasi:io@0.2` provider fragment (track key). */
635
+ export function io() {
636
+ return {
637
+ imports: {
638
+ "wasi:io/error@0.2": { Error: IoError },
639
+ "wasi:io/poll@0.2": { Pollable, poll },
640
+ "wasi:io/streams@0.2": { InputStream, OutputStream },
641
+ },
642
+ };
643
+ }