owned this note
owned this note
Published
Linked with GitHub
# Node.js WASI Preview2 API Ideas
The current preview1 API follows https://nodejs.org/dist/latest-v19.x/docs/api/wasi.html.
Preview2 is in development at https://github.com/bytecodealliance/preview2-prototyping/blob/main/wit/command.wit.
## Construction
Concept is that we can reuse the same `WASI` class only changing the version:
```js
const wasiInstance = new WASI({
version: 'preview2',
args: argv,
env,
stdin: 0,
stdout: 1,
stderr: 2,
returnOnExit: false,
preopens: {
'/sandbox': '/some/real/path/that/wasm/can/access',
},
})
```
Since `env`, `preopens`, `stdin`, `stdout` and `returnOnExit` are static to the instance, it likely makes sense for these to remain.
`args` may need to be separated for the preview2 interface, since they are explicit for the run function in command mode. That said, command mode only runs once, so they in theory are still static.
**Conclusions:**
* `'preview2'` should be supported as a version.
* `env` and `preopens` are fine as instance options, `args` may need to be reconsidered.
## Import Object
Where the WASI instance constructed then creates the corresponding preview2 imports interface for the Wasm module.
We already have `wasiInstance.getImportObject()` to return the import object, which could then return a preview2-compatible version.
There is also a `wasiInstance.wasiImport` which represents only the interface for the `wasi_snapshot_preview1` import. This wouldn't have a preview2 analog so wouldn't apply for the preview2 case.
**Conclusions:**
* `wasiInstance.getImportObject()` works fine for preview2
* `wasiImport` may need to be deprecated, or at least only deprecated for the preview2 API
## Instancing
In Node.js, WASI is instanced. This allows for it to be individually bound to separate Wasm modules with different static options (environment configurations, args, etc).
Preview1 also requires memory to be bound between the WASI instance and the Wasm module being executed.
Each preview1 WASI instance only runs once - either via `wasiInstance.start()` for commands or `wasiInstance.initialize()` for reactors. It binds to the memory of the Wasm module being executed and throws if these functions are called again.
In preview2, there's no memory binding, therefore it is possible to treat the WASI host functions as separate and shared between multiple Wasm modules.
As a result, for preview2:
1. Preview2 can have a host-level singular instance, something like `import * as defaultWasiInstance from 'node:wasi/preview2'`. This import can be imported by many different Wasm modules to get direct posix integration.
2. Custom instancing for preview2 remains useful for virtualization and custom WASI configurations.
**Conclusions**:
* An default WASI importObject for preview2 such as `node:wasi/preview2` can be supported
* There is still value in having a WASI virtualization interface via `new WASI({ version: 'preview2' })`, and this is a useful step before implementation of the above
## Preview2 Commands & Reactors
### Reactors
In preview2, reactors do not need any `initialize()` at all. That is, we can effectively deprecate `wasiInstance.initialize()` for the preview2 version.
Instead, once the Wasm module is bound to the import object, you can just call exported methods on it, as expected. It will then call down to WASI host bindings as necessary in a shared-nothing way.
### Commands
For preview2 commands, the Wasm Module that is the command itself exports a `run` function. Thus, like reactors, there is no need for an instance-level binding function.
**Conclusions**:
* Both `wasiInstance.initialize()` and `wasiInstance.start()` can be considered deprecated for the preview2 version of `WASI`
* We may need a new `wasiInstance.command(wasmModule, args)` for preview2 command invocation (since args are ambient)
## Capability Improvements
One of the major features of WASI is that it acts as a capability system.
It would be interesting to explore more fine-grained capability configurations for the WASI virtualization in Node.js.
For example - giving access to the file system, but denying access to the network.
In theory many complex capability scenarios are entirely possible, if we just create the right level of access controls over each WASI import, and allow their access and scopes to be configured.
It would be great to explore this area of WASI further in future as well as part of the `WASI` virtualization layer in Node.js.
**Conclusion**:
* Better fine-grained capability options for the `WASI` instance would be interesting to explore, both for preview2 and preview1.
## Specifying WASI Worlds
WASI consists of multiple separate worlds over a range of different platform features.
When creating the WASI instance in Node.js, Node.js needs to determine which of the worlds it should implement by default and which may be able to be conditionally enabled or disabled.
The base level of support to focus on will likely be the CLI world - https://github.com/WebAssembly/wasi-cli.
Other WASI proposals if implemented natively in Node.js might then be conditionally enabled with feature flags.
**Conclusion**:
* CLI world as the main focus
* Other worlds could be enabled via feature flags
## Conclusion
Overall, I believe we put Node.js in a great position for preview2 support by extending the existing `WASI` API to act as a preview2 virtualization interface, while continuing to support preview1.
There will certainly be various implementation complexities that arise, but I believe they can be managed in a coherent way.