@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/mod.js ADDED
@@ -0,0 +1,80 @@
1
+ // `@polyengine/wasi` — the WASI providers for polyengine hosts, and the
2
+ // executable check that the embedder conventions
3
+ // (`@polyengine/runtime/embedder`) serve WASI (contracts/embedder-api.md C2
4
+ // checklist item 7; docs/architecture.md §2 keeps implementations out of
5
+ // the RUNTIME — this package is where they live). Scope: p2
6
+ // baseline + p3 clocks + à la carte sockets on BOTH tracks (the
7
+ // poll-shaped `@0.2` surface std::net links, and `@0.3` UDP + TCP
8
+ // client/listener; one node-builtins backend serving Deno and Node;
9
+ // `@polyengine/wasi/sockets`, issue #4, server-JS hosts only). Sockets is
10
+ // deliberately not merged here: this root module stays host-agnostic
11
+ // web-platform code, and `wasi()` merges only AMBIENT, side-effect-
12
+ // benign capabilities (time, entropy, stdio capture, an empty
13
+ // filesystem). Anything granting network egress or host storage is
14
+ // opt-in regardless of how portable it is — sockets, the fetch-backed
15
+ // http fragment, the host-stdio cli impl (`./cli-stdio` — real
16
+ // stdin/stdout/terminal access; the unqualified `./cli` stays the
17
+ // capture impl), and the real filesystem impls (`./filesystem-node` =
18
+ // node:fs, `./filesystem-web` = OPFS; the unqualified `./filesystem`
19
+ // stays the empty-preopens stub).
20
+ //
21
+ // COMPOSITION — three forms, coarsest to finest (virtualization scenarios
22
+ // pinned by tests/version_resolution_test.ts):
23
+ //
24
+ // 1. Batteries: `instantiate(a, { ...wasi() })`.
25
+ // 2. À la carte fragments: every IMPL is its own subpath export
26
+ // (`@polyengine/wasi/{cli,cli-stdio,clocks,filesystem,filesystem-node,`
27
+ // `filesystem-web,http,io,random,sockets}`) and
28
+ // a plain `{ imports }` record — hand-merge exactly the set you mean:
29
+ // `{ ...io().imports, ...clocks().imports }`. Fragment dependencies:
30
+ // io.ts is the package's shared vocabulary (the parking kernel and
31
+ // the stream classes) — cli, clocks, and the real filesystem and
32
+ // sockets impls all ride it; random and http stand alone. Impl
33
+ // machinery that is not itself a fragment lives under src/internal/
34
+ // (never exported). Naming convention as impls multiply: one
35
+ // subpath per impl; the unqualified name is the batteries impl
36
+ // (`./filesystem` = empty preopens), alternatives carry their backend
37
+ // (`./filesystem-node` = node:fs with explicit preopens,
38
+ // `./filesystem-web` = OPFS).
39
+ // 3. Per-interface override: the merged record is a plain object keyed by
40
+ // TRACK keys (`wasi:random/random@0.2`), so later spreads replace
41
+ // single interfaces wholesale:
42
+ // `{ ...wasi(), "wasi:random/random@0.2": myStub }`.
43
+ // Replace the track key, don't add an exact-versioned sibling — the
44
+ // resolver refuses track+exact coexistence on one track as ambiguous
45
+ // (contracts/embedder-api.md §"Version canonicalization"). Per-guest
46
+ // virtualization needs no version tricks anyway: compose a different
47
+ // record per `instantiate` call. Some fragments also take the finer
48
+ // knob directly — `random({ source })` swaps the CSPRNG while keeping
49
+ // the WIT shapes and the no-short-reads rule.
50
+ //
51
+ // `wasi(options)` returns one flat imports-record fragment, keyed by
52
+ // compatibility-**track** keys per contracts/embedder-api.md §"Version
53
+ // canonicalization" (`@0.2`, `@0.3`) — this package is the flagship
54
+ // track-key-registration consumer: one `@0.2` provider serves every p2
55
+ // leaf regardless of whether the guest's binary says `0.2.6`, `0.2.9` or
56
+ // `0.2.12` (C0 finding D-2), and one `@0.3` union provider serves both
57
+ // divergent `monotonic-clock@0.3.0` drafts the corpus actually links
58
+ // (C0 finding D-1).
59
+ import { cli } from "./cli.js";
60
+ import { clocks } from "./clocks.js";
61
+ import { filesystem } from "./filesystem.js";
62
+ import { io } from "./io.js";
63
+ import { random } from "./random.js";
64
+ export { ExitError } from "./internal/cli_shared.js";
65
+ /**
66
+ * Build the merged `wasi:*` imports fragment for `instantiate`.
67
+ *
68
+ * Usage: `instantiate(artifacts, { ...wasi(), ...moreImports })`.
69
+ */
70
+ export function wasi(options = {}) {
71
+ const c = cli(options.cli);
72
+ const merged = {
73
+ ...c.imports,
74
+ ...io().imports,
75
+ ...clocks(options.clocks).imports,
76
+ ...random(options.random).imports,
77
+ ...filesystem().imports,
78
+ };
79
+ return Object.assign(merged, { captured: c.captured });
80
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "module"
3
+ }
package/esm/random.js ADDED
@@ -0,0 +1,96 @@
1
+ // `wasi:random@0.2` + `wasi:random@0.3` — random, insecure, insecure-seed
2
+ // (contracts/embedder-api.md §"WASI examination"; 0.2 leaves confirmed
3
+ // against `engine-go/main.wasm`'s import surface: `get-random-bytes`; 0.3
4
+ // shapes from the WASI 0.3.1 release — same three interfaces, same
5
+ // function names, `len` renamed `max-len` with short reads permitted; see
6
+ // the divergence note on `GET_RANDOM_VALUES_MAX`). One impl serves both
7
+ // tracks: chunk-to-full is conforming on each.
8
+ /**
9
+ * `crypto.getRandomValues` rejects requests over 65536 bytes
10
+ * (QuotaExceededError), while the 0.2 WIT this fragment serves requires
11
+ * exactly `len` bytes back ("Return `len` cryptographically-secure random
12
+ * or pseudo-random bytes", random.wit @0.2.x) — no shorter-return
13
+ * latitude, and callers (Rust `getrandom`, the Go runtime) fill fixed-size
14
+ * buffers trusting the length. So: chunk the fill, never clamp it.
15
+ *
16
+ * TRACK DIVERGENCE, for a future @0.3 fragment: `wasi:random@0.3.0`
17
+ * renames the parameter to `max-len` and PERMITS short reads
18
+ * ("Implementations MAY return fewer bytes than requested"; callers must
19
+ * loop; ≥1 byte required for max-len > 0). Authority moved with the WASI
20
+ * consolidation: `WebAssembly/WASI proposals/random/wit/random.wit` — the
21
+ * archived wasi-random repo's 0.3 rc still shows the old exact-len text.
22
+ * Chunk-to-full remains conforming there too ("up to max-len" includes
23
+ * exactly max-len) and makes conforming callers' mandatory loops terminate
24
+ * in one pass, so this helper serves both tracks unchanged.
25
+ *
26
+ * Either way the fill stays synchronous, satisfying both tracks' "must not
27
+ * block ... including on requests for [large] numbers of bytes".
28
+ */
29
+ const GET_RANDOM_VALUES_MAX = 65536;
30
+ function cryptoBytes(len) {
31
+ const out = new Uint8Array(Number(len));
32
+ for (let i = 0; i < out.length; i += GET_RANDOM_VALUES_MAX) {
33
+ crypto.getRandomValues(out.subarray(i, Math.min(i + GET_RANDOM_VALUES_MAX, out.length)));
34
+ }
35
+ return out;
36
+ }
37
+ /** The fill, honoring a virtualized `source` and its exact-length contract. */
38
+ function makeRandomBytes(source) {
39
+ if (source === undefined)
40
+ return cryptoBytes;
41
+ return (len) => {
42
+ const out = source(Number(len));
43
+ if (out.length !== Number(len)) {
44
+ throw new TypeError(`random source returned ${out.length} bytes, need exactly ${len} ` +
45
+ `(the @0.2 WIT permits no short reads)`);
46
+ }
47
+ return out;
48
+ };
49
+ }
50
+ function makeRandomU64(bytes) {
51
+ return () => {
52
+ const out = bytes(8n);
53
+ return new DataView(out.buffer, out.byteOffset, 8).getBigUint64(0, true);
54
+ };
55
+ }
56
+ /**
57
+ * A fixed default: `wasi:random/insecure-seed` is explicitly documented (WIT
58
+ * doc comment, io.wit deps) as allowed to be entirely deterministic — it
59
+ * exists to seed hash-map DoS resistance, not for cryptographic use. This
60
+ * shim defaults to a fixed, obviously-synthetic pair (documented here as
61
+ * exactly that) so runs are reproducible; pass `insecureSeed` to override.
62
+ */
63
+ const DEFAULT_INSECURE_SEED = [0n, 1n];
64
+ /** `wasi:random@0.2` + `@0.3` provider fragment (two track keys). */
65
+ export function random(options = {}) {
66
+ const seed = options.insecureSeed ?? DEFAULT_INSECURE_SEED;
67
+ const randomBytes = makeRandomBytes(options.source);
68
+ const randomU64 = makeRandomU64(randomBytes);
69
+ const randomIface = {
70
+ getRandomBytes: randomBytes,
71
+ getRandomU64: randomU64,
72
+ };
73
+ // "insecure" only means "not required to be a CSPRNG" — it is still
74
+ // wired to the real CSPRNG here for simplicity; only `insecure-seed`
75
+ // is deliberately, documentedly predictable.
76
+ const insecureIface = {
77
+ getInsecureRandomBytes: randomBytes,
78
+ getInsecureRandomU64: randomU64,
79
+ };
80
+ const insecureSeedIface = {
81
+ insecureSeed: () => seed,
82
+ };
83
+ return {
84
+ imports: {
85
+ "wasi:random/random@0.2": randomIface,
86
+ "wasi:random/insecure@0.2": insecureIface,
87
+ "wasi:random/insecure-seed@0.2": insecureSeedIface,
88
+ // The @0.3 track: identical function names; `max-len` PERMITS short
89
+ // reads but chunk-to-full returns exactly max-len, which "up to
90
+ // max-len" includes (module header) — one impl, both tracks.
91
+ "wasi:random/random@0.3": randomIface,
92
+ "wasi:random/insecure@0.3": insecureIface,
93
+ "wasi:random/insecure-seed@0.3": insecureSeedIface,
94
+ },
95
+ };
96
+ }
package/esm/sockets.js ADDED
@@ -0,0 +1,172 @@
1
+ // `wasi:sockets` — grants this process's NETWORK REACH to the guest,
2
+ // unscoped: there is no allowlist, address check, or TCP/UDP toggle, so
3
+ // a guest can reach anything this process can, loopback and instance
4
+ // metadata endpoints included (docs/security.md). Never rides the
5
+ // default `wasi()` merge.
6
+ //
7
+ // BOTH tracks: `types@0.3` + `ip-name-lookup@0.3` (UDP
8
+ // and TCP, client + listener; internal/sockets_03.ts) and the poll-shaped
9
+ // `@0.2` surface (internal/sockets_02.ts). THIS module is the public
10
+ // face: the `sockets()` fragment factory and the vocabulary re-exports.
11
+ // One backend serves both tracks: the
12
+ // node builtins (`node:dgram` / `node:net`), which
13
+ // real Node provides natively, Deno serves as STABLE node-compat (no
14
+ // `--unstable-net` needed — that flag gates only the native API's shape,
15
+ // not the capability), and Bun reaches through its compat (findings-only;
16
+ // JSC lacks multi-memory, so polyengine guests cannot run there regardless).
17
+ // Backend rationale, adapters, and measured costs: internal/sockets_platform.ts.
18
+ //
19
+ // À la carte (issue #4): this module is a separate export
20
+ // (`@polyengine/wasi/sockets`), never merged into `wasi()` — the
21
+ // baseline package stays host-agnostic web-platform code, while this
22
+ // fragment is server-JS-native by nature (browsers have no sockets;
23
+ // wasmtime owns the native story). Consumers that want it spread it in:
24
+ //
25
+ // instantiate(artifacts, { ...wasi(), ...sockets().imports })
26
+ //
27
+ // The UDP provider is adopted from polymorph-components/polymorph-iroh#69
28
+ // (that host's exam drives it over loopback QUIC); divergences from the
29
+ // adopted code are the track key (`@0.3`, per this package's conventions —
30
+ // one provider serves every 0.3.x), the fragment-scoped `onCall` hook
31
+ // replacing a module-global call log (a published provider must not grow a
32
+ // string per datagram by default), and `globalThis`-based feature detection
33
+ // (the module evaluates and answers honestly on any host). The TCP client
34
+ // surface is what the wosh listener bridges through (its
35
+ // `listener-core/src/tcp.rs` — issue #4's prospective consumer) and the
36
+ // smoke-c0 leg-4 composed-websocket shopping list names.
37
+ //
38
+ // The implemented resource shapes (0.3.x WIT — the full 0.3.1 release
39
+ // surface, minus the recorded not-supported options below):
40
+ //
41
+ // resource udp-socket {
42
+ // create/bind/connect/disconnect/send/receive,
43
+ // get-local-address/get-remote-address/get-address-family,
44
+ // get+set unicast-hop-limit, get+set receive/send-buffer-size
45
+ // }
46
+ // resource tcp-socket {
47
+ // create/bind/connect/listen/send/receive,
48
+ // get-local-address/get-remote-address/get-address-family/get-is-listening,
49
+ // set-listen-backlog-size, get+set keep-alive-enabled,
50
+ // get+set keep-alive-idle-time
51
+ // }
52
+ // ip-name-lookup { resolve-addresses } (system resolver via node:dns)
53
+ //
54
+ // OPTIONS HONESTY (the node option surface is narrow; nothing is
55
+ // emulated silently):
56
+ //
57
+ // * udp connect/disconnect are OS-level (node dgram connect: kernel
58
+ // filtering and default destination), not adapter filtering.
59
+ // * udp unicast-hop-limit: setter is real (dgram setTTL); the getter
60
+ // reports the cached value (default 64, documented) — node has no
61
+ // getter. Buffer sizes are real both ways once bound (SO_RCVBUF/
62
+ // SO_SNDBUF); before bind, gets report the cached request or fail
63
+ // `not-supported`.
64
+ // * tcp keep-alive: enabled + idle-time are real (node setKeepAlive);
65
+ // gets report cached values (idle default 7200 s, documented).
66
+ // keep-alive-interval/count, tcp hop-limit, and tcp buffer sizes
67
+ // have NO node:net API and fail `not-supported`.
68
+ // * set-listen-backlog-size: applied as listen()'s backlog hint;
69
+ // changing it while listening is `not-supported` (node cannot
70
+ // re-listen; wasmtime re-listens).
71
+ // * accepted sockets do NOT inherit the listener's options (wasmtime
72
+ // inherits; recorded divergence).
73
+ //
74
+ // Anything else a future guest links fails loudly with a trap naming the
75
+ // missing method rather than riding an untested emulation.
76
+ //
77
+ // The behavioral yardstick is wasmtime-wasi's p3 provider (the consumers'
78
+ // wasmtime hosts serve the same guests through it).
79
+ //
80
+ // UDP: the same 64 KiB datagram ceiling, the same state machine (`bind`
81
+ // once from unbound; `receive` and `get-local-address` demand a bound
82
+ // socket; `send` to a remote implicitly binds an unbound socket to a
83
+ // wildcard address; an omitted `send` remote requires connected mode, and
84
+ // an explicit remote on a connected socket is `invalid-argument`), and the same address-family validation (an
85
+ // IPv4-mapped or deprecated IPv4-compatible IPv6 address never crosses a
86
+ // family boundary). Recorded divergences, rooted in the platform exposing
87
+ // no socket options:
88
+ //
89
+ // * scope-id: a non-zero IPv6 `scope-id` fails `not-supported` (node
90
+ // hostnames cannot carry a zone; wasmtime binds it).
91
+ // * v6-only: wasmtime sets IPV6_V6ONLY on IPv6 sockets; node leaves the
92
+ // OS default, so an `::` wildcard bind on Linux is dual-stack and may
93
+ // receive IPv4 traffic, surfaced as IPv4-mapped sender addresses —
94
+ // which is also why the address codec parses the `::ffff:a.b.c.d`
95
+ // spelling.
96
+ // * unread datagrams queue in the adapter (node's receive path is
97
+ // push-shaped) and tail-drop past a bound — the kernel-buffer
98
+ // analogue; see sockets_platform.ts `MAX_QUEUED_DATAGRAMS`.
99
+ //
100
+ // TCP (the TcpSocketOperationalSemantics-0.3.0 state machine): `connect`
101
+ // once from `unbound` (a failed attempt closes the socket); `listen` once
102
+ // from `unbound` (implicit wildcard-ephemeral bind) or `bound`;
103
+ // `send`/`receive` once each, only when `connected`, and their failures
104
+ // NEVER throw — `send`'s error channel is its returned future (amendment
105
+ // A12: the async method's promise IS the future source) and `receive`'s
106
+ // is the future half of its tuple, resolved as result values. `listen`
107
+ // returns the perpetual accept stream, whose elements are connected
108
+ // `tcp-socket` resources (amendment A13: un-taken elements are destroyed
109
+ // at teardown, closing their connections); per-connection accept failures
110
+ // are skipped, listener-fatal ones end the stream. Stream teardown
111
+ // follows the WIT's shared-ownership note: the OS socket closes only when
112
+ // the resource handle AND every derived stream (pumps, accept stream) are
113
+ // done, so they all remain functional after the guest drops the
114
+ // `tcp-socket` handle. The receive stream ends (cleanly, no fake data) on
115
+ // BOTH graceful FIN and abnormal close; the two are distinguished by the
116
+ // future (`ok` vs `err`), exactly as the WIT documents. Guest-side
117
+ // failures while consuming `send`'s stream (a peer trap) are NOT socket
118
+ // errors: they propagate as producer failures on the host-failure channel.
119
+ //
120
+ // `listen` is SUSPENDING (embedder-api A1/A2 — the wasi:io `block`
121
+ // kernel): node defers the OS bind one event-loop turn, so `listen` parks
122
+ // the calling guest frame for that tick and returns fully settled — real
123
+ // ephemeral addresses from `get-local-address`, real error codes
124
+ // (`address-in-use`) from a failed bind. Guests that link `listen`
125
+ // auto-select jspi mode on JSPI engines (V8: Deno, Node, Chromium);
126
+ // client-shaped guests are untouched.
127
+ //
128
+ // Recorded TCP divergences:
129
+ //
130
+ // * `bind` records the address; the OS bind is DEFERRED to `listen` or
131
+ // `connect` (node cannot bind an unconnected socket), so bind errors
132
+ // (`address-in-use`, `address-not-bindable`) surface at those calls —
133
+ // with their real codes — not at `bind`.
134
+ //
135
+ // When the node builtins are absent (`process.getBuiltinModule` missing —
136
+ // a browser), `create` fails `error-code.not-supported` — the honest
137
+ // capability answer. On Deno the providers need `--allow-net`; a denied
138
+ // permission arrives as `Deno.errors.NotCapable` through the compat layer
139
+ // and maps to `access-denied`.
140
+ import { sockets02 } from "./internal/sockets_02.js";
141
+ import { sockets03 } from "./internal/sockets_03.js";
142
+ // The public vocabulary (types, address codec, validators, the platform
143
+ // error mapper) lives in internal/sockets_shared.ts; THIS module is its
144
+ // public home. The tracks live in internal/sockets_03.ts and
145
+ // internal/sockets_02.ts and share one node-builtins backend
146
+ // (internal/sockets_platform.ts).
147
+ export { ipHostname, isUnspecified, isValidAddressFamily, mapPlatformError, MAX_UDP_DATAGRAM_SIZE, parseNetAddr, sameSocketAddress, wildcardAddress, } from "./internal/sockets_shared.js";
148
+ // The 0.2 track's public pieces: the enum error vocabulary, the io-error
149
+ // resource `network-error-code` downcasts, and the opaque network.
150
+ export { Network, SocketIoError, } from "./internal/sockets_02.js";
151
+ export const SOCKETS_TYPES_INTERFACE = "wasi:sockets/types@0.3";
152
+ /**
153
+ * The `wasi:sockets` provider fragment, BOTH tracks (module header):
154
+ * `types@0.3` + `ip-name-lookup@0.3` (internal/sockets_03.ts) and the
155
+ * seven poll-shaped `@0.2` interfaces (internal/sockets_02.ts). Track
156
+ * keys — one provider serves every 0.2.x / 0.3.x the resolver folds onto
157
+ * its track. Resource classes are built per fragment so the `onCall`
158
+ * observer is scoped to it.
159
+ */
160
+ export function sockets(options = {}) {
161
+ const onCall = options.onCall ?? (() => { });
162
+ const track03 = sockets03(onCall);
163
+ return {
164
+ imports: {
165
+ ...track03.imports,
166
+ ...sockets02(onCall).imports,
167
+ },
168
+ UdpSocket: track03.UdpSocket,
169
+ TcpSocket: track03.TcpSocket,
170
+ resolveAddresses: track03.resolveAddresses,
171
+ };
172
+ }
package/package.json ADDED
@@ -0,0 +1,98 @@
1
+ {
2
+ "name": "@polyengine/wasi",
3
+ "version": "0.1.0-pre.g633468a",
4
+ "description": "WASI providers for polyengine hosts: the p2 baseline and p3 clocks, one module per semver track.",
5
+ "homepage": "https://github.com/polymorph-components/polyengine#readme",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/polymorph-components/polyengine.git"
9
+ },
10
+ "license": "Apache-2.0",
11
+ "bugs": {
12
+ "url": "https://github.com/polymorph-components/polyengine/issues"
13
+ },
14
+ "module": "./esm/mod.js",
15
+ "types": "./types/mod.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "import": {
19
+ "types": "./types/mod.d.ts",
20
+ "default": "./esm/mod.js"
21
+ }
22
+ },
23
+ "./cli": {
24
+ "import": {
25
+ "types": "./types/cli.d.ts",
26
+ "default": "./esm/cli.js"
27
+ }
28
+ },
29
+ "./cli-stdio": {
30
+ "import": {
31
+ "types": "./types/cli_stdio.d.ts",
32
+ "default": "./esm/cli_stdio.js"
33
+ }
34
+ },
35
+ "./clocks": {
36
+ "import": {
37
+ "types": "./types/clocks.d.ts",
38
+ "default": "./esm/clocks.js"
39
+ }
40
+ },
41
+ "./filesystem": {
42
+ "import": {
43
+ "types": "./types/filesystem.d.ts",
44
+ "default": "./esm/filesystem.js"
45
+ }
46
+ },
47
+ "./filesystem-node": {
48
+ "import": {
49
+ "types": "./types/filesystem_node.d.ts",
50
+ "default": "./esm/filesystem_node.js"
51
+ }
52
+ },
53
+ "./filesystem-web": {
54
+ "import": {
55
+ "types": "./types/filesystem_web.d.ts",
56
+ "default": "./esm/filesystem_web.js"
57
+ }
58
+ },
59
+ "./http": {
60
+ "import": {
61
+ "types": "./types/http.d.ts",
62
+ "default": "./esm/http.js"
63
+ }
64
+ },
65
+ "./io": {
66
+ "import": {
67
+ "types": "./types/io.d.ts",
68
+ "default": "./esm/io.js"
69
+ }
70
+ },
71
+ "./random": {
72
+ "import": {
73
+ "types": "./types/random.d.ts",
74
+ "default": "./esm/random.js"
75
+ }
76
+ },
77
+ "./sockets": {
78
+ "import": {
79
+ "types": "./types/sockets.d.ts",
80
+ "default": "./esm/sockets.js"
81
+ }
82
+ },
83
+ "./package.json": "./package.json"
84
+ },
85
+ "scripts": {},
86
+ "type": "module",
87
+ "engines": {
88
+ "node": ">=22.14.0"
89
+ },
90
+ "publishConfig": {
91
+ "access": "public"
92
+ },
93
+ "dependencies": {
94
+ "@polyengine/protocol": "0.1.0-pre.g633468a",
95
+ "@polyengine/runtime": "0.1.0-pre.g633468a"
96
+ },
97
+ "_generatedBy": "dnt@0.43.2"
98
+ }
package/types/cli.d.ts ADDED
@@ -0,0 +1,44 @@
1
+ export { type CliByteSource, type CliErrorCode, type CliIoResult, ExitError, TerminalInput, TerminalOutput, } from "./internal/cli_shared.js";
2
+ export interface CliOptions {
3
+ /** `get-arguments`; default `[]`. */
4
+ args?: string[];
5
+ /** `get-environment`; default `{}`. */
6
+ env?: Record<string, string>;
7
+ /** `initial-cwd`; default `undefined` (none). */
8
+ cwd?: string;
9
+ /** `get-stdin`'s buffer contents; default empty (matches contract: "stdin (empty)"). */
10
+ stdinBuffer?: Uint8Array;
11
+ /** Also `console.log`/`console.error` captured stdout/stderr writes. Default false. */
12
+ passthrough?: boolean;
13
+ /** `exit()` throws `ExitError` instead of merely recording. Default false. */
14
+ throwOnExit?: boolean;
15
+ }
16
+ /** Captured host-observable state exposed on the returned handle (contract wording). */
17
+ export interface CliCaptured {
18
+ stdout(): Uint8Array;
19
+ stderr(): Uint8Array;
20
+ stdoutText(): string;
21
+ stderrText(): string;
22
+ /** Whether `wasi:cli/exit#exit` has been called. */
23
+ exited(): boolean;
24
+ /** The `result` kind of the last `exit()` call's `status`, or `undefined` if never called. */
25
+ exitOk(): boolean | undefined;
26
+ /** The last `exit-with-code` status (0.3), or `undefined` if never called. */
27
+ exitCode(): number | undefined;
28
+ }
29
+ export interface CliResult {
30
+ imports: Record<string, unknown>;
31
+ captured: CliCaptured;
32
+ }
33
+ /**
34
+ * `wasi:cli@0.2` provider fragment (track key).
35
+ *
36
+ * `exit`'s WIT signature is `exit: func(status: result)` — `result` with no
37
+ * type parameters, i.e. `result<_, _>`. Per contracts/embedder-api.md's value
38
+ * table, a `result` in **parameter** (non-return) position is plain nested
39
+ * data: `{ kind: "ok" } | { kind: "err" }` (the A10 family — this comment
40
+ * and the impl carried the pre-A10 `tag` spelling until 2026-08-14, a
41
+ * latent bug the direct-call unit tests masked), never a throw. Only a
42
+ * function's own *return*-position result throws/rejects.
43
+ */
44
+ export declare function cli(options?: CliOptions): CliResult;
@@ -0,0 +1,31 @@
1
+ import { type ByteSink } from "./io.js";
2
+ export { type ByteSink } from "./io.js";
3
+ export interface CliStdioOptions {
4
+ /** stdin bytes; default: the host process's stdin. */
5
+ stdin?: AsyncIterable<Uint8Array>;
6
+ /** stdout sink; default: the host process's stdout. */
7
+ stdout?: ByteSink;
8
+ /** stderr sink; default: the host process's stderr. */
9
+ stderr?: ByteSink;
10
+ /** Terminal-ness per stream; default: the real streams' `isTTY`. */
11
+ isTty?: {
12
+ stdin?: boolean;
13
+ stdout?: boolean;
14
+ stderr?: boolean;
15
+ };
16
+ /** `get-arguments`; default: the host process's argv (script-relative). */
17
+ args?: string[];
18
+ /** `get-environment`; default: the host process's env. */
19
+ env?: Record<string, string>;
20
+ /** `initial-cwd`/`get-initial-cwd`; default: the host process's cwd. */
21
+ cwd?: string;
22
+ /** `exit` terminates the host process instead of throwing `ExitError`. */
23
+ exitProcess?: boolean;
24
+ }
25
+ export interface CliStdio {
26
+ imports: Record<string, unknown>;
27
+ }
28
+ /**
29
+ * `wasi:cli` over the host process's stdio (both tracks — module header).
30
+ */
31
+ export declare function cliStdio(options?: CliStdioOptions): CliStdio;
@@ -0,0 +1,8 @@
1
+ export interface ClocksOptions {
2
+ /** Override the monotonic clock's `now()` (nanoseconds); default `performance.now()`-derived. */
3
+ now?: () => bigint;
4
+ }
5
+ /** `wasi:clocks@0.2` + `wasi:clocks@0.3` provider fragment (two track keys). */
6
+ export declare function clocks(options?: ClocksOptions): {
7
+ imports: Record<string, unknown>;
8
+ };
@@ -0,0 +1,30 @@
1
+ /**
2
+ * `wasi:filesystem/types.descriptor` — constructible-never.
3
+ *
4
+ * `preopens#get-directories` always returns `[]` (below), so no `descriptor`
5
+ * instance can ever reach a guest through this shim; the class exists only
6
+ * so the resource *type* is a legal, structurally-typeable import target.
7
+ * Every method throws if somehow reached — loud, not a silent wrong answer —
8
+ * because reaching one would mean a caller found a descriptor this shim never
9
+ * handed out.
10
+ */
11
+ export declare class Descriptor {
12
+ #private;
13
+ constructor();
14
+ getFlags(): never;
15
+ getType(): never;
16
+ stat(): never;
17
+ statAt(): never;
18
+ openAt(): never;
19
+ readViaStream(): never;
20
+ writeViaStream(): never;
21
+ appendViaStream(): never;
22
+ metadataHashAt(): never;
23
+ }
24
+ /** `wasi:filesystem/types.directory-entry-stream` — same rationale as `Descriptor`. */
25
+ export declare class DirectoryEntryStream {
26
+ }
27
+ /** `wasi:filesystem@0.2` provider fragment (track key). */
28
+ export declare function filesystem(): {
29
+ imports: Record<string, unknown>;
30
+ };
@@ -0,0 +1,16 @@
1
+ import { type FilesystemAccessOptions, type FilesystemFragment, type MaybeAsync } from "./internal/fs_provider.js";
2
+ export interface FilesystemNodeOptions extends FilesystemAccessOptions {
3
+ /**
4
+ * Guest name → host directory path. Each entry becomes a preopen
5
+ * (`preopens#get-directories`). No default: filesystem access is an
6
+ * explicit grant. Host paths are resolved (realpath) at construction
7
+ * and must name directories. Read-only unless `writable` is set.
8
+ */
9
+ preopens: Record<string, string>;
10
+ }
11
+ /**
12
+ * `wasi:filesystem` over node's `node:fs` builtin (module header).
13
+ * Serves both the `@0.2` and `@0.3` tracks.
14
+ */
15
+ export declare function filesystemNode(options: FilesystemNodeOptions): FilesystemFragment;
16
+ export type { FilesystemFragment, MaybeAsync };
@@ -0,0 +1,61 @@
1
+ import { type FilesystemAccessOptions, type FilesystemFragment } from "./internal/fs_provider.js";
2
+ export interface OpfsFileLike {
3
+ readonly size: number;
4
+ readonly lastModified: number;
5
+ slice(start: number, end: number): {
6
+ arrayBuffer(): Promise<ArrayBuffer>;
7
+ };
8
+ arrayBuffer(): Promise<ArrayBuffer>;
9
+ }
10
+ export interface OpfsWritable {
11
+ write(params: {
12
+ type: "write";
13
+ position: number;
14
+ data: Uint8Array;
15
+ }): Promise<void>;
16
+ truncate(size: number): Promise<void>;
17
+ close(): Promise<void>;
18
+ }
19
+ export interface OpfsFileHandle {
20
+ readonly kind: "file";
21
+ readonly name: string;
22
+ getFile(): Promise<OpfsFileLike>;
23
+ createWritable(opts?: {
24
+ keepExistingData?: boolean;
25
+ }): Promise<OpfsWritable>;
26
+ isSameEntry(other: OpfsFileHandle | OpfsDirectoryHandle): Promise<boolean>;
27
+ /** Chromium's FileSystemHandle.move; absent on Firefox/Safari. */
28
+ move?(parent: OpfsDirectoryHandle, name: string): Promise<void>;
29
+ }
30
+ export interface OpfsDirectoryHandle {
31
+ readonly kind: "directory";
32
+ readonly name: string;
33
+ getDirectoryHandle(name: string, opts?: {
34
+ create?: boolean;
35
+ }): Promise<OpfsDirectoryHandle>;
36
+ getFileHandle(name: string, opts?: {
37
+ create?: boolean;
38
+ }): Promise<OpfsFileHandle>;
39
+ removeEntry(name: string, opts?: {
40
+ recursive?: boolean;
41
+ }): Promise<void>;
42
+ entries(): AsyncIterable<[string, OpfsDirectoryHandle | OpfsFileHandle]>;
43
+ isSameEntry(other: OpfsFileHandle | OpfsDirectoryHandle): Promise<boolean>;
44
+ move?(parent: OpfsDirectoryHandle, name: string): Promise<void>;
45
+ }
46
+ export interface FilesystemWebOptions extends FilesystemAccessOptions {
47
+ /**
48
+ * Guest name → OPFS directory handle. Each entry becomes a preopen.
49
+ * No default: filesystem access is an explicit grant — pass
50
+ * `await navigator.storage.getDirectory()` (or any structural
51
+ * equivalent, e.g. an in-memory fake) yourself. Read-only unless
52
+ * `writable` is set.
53
+ */
54
+ preopens: Record<string, OpfsDirectoryHandle>;
55
+ }
56
+ /**
57
+ * `wasi:filesystem` over the Origin Private File System (module header).
58
+ * Serves both the `@0.2` (parking, JSPI) and `@0.3` tracks.
59
+ */
60
+ export declare function filesystemWeb(options: FilesystemWebOptions): FilesystemFragment;
61
+ export type { FilesystemFragment };