@polyengine/runtime 0.4.0 → 0.5.1
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/embedder/copy.js +1 -1
- package/esm/embedder/instantiate.js +43 -9
- package/esm/embedder/mod.js +21 -10
- package/esm/embedder/streams.js +96 -5
- package/esm/exec/boundary.js +210 -19
- package/esm/exec/executor.js +16 -1
- package/esm/exec/host_streams.js +376 -0
- package/esm/intrinsics/async_builtins.js +5 -2
- package/esm/jspi/suspending.js +7 -2
- package/esm/task/streams.js +127 -2
- package/package.json +2 -2
- package/types/embedder/copy.d.ts +1 -1
- package/types/embedder/mod.d.ts +7 -4
- package/types/embedder/streams.d.ts +53 -6
- package/types/exec/boundary.d.ts +13 -0
- package/types/exec/host_streams.d.ts +70 -0
- package/types/jspi/suspending.d.ts +1 -1
- package/types/task/streams.d.ts +83 -0
|
@@ -2,8 +2,8 @@ import type { ValType } from "../cabi/types.js";
|
|
|
2
2
|
import type { ComponentValue } from "../cabi/types.js";
|
|
3
3
|
import { type HostFuture, type HostStream } from "../exec/host_streams.js";
|
|
4
4
|
import { ErrorContext as InternalErrorContext } from "../task/mod.js";
|
|
5
|
-
|
|
6
|
-
export type Chunk
|
|
5
|
+
import { type Chunk, type DirectDestination, type DirectSource, type DirectVerdict, type ErrorContext as ProtocolErrorContext, type Future as ProtocolFuture, type Stream as ProtocolStream, type StreamWriter as ProtocolStreamWriter } from "@polyengine/protocol";
|
|
6
|
+
export type { Chunk } from "@polyengine/protocol";
|
|
7
7
|
/**
|
|
8
8
|
* Per-element adaptation, supplied by the value adapter.
|
|
9
9
|
* @internal — supplied by the value adapter, never by a host.
|
|
@@ -26,13 +26,14 @@ export interface ElemCodec<T> {
|
|
|
26
26
|
export { StreamProducerError } from "@polyengine/protocol";
|
|
27
27
|
/** True for `stream<u8>` / `future<u8>`, whose chunks are `Uint8Array`. */
|
|
28
28
|
export declare function isU8Element(element: ValType | null): boolean;
|
|
29
|
+
export type { DirectDestination, DirectSource, DirectVerdict } from "@polyengine/protocol";
|
|
29
30
|
/**
|
|
30
31
|
* A stream handle.
|
|
31
32
|
*
|
|
32
33
|
* `read` returning an empty chunk is end-of-stream, exactly as the contract
|
|
33
34
|
* spells it; `readable()` and the async iterator are built on it.
|
|
34
35
|
*/
|
|
35
|
-
export declare class Stream<T> {
|
|
36
|
+
export declare class Stream<T> implements ProtocolStream<T> {
|
|
36
37
|
#private;
|
|
37
38
|
private constructor();
|
|
38
39
|
/** Wrap a stream value that was lifted out of a guest. */
|
|
@@ -66,6 +67,29 @@ export declare class Stream<T> {
|
|
|
66
67
|
get codec(): ElemCodec<T> | null;
|
|
67
68
|
/** Low-level read: up to `max` elements; an empty chunk means end-of-stream. */
|
|
68
69
|
read(max: number): Promise<Chunk<T>>;
|
|
70
|
+
/**
|
|
71
|
+
* Consume the writer's bytes in place, without an intermediate chunk
|
|
72
|
+
* (`stream<u8>` only — contracts/embedder-api.md amendment A21,
|
|
73
|
+
* polyengine#128).
|
|
74
|
+
*
|
|
75
|
+
* At every rendezvous with a writer of nonzero capacity, `consume` runs
|
|
76
|
+
* exactly once, synchronously, with a `DirectSource` over the writer's
|
|
77
|
+
* unread bytes — guest linear memory when the peer is a guest, so the
|
|
78
|
+
* consumer's own `set()`/`subarray` copy IS the canonical-ABI copy.
|
|
79
|
+
* `"more"` keeps the session parked for the next rendezvous; `"done"` ends
|
|
80
|
+
* it. Resolves with the session's total byte count. Marking a prefix is
|
|
81
|
+
* normal: the writer re-offers the rest on its own schedule.
|
|
82
|
+
*
|
|
83
|
+
* `"done"` with zero bytes marked *retracts*: the session ends and the
|
|
84
|
+
* writer's operation stays parked, with no event delivered. `"more"` with
|
|
85
|
+
* zero marked, and a throwing callback, reject — and in both cases the
|
|
86
|
+
* writer's parked operation survives and the stream stays alive.
|
|
87
|
+
*
|
|
88
|
+
* Refusals mirror `read`: an unbound `Stream.create()` handle and a handle
|
|
89
|
+
* already passed to a guest (the A15 transfer guard) both throw, as does a
|
|
90
|
+
* non-`u8` element type.
|
|
91
|
+
*/
|
|
92
|
+
readDirect(consume: (src: DirectSource) => DirectVerdict): Promise<number>;
|
|
69
93
|
/**
|
|
70
94
|
* Cancel an in-flight `read` (R-fix review advisory 1).
|
|
71
95
|
*
|
|
@@ -103,7 +127,7 @@ export declare class Stream<T> {
|
|
|
103
127
|
[Symbol.asyncIterator](): AsyncIterator<Chunk<T>>;
|
|
104
128
|
}
|
|
105
129
|
/** Writer half of `Stream.create()`. */
|
|
106
|
-
export declare class StreamWriter<T> {
|
|
130
|
+
export declare class StreamWriter<T> implements ProtocolStreamWriter<T> {
|
|
107
131
|
#private;
|
|
108
132
|
constructor(stream: Stream<T>);
|
|
109
133
|
/**
|
|
@@ -117,6 +141,29 @@ export declare class StreamWriter<T> {
|
|
|
117
141
|
* window is misuse. Plain-array chunks are lowered (copied) up front.
|
|
118
142
|
*/
|
|
119
143
|
write(values: Chunk<T>): Promise<number>;
|
|
144
|
+
/**
|
|
145
|
+
* Fill the reader's landing zone in place, without an intermediate chunk
|
|
146
|
+
* (`stream<u8>` only — contracts/embedder-api.md amendment A21,
|
|
147
|
+
* polyengine#128).
|
|
148
|
+
*
|
|
149
|
+
* At every rendezvous with a reader of nonzero capacity, `produce` runs
|
|
150
|
+
* exactly once, synchronously, with a `DirectDestination` over the reader's
|
|
151
|
+
* unfilled landing zone — guest linear memory when the peer is a guest, so
|
|
152
|
+
* the producer's own `set()` IS the canonical-ABI copy and an external byte
|
|
153
|
+
* mover (a websocket frame, a SAB ring segment, a transferred
|
|
154
|
+
* `ArrayBuffer`) never pays a second copy inside the runtime. `"more"`
|
|
155
|
+
* keeps the session parked for the next rendezvous; `"done"` ends it.
|
|
156
|
+
* Resolves with the session's total byte count.
|
|
157
|
+
*
|
|
158
|
+
* `"done"` with zero bytes marked *retracts* (the session ends, the
|
|
159
|
+
* reader's operation stays parked, no event — the speculative-park
|
|
160
|
+
* correction); `"more"` with zero marked, and a throwing callback, reject.
|
|
161
|
+
*
|
|
162
|
+
* Parks until the element type is known, exactly as `write` does — a
|
|
163
|
+
* `Stream.create()` writer has no element type until the lowering site
|
|
164
|
+
* binds one — and then requires `u8`.
|
|
165
|
+
*/
|
|
166
|
+
writeDirect(produce: (dest: DirectDestination) => DirectVerdict): Promise<number>;
|
|
120
167
|
/** Offer values until all are taken or the reader goes away. */
|
|
121
168
|
writeAll(values: Chunk<T>): Promise<number>;
|
|
122
169
|
cancelWrite(): void;
|
|
@@ -131,7 +178,7 @@ export declare function publishHostStream<T>(s: Stream<T>, h: HostStream<T>): vo
|
|
|
131
178
|
* A future whose write end dropped without ever writing rejects with
|
|
132
179
|
* `DroppedError` — not `undefined`, which `future<void>` legitimately yields.
|
|
133
180
|
*/
|
|
134
|
-
export declare class Future<T> implements
|
|
181
|
+
export declare class Future<T> implements ProtocolFuture<T> {
|
|
135
182
|
#private;
|
|
136
183
|
private constructor();
|
|
137
184
|
static fromLifted<T>(value: ComponentValue, codec: ElemCodec<T>): Future<T>;
|
|
@@ -182,7 +229,7 @@ export declare class Future<T> implements PromiseLike<T> {
|
|
|
182
229
|
* The internal value is `task/streams.ts`'s `ErrorContext` (debug message
|
|
183
230
|
* only, per definitions.py).
|
|
184
231
|
*/
|
|
185
|
-
export declare class ErrorContext {
|
|
232
|
+
export declare class ErrorContext implements ProtocolErrorContext {
|
|
186
233
|
readonly message: string;
|
|
187
234
|
/** @internal — the internal value, preserved so it can be lowered back. */
|
|
188
235
|
readonly internal: InternalErrorContext;
|
package/types/exec/boundary.d.ts
CHANGED
|
@@ -336,6 +336,19 @@ export declare function createLoweredImport(input: {
|
|
|
336
336
|
mode: SuspensionMode;
|
|
337
337
|
/** Host fn carries the `suspending()` brand (embedder-api.md A1). */
|
|
338
338
|
suspendable: boolean;
|
|
339
|
+
/**
|
|
340
|
+
* Host fn carries the `deferCancel()` brand (embedder-api.md A23): the
|
|
341
|
+
* import must run to completion, so a cancellation is accepted and ignored
|
|
342
|
+
* instead of taking the default discard.
|
|
343
|
+
*/
|
|
344
|
+
deferCancel: boolean;
|
|
345
|
+
/**
|
|
346
|
+
* Host fn carries the `abortable()` brand (embedder-api.md A24): every call
|
|
347
|
+
* receives a fresh `AbortSignal` appended after the WIT-declared params, and
|
|
348
|
+
* the runtime aborts it when — and only when — the call is discarded by a
|
|
349
|
+
* guest cancellation.
|
|
350
|
+
*/
|
|
351
|
+
abortable: boolean;
|
|
339
352
|
}): CoreFn;
|
|
340
353
|
/**
|
|
341
354
|
* The callback-ABI dispatch loop of `canon_lift` (definitions.py lines
|
|
@@ -37,6 +37,49 @@ export declare class HostBuffer {
|
|
|
37
37
|
* writer used.
|
|
38
38
|
*/
|
|
39
39
|
taken(): PayloadChunk;
|
|
40
|
+
byteView(n: number): Uint8Array;
|
|
41
|
+
advanceBytes(k: number): void;
|
|
42
|
+
endWindow(): void;
|
|
43
|
+
}
|
|
44
|
+
/** The scoped landing zone handed to a `writeDirect` producer (A21, #128). */
|
|
45
|
+
export interface DirectDestination {
|
|
46
|
+
/**
|
|
47
|
+
* The reader's still-unfilled bytes. Re-derived on every call (a
|
|
48
|
+
* `memory.grow` between two rendezvous of one session never yields a stale
|
|
49
|
+
* view) and shrinking by whatever has been marked so far in THIS
|
|
50
|
+
* invocation. DEAD once the callback returns.
|
|
51
|
+
*/
|
|
52
|
+
remaining(): Uint8Array;
|
|
53
|
+
/**
|
|
54
|
+
* Acknowledge bytes written into the view. Cumulative within the
|
|
55
|
+
* invocation; acknowledged only if the callback then returns cleanly.
|
|
56
|
+
*/
|
|
57
|
+
markWritten(n: number): void;
|
|
58
|
+
}
|
|
59
|
+
/** The scoped view handed to a `readDirect` consumer (A21, #128). */
|
|
60
|
+
export interface DirectSource {
|
|
61
|
+
/**
|
|
62
|
+
* The writer's unread bytes; read-only by contract. Same scoping and
|
|
63
|
+
* re-derivation rules as `DirectDestination.remaining`.
|
|
64
|
+
*/
|
|
65
|
+
remaining(): Uint8Array;
|
|
66
|
+
/** Acknowledge bytes consumed from the view. See `markWritten`. */
|
|
67
|
+
markRead(n: number): void;
|
|
68
|
+
}
|
|
69
|
+
/** The callback's poll cadence, spelled event-style (A21). */
|
|
70
|
+
export type DirectVerdict = "more" | "done";
|
|
71
|
+
/**
|
|
72
|
+
* Out-parameter of the low-level direct forms: `true` iff the session ended
|
|
73
|
+
* because the callback itself returned `"done"`, rather than because the peer
|
|
74
|
+
* dropped / the operation was cancelled / the peer's instance trapped.
|
|
75
|
+
*
|
|
76
|
+
* The conventions layer needs the distinction for A7 precision — a session
|
|
77
|
+
* the producer already completed keeps its resolution even if the peer then
|
|
78
|
+
* trapped — and `Promise<number>` is the contract's return shape, so it rides
|
|
79
|
+
* here rather than in the resolved value.
|
|
80
|
+
*/
|
|
81
|
+
export interface DirectSessionInfo {
|
|
82
|
+
endedByVerdict: boolean;
|
|
40
83
|
}
|
|
41
84
|
/** Host end the embedder WRITES; the guest reads. */
|
|
42
85
|
export interface HostWritableEnd<T> {
|
|
@@ -65,6 +108,24 @@ export interface HostWritableEnd<T> {
|
|
|
65
108
|
* if the reader dropped.
|
|
66
109
|
*/
|
|
67
110
|
writeAll(values: T[]): Promise<number>;
|
|
111
|
+
/**
|
|
112
|
+
* Park a **direct session** on this end (`stream<u8>` only — embedder-api
|
|
113
|
+
* amendment A21, polyengine#128).
|
|
114
|
+
*
|
|
115
|
+
* At every rendezvous with a reader of nonzero capacity, `produce` runs
|
|
116
|
+
* exactly once, synchronously, inside the rendezvous, with a
|
|
117
|
+
* `DirectDestination` over the reader's unfilled landing zone — guest linear
|
|
118
|
+
* memory when the peer is a guest, so the producer's own `set()` is the
|
|
119
|
+
* canonical-ABI copy. `"more"` keeps the session parked for the next
|
|
120
|
+
* rendezvous; `"done"` ends it. Resolves with the session's total.
|
|
121
|
+
*
|
|
122
|
+
* Marks acknowledge on clean return only. `"done"` with zero marked is
|
|
123
|
+
* *retraction* (the session ends, the reader's operation stays parked, no
|
|
124
|
+
* event); `"more"` with zero marked, and a throwing callback, reject.
|
|
125
|
+
*
|
|
126
|
+
* Participates in the one-in-flight-per-end rule exactly as `write` does.
|
|
127
|
+
*/
|
|
128
|
+
writeDirect(produce: (dest: DirectDestination) => DirectVerdict, info?: DirectSessionInfo): Promise<number>;
|
|
68
129
|
/**
|
|
69
130
|
* Cancel an in-flight `write`/`writeAll` (definitions.py
|
|
70
131
|
* `SharedStreamImpl.cancel` -> `CopyResult.CANCELLED`). No-op when nothing
|
|
@@ -92,6 +153,15 @@ export interface HostReadableEnd<T> {
|
|
|
92
153
|
* array.
|
|
93
154
|
*/
|
|
94
155
|
read(max: number): Promise<T[]>;
|
|
156
|
+
/**
|
|
157
|
+
* Park a **direct session** on this end (`stream<u8>` only — embedder-api
|
|
158
|
+
* amendment A21, polyengine#128). The mirror of
|
|
159
|
+
* `HostWritableEnd.writeDirect`: `consume` receives a `DirectSource` over
|
|
160
|
+
* the writer's unread bytes (a view of guest memory, or of the offered
|
|
161
|
+
* host chunk itself) and may take a prefix — a partial take is normal, and
|
|
162
|
+
* the writer re-offers on its own schedule.
|
|
163
|
+
*/
|
|
164
|
+
readDirect(consume: (src: DirectSource) => DirectVerdict, info?: DirectSessionInfo): Promise<number>;
|
|
95
165
|
/** Cancel an in-flight `read`; see `HostWritableEnd.cancelWrite`. */
|
|
96
166
|
cancelRead(): void;
|
|
97
167
|
drop(): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { anySuspendingImport, isSuspending, suspending } from "@polyengine/protocol";
|
|
1
|
+
export { abortable, anySuspendingImport, deferCancel, isAbortable, isDeferCancel, isSuspending, suspending, } from "@polyengine/protocol";
|
package/types/task/streams.d.ts
CHANGED
|
@@ -51,7 +51,90 @@ export declare class GuestBuffer {
|
|
|
51
51
|
read(n: number): PayloadChunk;
|
|
52
52
|
/** definitions.py `WritableBufferGuestImpl.write`. */
|
|
53
53
|
write(vs: PayloadChunk): void;
|
|
54
|
+
/**
|
|
55
|
+
* A fresh view over the next `n` bytes of this buffer's remaining range.
|
|
56
|
+
*
|
|
57
|
+
* Fresh on every call, via `bytesOf` (cabi/memory.ts:195) over the
|
|
58
|
+
* `LiveMemory` getters — so a `memory.grow` between two rendezvous of one
|
|
59
|
+
* parked direct session never yields a view onto the detached buffer.
|
|
60
|
+
*/
|
|
61
|
+
byteView(n: number): Uint8Array;
|
|
62
|
+
/**
|
|
63
|
+
* Advance by `k` WITHOUT copying: the bytes already moved through the view
|
|
64
|
+
* `byteView` handed out. Called by the seam only after the direct callback
|
|
65
|
+
* returned cleanly, which is what makes marks acknowledge-on-clean-return.
|
|
66
|
+
*/
|
|
67
|
+
advanceBytes(k: number): void;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The buffer surface the rendezvous actually uses (definitions.py `Buffer`,
|
|
71
|
+
* line 918). `GuestBuffer` and the host layer's `HostBuffer` both satisfy it.
|
|
72
|
+
*/
|
|
73
|
+
export interface RendezvousBuffer {
|
|
74
|
+
remain(): number;
|
|
75
|
+
isZeroLength(): boolean;
|
|
76
|
+
read(n: number): PayloadChunk;
|
|
77
|
+
write(vs: PayloadChunk): void;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* A21: the peer half of a direct rendezvous — a buffer that can expose its
|
|
81
|
+
* remaining range as bytes and be advanced without a copy.
|
|
82
|
+
*
|
|
83
|
+
* Implemented by `GuestBuffer` (a view into guest linear memory: the
|
|
84
|
+
* embedder's own `set()` becomes the one ABI copy) and by `HostBuffer` (a
|
|
85
|
+
* view of the offered chunk when it is the source; a synthesized scratch that
|
|
86
|
+
* becomes the delivered chunk when it is the destination).
|
|
87
|
+
*/
|
|
88
|
+
export interface ByteWindow {
|
|
89
|
+
/**
|
|
90
|
+
* A view over the next `n` bytes. May be called several times within one
|
|
91
|
+
* direct invocation (`remaining()` re-derives on every call); an
|
|
92
|
+
* implementation that *synthesizes* the window must return the same
|
|
93
|
+
* storage for the whole invocation and release it in `endWindow`.
|
|
94
|
+
*/
|
|
95
|
+
byteView(n: number): Uint8Array;
|
|
96
|
+
/** Record `k` bytes as moved. Called only after a clean callback return. */
|
|
97
|
+
advanceBytes(k: number): void;
|
|
98
|
+
/** End of one direct invocation; drop any synthesized window. */
|
|
99
|
+
endWindow?(): void;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* A21: the parked direct session, as the rendezvous sees it. It presents the
|
|
103
|
+
* ordinary buffer surface (so `remain()`/`isZeroLength()` keep the reference
|
|
104
|
+
* control flow working) but its `read`/`write` are never called — the seam
|
|
105
|
+
* routes it through `runDirect` instead.
|
|
106
|
+
*/
|
|
107
|
+
export interface DirectBuffer extends RendezvousBuffer {
|
|
108
|
+
readonly direct: true;
|
|
109
|
+
/**
|
|
110
|
+
* Run this session's callback exactly once against the peer's window,
|
|
111
|
+
* with `n` bytes of capacity. Applies the acknowledged marks to `peer`
|
|
112
|
+
* itself, and settles the session on failure — the seam only routes the
|
|
113
|
+
* rendezvous state that follows.
|
|
114
|
+
*/
|
|
115
|
+
runDirect(peer: ByteWindow, n: number): DirectOutcome;
|
|
116
|
+
/**
|
|
117
|
+
* Reject this session out-of-band (the two-direct-sessions rendezvous,
|
|
118
|
+
* where neither side owns memory).
|
|
119
|
+
*/
|
|
120
|
+
failDirect(error: Error): void;
|
|
54
121
|
}
|
|
122
|
+
/**
|
|
123
|
+
* What the seam did, and hence how the rendezvous must continue.
|
|
124
|
+
*
|
|
125
|
+
* * `"chunk"` — no direct session was involved: the reference copy ran.
|
|
126
|
+
* * `"copied"` — the callback acknowledged ≥ 1 byte; continue exactly as
|
|
127
|
+
* after a reference copy (fire the pending side's `on_copy`).
|
|
128
|
+
* * `"retracted"` — `"done"` with zero marked. Continue as if the direct
|
|
129
|
+
* side's buffer had had `remain() == 0` all along, which is a state
|
|
130
|
+
* definitions.py already routes.
|
|
131
|
+
* * `"failed"` — misuse or a throwing callback; the session has already
|
|
132
|
+
* rejected. No copy, no event, the peer's parked operation survives.
|
|
133
|
+
* * `"both-direct"` — neither side owns memory; the ARRIVING side is
|
|
134
|
+
* rejected by the caller and the parked side is left undisturbed.
|
|
135
|
+
*/
|
|
136
|
+
export type DirectOutcome = "copied" | "retracted" | "failed";
|
|
137
|
+
export type RendezvousOutcome = DirectOutcome | "chunk" | "both-direct";
|
|
55
138
|
/** Common shape of the object a `stream`/`future` *value* refers to. */
|
|
56
139
|
export interface SharedBase {
|
|
57
140
|
readonly t: ValType | null;
|