@bytecodealliance/preview2-shim 0.23.0 → 0.24.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/README.md +148 -7
- package/dist/browser/cli.d.ts +22 -1
- package/dist/browser/cli.js +66 -34
- package/dist/browser/clocks.js +24 -5
- package/dist/browser/common.d.ts +5 -0
- package/dist/browser/common.js +16 -0
- package/dist/browser/filesystem.d.ts +55 -97
- package/dist/browser/filesystem.js +106 -287
- package/dist/browser/http.d.ts +9 -0
- package/dist/browser/http.js +446 -136
- package/dist/browser/in-memory-filesystem.d.ts +22 -0
- package/dist/browser/in-memory-filesystem.js +530 -0
- package/dist/browser/in-memory-http.d.ts +8 -0
- package/dist/browser/in-memory-http.js +11 -0
- package/dist/browser/in-memory-sockets.d.ts +34 -0
- package/dist/browser/in-memory-sockets.js +294 -0
- package/dist/browser/io.d.ts +11 -2
- package/dist/browser/io.js +200 -39
- package/dist/browser/random.js +5 -3
- package/dist/browser/sockets.d.ts +2 -1
- package/dist/browser/sockets.js +75 -56
- package/dist/common/instantiation.d.ts +4 -1
- package/dist/common/instantiation.js +85 -49
- package/dist/io/worker-socket-tcp.js +3 -3
- package/dist/io/worker-socket-udp.js +3 -4
- package/dist/io/worker-sockets.d.ts +4 -4
- package/dist/io/worker-sockets.js +29 -9
- package/dist/io/worker-thread.bundle.js +22 -9
- package/dist/nodejs/filesystem.d.ts +7 -0
- package/dist/nodejs/filesystem.js +65 -22
- package/dist/nodejs/sockets.d.ts +13 -0
- package/dist/nodejs/sockets.js +20 -0
- package/package.json +2 -2
- package/types/instantiation.d.ts +64 -12
package/README.md
CHANGED
|
@@ -4,12 +4,145 @@ WASI Preview2 implementations for Node.js & browsers.
|
|
|
4
4
|
|
|
5
5
|
Node.js support is fully tested and conformant against the Wasmtime test suite.
|
|
6
6
|
|
|
7
|
-
Browser support is
|
|
7
|
+
Browser support is available with the platform limitations documented below.
|
|
8
8
|
|
|
9
9
|
The Node.js implementation owns its worker artifact. Direct package use and supported downstream
|
|
10
10
|
bundlers should resolve it through the public shim imports; applications do not need to import or
|
|
11
11
|
copy files from `dist/io`.
|
|
12
12
|
|
|
13
|
+
## Browser support matrix
|
|
14
|
+
|
|
15
|
+
Browser defaults are capability-safe: clocks and secure randomness use Web APIs, stdout and stderr
|
|
16
|
+
write to the console, stdin is closed, outbound HTTP uses `fetch`, filesystem preopens must be
|
|
17
|
+
configured explicitly, and raw sockets are unavailable unless an embedding supplies an adapter.
|
|
18
|
+
|
|
19
|
+
| WASI area | Browser status | Default capability |
|
|
20
|
+
| ----------------------------- | ------------------------------------------------------------- | ----------------------------------------------------------------- |
|
|
21
|
+
| CLI environment and arguments | Configurable per `WASIShim`; compatibility setters are global | Empty snapshots unless configured |
|
|
22
|
+
| CLI stdin | Adapter-backed | Closed stream |
|
|
23
|
+
| CLI stdout and stderr | Web API | Console-backed, preserving split UTF-8 writes until flush/newline |
|
|
24
|
+
| CLI terminals | Adapter-backed | No terminal resource |
|
|
25
|
+
| Clocks | Web API | `performance.now`, `Date.now`, and timer-backed pollables |
|
|
26
|
+
| Random | Web API | `crypto.getRandomValues`, including requests larger than 64 KiB |
|
|
27
|
+
| I/O streams and poll | Implemented browser resources | Non-blocking streams depend on their injected handlers |
|
|
28
|
+
| Filesystem | Adapter-backed; opt-in in-memory compatibility implementation | No persistent storage is selected implicitly |
|
|
29
|
+
| Outbound HTTP | Web API | Delegates to `fetch` |
|
|
30
|
+
| Incoming HTTP | Adapter-backed; opt-in in-memory client | Browsers cannot listen for arbitrary inbound HTTP |
|
|
31
|
+
| TCP and UDP | Adapter-backed; opt-in in-memory implementations | Raw sockets are not exposed by standard browsers |
|
|
32
|
+
| DNS | Host adapter required | DNS is not exposed independently by standard browsers |
|
|
33
|
+
| `WASIShim` instantiation | Implemented | Interface namespaces can be overridden per instance |
|
|
34
|
+
|
|
35
|
+
An operation is not considered supported merely because its interface shape exists. Adapter-backed
|
|
36
|
+
rows require the embedding application to provide that capability; unavailable operations fail with
|
|
37
|
+
a WASI-domain error instead of logging or returning a placeholder resource.
|
|
38
|
+
|
|
39
|
+
### Detailed browser capabilities
|
|
40
|
+
|
|
41
|
+
The following table describes the built-in browser implementation. An application-provided
|
|
42
|
+
namespace can replace any row through `WASIShim`.
|
|
43
|
+
|
|
44
|
+
| Interface | Implemented | Host adapter required | Unsupported by browser implementation |
|
|
45
|
+
| ----------------------------- | ------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- | ---------------------------------------------- |
|
|
46
|
+
| `wasi:cli` | environment, arguments, initial cwd, exit, stream and terminal accessors | stdin/stdout/stderr handlers and terminal resources | — |
|
|
47
|
+
| `wasi:clocks` | wall clock, monotonic clock, timer subscriptions | — | timezone APIs (not part of Preview 2) |
|
|
48
|
+
| `wasi:random` | secure and insecure bytes, insecure seed | — | — |
|
|
49
|
+
| `wasi:io` | errors, input/output streams, poll and pollables | readiness and I/O behavior for injected stream handlers | synchronous blocking of the browser event loop |
|
|
50
|
+
| `wasi:filesystem` | descriptors, files, directories, links, metadata, streams, preopens through the ephemeral adapter | persistent storage, permissions, and external file handles | symbolic-link creation and reading |
|
|
51
|
+
| `wasi:http/outgoing-handler` | Fetch-backed requests and buffered request bodies | Fetch implementation and network permission | request/response trailers; streaming uploads |
|
|
52
|
+
| `wasi:http/incoming-handler` | request/response translation, injectable handler, and in-memory client | HTTP server, service worker, or other request source | direct browser listening |
|
|
53
|
+
| `wasi:sockets/ip-name-lookup` | interface shape only | complete interface replacement | built-in DNS lookup |
|
|
54
|
+
| `wasi:sockets/tcp*` | opt-in in-memory server/client implementation | adapter for external connectivity | built-in raw TCP |
|
|
55
|
+
| `wasi:sockets/udp*` | opt-in in-memory server/client implementation | adapter for external connectivity | built-in raw UDP |
|
|
56
|
+
|
|
57
|
+
Outbound HTTP buffers a requested body until `outgoing-body.finish` before calling `fetch`.
|
|
58
|
+
This preserves complete-body semantics across browsers but does not provide streaming upload or
|
|
59
|
+
upload backpressure. Incoming Fetch bodies retain their asynchronous stream behavior. HTTP
|
|
60
|
+
trailers are not implemented.
|
|
61
|
+
|
|
62
|
+
Chromium-based browsers can opt into Fetch request streaming. This setting uses a
|
|
63
|
+
`ReadableStream` request body with `duplex: "half"`; unsupported browsers reject the request, so
|
|
64
|
+
applications should enable it only after applying their own browser support policy or feature
|
|
65
|
+
detection:
|
|
66
|
+
|
|
67
|
+
```js
|
|
68
|
+
import { http } from "@bytecodealliance/preview2-shim";
|
|
69
|
+
|
|
70
|
+
http._setRequestStreaming(true);
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
The setting affects subsequent requests made through the browser HTTP shim. Call
|
|
74
|
+
`http._setRequestStreaming(false)` to restore portable completion buffering.
|
|
75
|
+
|
|
76
|
+
Browser applications select storage explicitly. The bundled file-data adapter is ephemeral and must
|
|
77
|
+
be opted into:
|
|
78
|
+
|
|
79
|
+
```js
|
|
80
|
+
import { filesystem } from "@bytecodealliance/preview2-shim";
|
|
81
|
+
import { WASIShim } from "@bytecodealliance/preview2-shim/instantiation";
|
|
82
|
+
|
|
83
|
+
const shim = new WASIShim({
|
|
84
|
+
environment: { MODE: "browser" },
|
|
85
|
+
arguments: ["component"],
|
|
86
|
+
stdout: { write: (bytes) => terminal.write(bytes) },
|
|
87
|
+
browserFilesystem: {
|
|
88
|
+
adapter: new filesystem.InMemoryFilesystemAdapter(),
|
|
89
|
+
preopens: { "/data": { dir: {} } },
|
|
90
|
+
},
|
|
91
|
+
sandbox: { enableNetwork: false },
|
|
92
|
+
});
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
The browser shim does not request File System Access permissions or choose IndexedDB/OPFS on an
|
|
96
|
+
application's behalf. Applications that need another storage model implement the generated
|
|
97
|
+
`wasi:filesystem/types` and `wasi:filesystem/preopens` namespaces and inject them through the
|
|
98
|
+
`filesystem` option:
|
|
99
|
+
|
|
100
|
+
```js
|
|
101
|
+
const shim = new WASIShim({
|
|
102
|
+
filesystem: {
|
|
103
|
+
types: applicationFilesystemTypes,
|
|
104
|
+
preopens: applicationFilesystemPreopens,
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
This keeps permission prompts, handle acquisition, persistence, and synchronization policy in
|
|
110
|
+
application code. Raw TCP, UDP, and DNS are denied by default; outbound HTTP remains a separate
|
|
111
|
+
`fetch` capability.
|
|
112
|
+
|
|
113
|
+
Deterministic in-memory transports are available for components that act as servers and for tests:
|
|
114
|
+
|
|
115
|
+
```js
|
|
116
|
+
import { http, sockets } from "@bytecodealliance/preview2-shim";
|
|
117
|
+
|
|
118
|
+
const tcpSockets = new sockets.InMemoryTcpSockets();
|
|
119
|
+
const tcpClient = tcpSockets.connect(serverAddress);
|
|
120
|
+
const udpSockets = new sockets.InMemoryUdpSockets();
|
|
121
|
+
const udpClient = udpSockets.createClient(clientAddress);
|
|
122
|
+
const httpClient = new http.InMemoryHttpClient(component.incomingHandler);
|
|
123
|
+
|
|
124
|
+
const shim = new WASIShim({ tcpSockets, udpSockets });
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
These implementations route bytes only within the current JavaScript realm; they do not grant raw
|
|
128
|
+
browser network access. `InMemoryHttpClient.fetch(request)` returns a standard Web `Response`.
|
|
129
|
+
|
|
130
|
+
For a small application-owned implementation, see the
|
|
131
|
+
[Map-backed browser filesystem test shim](./test/fixtures/filesystem-shim/in-memory-map.ts). It keeps named
|
|
132
|
+
roots in an in-memory `Map`, implements `createPreopens`, and is intentionally example code rather
|
|
133
|
+
than a published or supported filesystem package. The example is exercised through the reusable
|
|
134
|
+
[filesystem implementation test suite](./test/filesystem-conformance.ts), which can also be pointed
|
|
135
|
+
at other implementations.
|
|
136
|
+
|
|
137
|
+
Browser filesystem adapters own the capabilities passed in `preopens` and the roots returned from
|
|
138
|
+
`getRoot`. A root may be shared by multiple descriptors and preopen names; the adapter is therefore
|
|
139
|
+
responsible for persistence and synchronization of shared mutations. Calling `dispose` on the
|
|
140
|
+
namespace returned by `createFilesystem` calls the adapter's optional `dispose` method once and
|
|
141
|
+
invalidates further preopen access. `WASIShim` does not currently cascade disposal, so embeddings
|
|
142
|
+
using external handles must retain and dispose their application-owned filesystem namespace or
|
|
143
|
+
adapter themselves. The bundled in-memory adapter keeps all state in memory and shares mutations
|
|
144
|
+
for the same file-data object.
|
|
145
|
+
|
|
13
146
|
# Features
|
|
14
147
|
|
|
15
148
|
## WASI Shim object for easy instantiation
|
|
@@ -57,9 +190,9 @@ const component = await instantiate(loader, new WASIShim().getImportObject());
|
|
|
57
190
|
|
|
58
191
|
## Sandboxing
|
|
59
192
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
193
|
+
On Node.js, the preview2-shim provides host filesystem, environment, and network access by default,
|
|
194
|
+
matching the usual behavior of Node.js libraries. Browser defaults expose no filesystem preopens or
|
|
195
|
+
raw sockets. Both platforms can configure which capabilities a guest receives.
|
|
63
196
|
|
|
64
197
|
### Using WASIShim for sandboxing
|
|
65
198
|
|
|
@@ -78,7 +211,7 @@ const sandboxedShim = new WASIShim({
|
|
|
78
211
|
},
|
|
79
212
|
});
|
|
80
213
|
|
|
81
|
-
//
|
|
214
|
+
// Node.js only: map virtual paths to host paths
|
|
82
215
|
const limitedShim = new WASIShim({
|
|
83
216
|
sandbox: {
|
|
84
217
|
preopens: {
|
|
@@ -95,13 +228,21 @@ const component = await instantiate(loader, sandboxedShim.getImportObject());
|
|
|
95
228
|
### Notes on sandboxing
|
|
96
229
|
|
|
97
230
|
- By default (when no options are passed), the shim is providing full access to match typical
|
|
98
|
-
Node.js library behavior.
|
|
231
|
+
Node.js library behavior. In browsers, filesystem preopens remain empty until the application
|
|
232
|
+
explicitly injects filesystem namespaces or selects the ephemeral file-data adapter.
|
|
233
|
+
- `sandbox.preopens` maps guest paths to Node.js host paths on Node.js. With
|
|
234
|
+
`browserFilesystem`, the same option maps guest paths to capabilities understood by its adapter
|
|
235
|
+
and overrides `browserFilesystem.preopens`. A custom `filesystem` can implement
|
|
236
|
+
`createPreopens(preopens)` to interpret the provided properties and return its own
|
|
237
|
+
`wasi:filesystem/preopens` namespace. The shim passes those properties through unchanged.
|
|
99
238
|
- Each `WASIShim` instance has its own isolated preopens, environment variables, and arguments.
|
|
100
239
|
Multiple instances with different configurations will not affect each other.
|
|
101
240
|
- The direct preopen functions (`_setPreopens`, `_clearPreopens`, etc.) modify global state and
|
|
102
241
|
affect all components not using `WASIShim` with explicit configuration. For isolation, prefer
|
|
103
242
|
using `WASIShim` with the `sandbox` option containing `preopens` and `env`.
|
|
104
|
-
- When `sandbox.enableNetwork: false`,
|
|
243
|
+
- When `sandbox.enableNetwork: false`, Node.js socket operations receive an instance-local denied
|
|
244
|
+
network capability. Outbound HTTP is a separate Fetch capability; replace or omit the HTTP
|
|
245
|
+
namespace when the embedding must deny it as well.
|
|
105
246
|
|
|
106
247
|
[jco]: https://www.npmjs.com/package/@bytecodealliance/jco
|
|
107
248
|
|
package/dist/browser/cli.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { exit as ExitNamespace, stderr as StderrNamespace, stdin as StdinNamespace, stdout as StdoutNamespace, terminalInput as TerminalInputNamespace, terminalOutput as TerminalOutputNamespace, terminalStderr as TerminalStderrNamespace, terminalStdin as TerminalStdinNamespace, terminalStdout as TerminalStdoutNamespace } from "../../types/cli.js";
|
|
1
|
+
import type { environment as EnvironmentNamespace, exit as ExitNamespace, stderr as StderrNamespace, stdin as StdinNamespace, stdout as StdoutNamespace, terminalInput as TerminalInputNamespace, terminalOutput as TerminalOutputNamespace, terminalStderr as TerminalStderrNamespace, terminalStdin as TerminalStdinNamespace, terminalStdout as TerminalStdoutNamespace } from "../../types/cli.js";
|
|
2
2
|
import { type InputStreamHandler, type OutputStreamHandler } from "./io.js";
|
|
3
3
|
export { _setEnv, _setArgs, environment } from "./environment.js";
|
|
4
4
|
export { _setCwd } from "./config.js";
|
|
@@ -6,6 +6,14 @@ export declare const exit: typeof ExitNamespace;
|
|
|
6
6
|
export declare function _setStdin(handler: InputStreamHandler): void;
|
|
7
7
|
export declare function _setStderr(handler: OutputStreamHandler): void;
|
|
8
8
|
export declare function _setStdout(handler: OutputStreamHandler): void;
|
|
9
|
+
export interface BrowserCliConfig {
|
|
10
|
+
environment?: Record<string, string>;
|
|
11
|
+
arguments?: string[];
|
|
12
|
+
initialCwd?: string;
|
|
13
|
+
stdin?: InputStreamHandler;
|
|
14
|
+
stdout?: OutputStreamHandler;
|
|
15
|
+
stderr?: OutputStreamHandler;
|
|
16
|
+
}
|
|
9
17
|
export declare const stdin: typeof StdinNamespace;
|
|
10
18
|
export declare const stdout: typeof StdoutNamespace;
|
|
11
19
|
export declare const stderr: typeof StderrNamespace;
|
|
@@ -14,3 +22,16 @@ export declare const terminalOutput: typeof TerminalOutputNamespace;
|
|
|
14
22
|
export declare const terminalStderr: typeof TerminalStderrNamespace;
|
|
15
23
|
export declare const terminalStdin: typeof TerminalStdinNamespace;
|
|
16
24
|
export declare const terminalStdout: typeof TerminalStdoutNamespace;
|
|
25
|
+
/** Create isolated browser CLI interfaces without changing compatibility globals. */
|
|
26
|
+
export declare function createCli(config?: BrowserCliConfig): {
|
|
27
|
+
environment: typeof EnvironmentNamespace;
|
|
28
|
+
exit: typeof ExitNamespace;
|
|
29
|
+
stdin: typeof StdinNamespace;
|
|
30
|
+
stdout: typeof StdoutNamespace;
|
|
31
|
+
stderr: typeof StderrNamespace;
|
|
32
|
+
terminalInput: typeof TerminalInputNamespace;
|
|
33
|
+
terminalOutput: typeof TerminalOutputNamespace;
|
|
34
|
+
terminalStdin: typeof TerminalStdinNamespace;
|
|
35
|
+
terminalStdout: typeof TerminalStdoutNamespace;
|
|
36
|
+
terminalStderr: typeof TerminalStderrNamespace;
|
|
37
|
+
};
|
package/dist/browser/cli.js
CHANGED
|
@@ -29,41 +29,46 @@ export function _setStdout(handler) {
|
|
|
29
29
|
stdoutStream.handler = handler;
|
|
30
30
|
}
|
|
31
31
|
const stdinStream = inputStreamCreate({
|
|
32
|
-
blockingRead(
|
|
33
|
-
|
|
34
|
-
return new Uint8Array(0);
|
|
32
|
+
blockingRead() {
|
|
33
|
+
throw { tag: "closed" };
|
|
35
34
|
},
|
|
36
35
|
subscribe() {
|
|
37
|
-
// TODO
|
|
38
36
|
return pollableCreate();
|
|
39
37
|
},
|
|
40
|
-
[symbolDispose]() {
|
|
41
|
-
// TODO
|
|
42
|
-
},
|
|
43
|
-
});
|
|
44
|
-
const textDecoder = new TextDecoder();
|
|
45
|
-
const stdoutStream = outputStreamCreate({
|
|
46
|
-
write(contents) {
|
|
47
|
-
if (contents.at(-1) == 10) {
|
|
48
|
-
// console.log already appends a new line
|
|
49
|
-
contents = contents.subarray(0, -1);
|
|
50
|
-
}
|
|
51
|
-
console.log(textDecoder.decode(contents));
|
|
52
|
-
},
|
|
53
|
-
blockingFlush() { },
|
|
54
38
|
[symbolDispose]() { },
|
|
55
39
|
});
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
40
|
+
function consoleStream(writeLine) {
|
|
41
|
+
const decoder = new TextDecoder();
|
|
42
|
+
let pending = "";
|
|
43
|
+
const emitCompleteLines = () => {
|
|
44
|
+
const lines = pending.split("\n");
|
|
45
|
+
pending = lines.pop();
|
|
46
|
+
for (const line of lines) {
|
|
47
|
+
writeLine(line.endsWith("\r") ? line.slice(0, -1) : line);
|
|
61
48
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
49
|
+
};
|
|
50
|
+
return {
|
|
51
|
+
write(contents) {
|
|
52
|
+
pending += decoder.decode(contents, { stream: true });
|
|
53
|
+
emitCompleteLines();
|
|
54
|
+
},
|
|
55
|
+
flush() {
|
|
56
|
+
pending += decoder.decode();
|
|
57
|
+
if (pending) {
|
|
58
|
+
writeLine(pending);
|
|
59
|
+
}
|
|
60
|
+
pending = "";
|
|
61
|
+
},
|
|
62
|
+
blockingFlush() {
|
|
63
|
+
this.flush?.();
|
|
64
|
+
},
|
|
65
|
+
drop() {
|
|
66
|
+
this.flush?.();
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
const stdoutStream = outputStreamCreate(consoleStream((line) => console.log(line)));
|
|
71
|
+
const stderrStream = outputStreamCreate(consoleStream((line) => console.error(line)));
|
|
67
72
|
export const stdin = {
|
|
68
73
|
getStdin() {
|
|
69
74
|
return stdinStream;
|
|
@@ -83,9 +88,6 @@ class TerminalInput {
|
|
|
83
88
|
}
|
|
84
89
|
class TerminalOutput {
|
|
85
90
|
}
|
|
86
|
-
const terminalStdoutInstance = new TerminalOutput();
|
|
87
|
-
const terminalStderrInstance = new TerminalOutput();
|
|
88
|
-
const terminalStdinInstance = new TerminalInput();
|
|
89
91
|
export const terminalInput = {
|
|
90
92
|
TerminalInput,
|
|
91
93
|
};
|
|
@@ -94,16 +96,46 @@ export const terminalOutput = {
|
|
|
94
96
|
};
|
|
95
97
|
export const terminalStderr = {
|
|
96
98
|
getTerminalStderr() {
|
|
97
|
-
return
|
|
99
|
+
return undefined;
|
|
98
100
|
},
|
|
99
101
|
};
|
|
100
102
|
export const terminalStdin = {
|
|
101
103
|
getTerminalStdin() {
|
|
102
|
-
return
|
|
104
|
+
return undefined;
|
|
103
105
|
},
|
|
104
106
|
};
|
|
105
107
|
export const terminalStdout = {
|
|
106
108
|
getTerminalStdout() {
|
|
107
|
-
return
|
|
109
|
+
return undefined;
|
|
108
110
|
},
|
|
109
111
|
};
|
|
112
|
+
/** Create isolated browser CLI interfaces without changing compatibility globals. */
|
|
113
|
+
export function createCli(config = {}) {
|
|
114
|
+
const stdinInstance = inputStreamCreate(config.stdin ?? {
|
|
115
|
+
blockingRead() {
|
|
116
|
+
throw { tag: "closed" };
|
|
117
|
+
},
|
|
118
|
+
subscribe: () => pollableCreate(),
|
|
119
|
+
});
|
|
120
|
+
const stdoutInstance = outputStreamCreate(config.stdout ?? consoleStream((line) => console.log(line)));
|
|
121
|
+
const stderrInstance = outputStreamCreate(config.stderr ?? consoleStream((line) => console.error(line)));
|
|
122
|
+
const env = Object.entries(config.environment ?? {});
|
|
123
|
+
const args = [...(config.arguments ?? [])];
|
|
124
|
+
const cwd = config.initialCwd ?? "/";
|
|
125
|
+
return {
|
|
126
|
+
environment: {
|
|
127
|
+
getEnvironment: () => env.map(([key, value]) => [key, value]),
|
|
128
|
+
getArguments: () => [...args],
|
|
129
|
+
initialCwd: () => cwd,
|
|
130
|
+
},
|
|
131
|
+
exit,
|
|
132
|
+
stdin: { getStdin: () => stdinInstance },
|
|
133
|
+
stdout: { getStdout: () => stdoutInstance },
|
|
134
|
+
stderr: { getStderr: () => stderrInstance },
|
|
135
|
+
terminalInput,
|
|
136
|
+
terminalOutput,
|
|
137
|
+
terminalStdin,
|
|
138
|
+
terminalStdout,
|
|
139
|
+
terminalStderr,
|
|
140
|
+
};
|
|
141
|
+
}
|
package/dist/browser/clocks.js
CHANGED
|
@@ -1,4 +1,21 @@
|
|
|
1
|
+
import { checkedU64 } from "./common.js";
|
|
1
2
|
import { pollableCreate } from "./io.js";
|
|
3
|
+
const MAX_TIMEOUT_MS = 0x7fffffff;
|
|
4
|
+
function timeout(durationNs) {
|
|
5
|
+
let remainingMs = Number((durationNs + 999999n) / 1000000n);
|
|
6
|
+
return new Promise((resolve) => {
|
|
7
|
+
const next = () => {
|
|
8
|
+
if (remainingMs <= 0) {
|
|
9
|
+
resolve();
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
const delay = Math.min(remainingMs, MAX_TIMEOUT_MS);
|
|
13
|
+
remainingMs -= delay;
|
|
14
|
+
setTimeout(next, delay);
|
|
15
|
+
};
|
|
16
|
+
next();
|
|
17
|
+
});
|
|
18
|
+
}
|
|
2
19
|
export const monotonicClock = {
|
|
3
20
|
resolution() {
|
|
4
21
|
// usually we dont get sub-millisecond accuracy in the browser
|
|
@@ -10,17 +27,19 @@ export const monotonicClock = {
|
|
|
10
27
|
return BigInt(Math.floor(performance.now() * 1e6));
|
|
11
28
|
},
|
|
12
29
|
subscribeInstant(instant) {
|
|
13
|
-
instant =
|
|
30
|
+
instant = checkedU64(instant, "instant");
|
|
14
31
|
const now = monotonicClock.now();
|
|
15
32
|
if (instant <= now) {
|
|
16
|
-
return pollableCreate(
|
|
33
|
+
return pollableCreate();
|
|
17
34
|
}
|
|
18
35
|
return monotonicClock.subscribeDuration(instant - now);
|
|
19
36
|
},
|
|
20
37
|
subscribeDuration(duration) {
|
|
21
|
-
duration =
|
|
22
|
-
|
|
23
|
-
|
|
38
|
+
duration = checkedU64(duration, "duration");
|
|
39
|
+
if (duration === 0n) {
|
|
40
|
+
return pollableCreate();
|
|
41
|
+
}
|
|
42
|
+
return pollableCreate(timeout(duration));
|
|
24
43
|
},
|
|
25
44
|
};
|
|
26
45
|
export const wallClock = {
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const MAX_U64: bigint;
|
|
2
|
+
/** Validate and return a WebAssembly u64 value. */
|
|
3
|
+
export declare function checkedU64(value: bigint, name: string): bigint;
|
|
4
|
+
/** Convert a WebAssembly u64 value to a lossless JavaScript number. */
|
|
5
|
+
export declare function checkedU64AsNumber(value: bigint, name: string): number;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export const MAX_U64 = (1n << 64n) - 1n;
|
|
2
|
+
/** Validate and return a WebAssembly u64 value. */
|
|
3
|
+
export function checkedU64(value, name) {
|
|
4
|
+
if (typeof value !== "bigint" || value < 0n || value > MAX_U64) {
|
|
5
|
+
throw new TypeError(`${name} must be a valid u64`);
|
|
6
|
+
}
|
|
7
|
+
return value;
|
|
8
|
+
}
|
|
9
|
+
/** Convert a WebAssembly u64 value to a lossless JavaScript number. */
|
|
10
|
+
export function checkedU64AsNumber(value, name) {
|
|
11
|
+
checkedU64(value, name);
|
|
12
|
+
if (value > BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
13
|
+
throw new RangeError(`${name} exceeds JavaScript's safe integer range`);
|
|
14
|
+
}
|
|
15
|
+
return Number(value);
|
|
16
|
+
}
|
|
@@ -1,104 +1,75 @@
|
|
|
1
1
|
import { types as TypesNamespace, preopens as PreopensNamespace } from "../../types/filesystem.js";
|
|
2
|
-
import {
|
|
2
|
+
import type { FileData } from "./in-memory-filesystem.js";
|
|
3
3
|
export { _setCwd } from "./config.js";
|
|
4
|
+
export { InMemoryFilesystemAdapter } from "./in-memory-filesystem.js";
|
|
5
|
+
export type { FileData, FileDataEntry } from "./in-memory-filesystem.js";
|
|
4
6
|
type Filesize = TypesNamespace.Filesize;
|
|
5
7
|
type OpenFlags = TypesNamespace.OpenFlags;
|
|
6
8
|
type PathFlags = TypesNamespace.PathFlags;
|
|
7
|
-
export interface
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
export interface BrowserDirectoryEntryStream {
|
|
10
|
+
readDirectoryEntry(): TypesNamespace.DirectoryEntry | undefined;
|
|
11
|
+
}
|
|
12
|
+
export interface BrowserFilesystemDescriptor extends Omit<TypesNamespace.Descriptor, "isSameObject" | "linkAt" | "openAt" | "readDirectory" | "renameAt"> {
|
|
13
|
+
readDirectory(): BrowserDirectoryEntryStream;
|
|
14
|
+
linkAt(oldPathFlags: PathFlags, oldPath: string, newDescriptor: BrowserFilesystemDescriptor, newPath: string): void;
|
|
15
|
+
openAt(pathFlags: PathFlags, path: string, openFlags: OpenFlags, flags: TypesNamespace.DescriptorFlags): BrowserFilesystemDescriptor;
|
|
16
|
+
renameAt(oldPath: string, newDescriptor: BrowserFilesystemDescriptor, newPath: string): void;
|
|
17
|
+
isSameObject(other: BrowserFilesystemDescriptor): boolean;
|
|
18
|
+
}
|
|
19
|
+
export interface BrowserFilesystemAdapter<Capability = unknown> {
|
|
20
|
+
getRoot(capability: Capability): BrowserFilesystemDescriptor;
|
|
21
|
+
dispose?(): void;
|
|
22
|
+
}
|
|
23
|
+
export interface BrowserFilesystemConfig<Capability> {
|
|
24
|
+
adapter: BrowserFilesystemAdapter<Capability>;
|
|
25
|
+
preopens: Record<string, Capability>;
|
|
10
26
|
}
|
|
11
|
-
/**
|
|
12
|
-
* Root file data structure representing a filesystem tree.
|
|
13
|
-
* Each entry is either a directory (has `dir` property) or a file (has `source` property).
|
|
14
|
-
* @example
|
|
15
|
-
* // A simple filesystem with one directory containing one file:
|
|
16
|
-
* const fileData = {
|
|
17
|
-
* dir: {
|
|
18
|
-
* 'myfile.txt': { source: new Uint8Array([72, 101, 108, 108, 111]) }
|
|
19
|
-
* }
|
|
20
|
-
* };
|
|
21
|
-
*/
|
|
22
|
-
export type FileData = FileDataEntry;
|
|
23
|
-
export declare function _setFileData(fileData: FileData): void;
|
|
24
|
-
export declare function _getFileData(): string;
|
|
25
27
|
declare class DirectoryEntryStream implements TypesNamespace.DirectoryEntryStream {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
static _create(entries: [string, FileDataEntry][]): DirectoryEntryStream;
|
|
28
|
+
#private;
|
|
29
|
+
static _create(implementation: BrowserDirectoryEntryStream): DirectoryEntryStream;
|
|
29
30
|
readDirectoryEntry(): TypesNamespace.DirectoryEntry | undefined;
|
|
30
31
|
}
|
|
31
32
|
declare class Descriptor implements TypesNamespace.Descriptor {
|
|
32
33
|
#private;
|
|
33
|
-
|
|
34
|
-
static _create(
|
|
35
|
-
readViaStream(
|
|
36
|
-
writeViaStream(
|
|
37
|
-
appendViaStream():
|
|
34
|
+
_getImplementation(descriptor: Descriptor): BrowserFilesystemDescriptor;
|
|
35
|
+
static _create(implementation: BrowserFilesystemDescriptor): Descriptor;
|
|
36
|
+
readViaStream(offset: Filesize): import("../../types/interfaces/wasi-io-streams.js").InputStream;
|
|
37
|
+
writeViaStream(offset: Filesize): import("../../types/interfaces/wasi-io-streams.js").OutputStream;
|
|
38
|
+
appendViaStream(): import("../../types/interfaces/wasi-io-streams.js").OutputStream;
|
|
38
39
|
advise(offset: Filesize, length: Filesize, advice: TypesNamespace.Advice): void;
|
|
39
40
|
syncData(): void;
|
|
40
41
|
getFlags(): TypesNamespace.DescriptorFlags;
|
|
41
|
-
getType():
|
|
42
|
-
setSize(size:
|
|
43
|
-
setTimes(dataAccessTimestamp:
|
|
44
|
-
read(length:
|
|
42
|
+
getType(): TypesNamespace.DescriptorType;
|
|
43
|
+
setSize(size: Filesize): void;
|
|
44
|
+
setTimes(dataAccessTimestamp: TypesNamespace.NewTimestamp, dataModificationTimestamp: TypesNamespace.NewTimestamp): void;
|
|
45
|
+
read(length: Filesize, offset: Filesize): [Uint8Array<ArrayBufferLike>, boolean];
|
|
45
46
|
write(buffer: Uint8Array, offset: Filesize): bigint;
|
|
46
47
|
readDirectory(): DirectoryEntryStream;
|
|
47
48
|
sync(): void;
|
|
48
49
|
createDirectoryAt(path: string): void;
|
|
49
|
-
stat():
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
nanoseconds: number;
|
|
60
|
-
};
|
|
61
|
-
statusChangeTimestamp: {
|
|
62
|
-
seconds: bigint;
|
|
63
|
-
nanoseconds: number;
|
|
64
|
-
};
|
|
65
|
-
};
|
|
66
|
-
statAt(_pathFlags: PathFlags, path: string): {
|
|
67
|
-
type: "directory" | "regular-file" | "unknown";
|
|
68
|
-
linkCount: bigint;
|
|
69
|
-
size: bigint;
|
|
70
|
-
dataAccessTimestamp: {
|
|
71
|
-
seconds: bigint;
|
|
72
|
-
nanoseconds: number;
|
|
73
|
-
};
|
|
74
|
-
dataModificationTimestamp: {
|
|
75
|
-
seconds: bigint;
|
|
76
|
-
nanoseconds: number;
|
|
77
|
-
};
|
|
78
|
-
statusChangeTimestamp: {
|
|
79
|
-
seconds: bigint;
|
|
80
|
-
nanoseconds: number;
|
|
81
|
-
};
|
|
82
|
-
};
|
|
83
|
-
setTimesAt(): void;
|
|
84
|
-
linkAt(): void;
|
|
85
|
-
openAt(_pathFlags: PathFlags, path: string, openFlags: OpenFlags, _flags: TypesNamespace.DescriptorFlags): Descriptor;
|
|
86
|
-
readlinkAt(_path: string): string;
|
|
87
|
-
removeDirectoryAt(): void;
|
|
88
|
-
renameAt(): void;
|
|
89
|
-
symlinkAt(): void;
|
|
90
|
-
unlinkFileAt(): void;
|
|
50
|
+
stat(): TypesNamespace.DescriptorStat;
|
|
51
|
+
statAt(pathFlags: PathFlags, path: string): TypesNamespace.DescriptorStat;
|
|
52
|
+
setTimesAt(pathFlags: PathFlags, path: string, dataAccessTimestamp: TypesNamespace.NewTimestamp, dataModificationTimestamp: TypesNamespace.NewTimestamp): void;
|
|
53
|
+
linkAt(oldPathFlags: PathFlags, oldPath: string, newDescriptor: TypesNamespace.Descriptor, newPath: string): void;
|
|
54
|
+
openAt(pathFlags: PathFlags, path: string, openFlags: OpenFlags, flags: TypesNamespace.DescriptorFlags): Descriptor;
|
|
55
|
+
readlinkAt(path: string): string;
|
|
56
|
+
removeDirectoryAt(path: string): void;
|
|
57
|
+
renameAt(oldPath: string, newDescriptor: TypesNamespace.Descriptor, newPath: string): void;
|
|
58
|
+
symlinkAt(oldPath: string, newPath: string): void;
|
|
59
|
+
unlinkFileAt(path: string): void;
|
|
91
60
|
isSameObject(other: TypesNamespace.Descriptor): boolean;
|
|
92
|
-
metadataHash():
|
|
93
|
-
|
|
94
|
-
lower: bigint;
|
|
95
|
-
};
|
|
96
|
-
metadataHashAt(_pathFlags: any, _path: string): {
|
|
97
|
-
upper: bigint;
|
|
98
|
-
lower: bigint;
|
|
99
|
-
};
|
|
61
|
+
metadataHash(): TypesNamespace.MetadataHashValue;
|
|
62
|
+
metadataHashAt(pathFlags: PathFlags, path: string): TypesNamespace.MetadataHashValue;
|
|
100
63
|
}
|
|
101
64
|
export declare const preopens: typeof PreopensNamespace;
|
|
65
|
+
/** Create isolated filesystem namespaces backed by an application-selected adapter. */
|
|
66
|
+
export declare function createFilesystem<Capability>({ adapter, preopens: configuredPreopens, }: BrowserFilesystemConfig<Capability>): {
|
|
67
|
+
types: typeof TypesNamespace;
|
|
68
|
+
preopens: typeof PreopensNamespace;
|
|
69
|
+
dispose(): void;
|
|
70
|
+
};
|
|
71
|
+
export declare function _setFileData(fileData: FileData): void;
|
|
72
|
+
export declare function _getFileData(): string;
|
|
102
73
|
/**
|
|
103
74
|
* Replace all preopens with the given set.
|
|
104
75
|
* @param preopensConfig - Map of virtual paths to file data entries
|
|
@@ -110,23 +81,10 @@ export declare function _setPreopens(preopensConfig: Record<string, FileData>):
|
|
|
110
81
|
* @param fileData - The file data object representing the directory
|
|
111
82
|
*/
|
|
112
83
|
export declare function _addPreopen(virtualPath: string, fileData: FileData): void;
|
|
113
|
-
/**
|
|
114
|
-
* Clear all preopens, giving the guest no filesystem access.
|
|
115
|
-
*
|
|
116
|
-
* This functionality exists mostly to maintain backwards compatibility. Prefer setting preopens
|
|
117
|
-
* via `WASIShim` rather than making top level changes to preopens using these functions.
|
|
118
|
-
*/
|
|
84
|
+
/** Clear all preopens, giving the guest no filesystem access. */
|
|
119
85
|
export declare function _clearPreopens(): void;
|
|
120
|
-
/**
|
|
121
|
-
* Get current preopens configuration.
|
|
122
|
-
* @returns Array of [descriptor, virtualPath] pairs
|
|
123
|
-
*/
|
|
86
|
+
/** Get current preopens configuration. */
|
|
124
87
|
export declare function _getPreopens(): [Descriptor, string][];
|
|
125
|
-
/**
|
|
126
|
-
|
|
127
|
-
* This is used internally to create isolated preopen instances.
|
|
128
|
-
* @param hostPreopen - The host filesystem path
|
|
129
|
-
* @returns A preopen descriptor
|
|
130
|
-
*/
|
|
131
|
-
export declare function _createPreopenDescriptor(hostPreopen: string): Descriptor;
|
|
88
|
+
/** Reject host paths because browser filesystems require explicit capabilities. */
|
|
89
|
+
export declare function _createPreopenDescriptor(hostPreopen: string): void;
|
|
132
90
|
export declare const types: typeof TypesNamespace;
|