x

Node.js backend compatibility layer for moonbitlang/async in MoonBit, with native delegation and JS FFI implementations for process, fs, http, gzip, tls, socket, raw_fd, signal, sync primitives, websocket, stdio, pipe, and sys.

moonbit
async
backend
compatibility
nodejs
process
filesystem
http
gzip
tls
socket
tcp
udp
signal
raw-fd
queue
semaphore
websocket
wasi
moon add mizchi/x@0.5.3
Download zip
Author
Version
0.5.3
License
Apache-2.0
Last updated
16 hours ago
Downloads
186K
README

#mizchi/x

Node.js backend compatibility layer for moonbitlang/async in MoonBit.

mizchi/x keeps native behavior by delegating to moonbitlang/async, and provides JS FFI implementations so the same async-style code can run on --target js (Node.js) with minimal changes.

#Primary Goal

  • Run code written against moonbitlang/async on Node.js (--target js).
  • Keep native (--target native) semantics close by forwarding to upstream implementations.

  • mizchi/x: API is aligned with moonbitlang/async contracts (native-compatible surface).
  • mizchi/js/node: API is aligned with raw Node.js/JavaScript style APIs.

#Dependencies

DependencyVersion
moonbitlang/async0.19.0
moonbitlang/x0.4.43

#Packages

PackageDescription
mizchi/x/processCommand execution (run, spawn, spawn_orphan, wait_pid, process pipes, file/stdio redirects, native pipe redirects, collect_output, collect_stdout, collect_stderr, collect_output_merged)
mizchi/x/fsFile system operations (open, create, File, read_file, write_file, tmpdir, walk, exists, mkdir, readdir, opendir, Directory::next, rename, remove, rmdir)
mizchi/x/httpHTTP client/server (get, post, put, get_stream, post_stream, put_stream, Client, Server, Cookie)
mizchi/x/gzipGzip stream encoder/decoder (Encoder, Decoder)
mizchi/x/tlsTLS client/server streams (Tls::client, Tls::server_from_pair, peer certificate, channel binding, rand_bytes, sha1)
mizchi/x/socketTCP/UDP sockets (Addr, Tcp, TcpServer, UdpClient, UdpServer)
mizchi/x/signalSignal constants and global cancellation signal configuration (Signal, to_int, set_global_cancellation_signals)
mizchi/x/raw_fdRaw file descriptor reads/writes (RawFd::read, RawFd::write, RawFd::close)
mizchi/x/aqueueAsync queue (Queue, Kind, put, get, try_put, try_get, close)
mizchi/x/cond_varAsync condition variable (Cond::wait, Cond::signal, Cond::broadcast)
mizchi/x/semaphoreAsync semaphore (Semaphore::acquire, release, try_acquire)
mizchi/x/websocketWebSocket client (connect, Conn::send_text, Conn::send_binary, Conn::recv, Conn::close)
mizchi/x/stdioStandard I/O (stdin, stdout, stderr with @io.Reader/@io.Writer)
mizchi/x/pipeIn-memory pipes (pipe()PipeRead/PipeWrite with @io.Reader/@io.Writer; native process redirect support)
mizchi/x/sysEnvironment variables and CLI args (get_env_var, get_cli_args, exit)

#Platform Support

Packagenativejswasmwasm-gc
processYesYesstubstub
fsYesYesWASI P1stub
httpYesYesstubstub
gzipYesYesYesYes
tlsYesYesstubstub
socketTCP/UDPTCP/UDPstubstub
signalYesconstants/no-opconstants/no-opconstants/no-op
raw_fdYesYesstubstub
aqueueYesYesYesYes
cond_varYesYesYesYes
semaphoreYesYesYesYes
websocketYesYesstubstub
stdioYesYesstubstub
pipeYesYesstubstub
sysYesYesYesstub

  • Yes — Full implementation.
  • stub — Compiles but aborts at runtime with "not supported" message.
  • WASI P1 — Partial implementation using WASI Preview 1 syscalls.

#Upstream Compatibility

Compatibility with moonbitlang/async 0.19.0 / moonbitlang/x 0.4.43.

The wrapper re-exports upstream types and APIs with matching signatures. On native, each function delegates directly to the upstream implementation. On JS, equivalent behavior is provided via extern "js" FFI (Node.js).

#Extension Notes

mizchi/x/fs now exposes the upstream-style File API (open, create, stream @io.Reader/@io.Writer, random access, size, timestamps, sync, tmpdir, and walk) on native and Node.js. Native delegates to moonbitlang/async/fs; JS maps to node:fs/promises. JS file locking is not supported because Node.js has no standard advisory file-locking API.

mizchi/x/socket currently covers TCP and UDP on native and Node.js. Native delegates to moonbitlang/async/socket; JS maps TCP to node:net and UDP to node:dgram. Node.js does not expose stable OS file descriptors for these sockets, so fd() returns -1 on JS. TCP connect/accept/run_forever and UDP unicast roundtrips are covered by automated tests. UDP multicast helpers are mapped to Node where available, but multicast is not covered by automated tests.

mizchi/x/process exposes async 0.19 process inputs/outputs, file redirection, spawn, spawn_orphan, wait_pid, Process::wait, Process::try_wait, and cancellation handlers. Native delegates to moonbitlang/async/process; JS maps to node:child_process.

mizchi/x/http includes async 0.19 response cookies and upstream-style streaming requests: get_stream returns a Client, while post_stream/put_stream return a writable Client whose response is obtained with end_request(). Native supports upstream proxy/trust options; JS returns NotSupported for proxy clients and custom TLS trust.

mizchi/x/gzip mirrors moonbitlang/async/gzip and works across native, JS, wasm, and wasm-gc via the upstream stream encoder/decoder.

mizchi/x/tls mirrors moonbitlang/async/tls. Native delegates to the upstream OpenSSL/Schannel implementation. JS maps to Node.js node:tls over any @io.Reader/@io.Writer pair and supports client/server handshakes, graceful shutdown, peer certificate access, tls-unique/tls-server-end-point style channel bindings, rand_bytes, and sha1. WASM targets compile as stubs.

mizchi/x/signal mirrors the async signal constants on native and provides portable numeric constants on JS/wasm targets. Global cancellation signal setup delegates to upstream on native and is a no-op on JS/wasm.

mizchi/x/raw_fd owns an existing OS file descriptor and provides single read/write operations. Native uses a small C FFI shim; JS maps to Node.js fs.read/fs.write/fs.closeSync. WASM targets compile as stubs.

mizchi/x/aqueue, mizchi/x/cond_var, and mizchi/x/semaphore are thin wrappers around the async synchronization primitives, keeping the same high-level API available from this compatibility module.

#http (moonbitlang/async/http)

Upstream tests: 47 | Covered: 31 | Skipped: 16

Upstream testStatusNotes
http requestCovered
https requestCovered
passthrough modeCovered
passthrough mode remaining dataCoverednative
Parser/body/cookie/gzip/sender edge casesCoveredhttp_upstream_test.mbt mirrors applicable upstream cases
request streamingCoverednative and JS
Other 16 testsSkipInternal parser/sender/proxy tests

#websocket (moonbitlang/async/websocket)

Upstream tests: 24 | Covered: 1 | Skipped: 23

Most upstream tests are internal (frame handling, ping/pong, UTF-8 validation) and not applicable to the wrapper's high-level API. CloseCode conversions is covered directly; the wrapper also has independent tests covering its own API.

#pipe (moonbitlang/async/pipe)

Upstream tests: 3 | Covered: 3 | Skipped: 0

All upstream pipe tests are fully covered.

#stdio (moonbitlang/async/stdio)

Upstream tests: 3 | Covered: 3 | Skipped: 0

All upstream stdio/process redirect tests are covered on native and JS.

#tls (moonbitlang/async/tls)

Upstream tests: 9 | Covered: 9 | Skipped: 0

Upstream testStatusNotes
one wayCoverednative and JS
echoCoverednative and JS
`connect` accidental close Coverednative and JS
`read` already closed Coverednative and JS
client custom root certificateCoverednative and JS
client custom root certificate rejects different rootCoverednative and JS
get_peer_certificateCoverednative and JS
channel bindingCoverednative and JS
peer close connectionCoverednative and JS

#Quick Start

just # check + test just fmt # format code just check # type check just test # run tests

#Upstream test coverage by package

Coverage is verified by scripts/sync-upstream-tests.sh, which extracts test names from the upstream packages and checks that each is either covered by a wrapper test or explicitly excluded in scripts/upstream-exclude.conf.

#process (moonbitlang/async/process)

Upstream tests: 28 | Covered: 26 | Skipped: 2

Upstream testStatusNotes
basic waitCovered
basic_lsCovered
collect_stdoutCovered
collect_errorCovered
collect_outputCovered
collect_output blockedCovered
collect_output_mergedCovered
wait exitcodeCovered
set cwdCovered
set_envCovered
set_env no inheritCovered
wait_pidCovered
basic_catCovered
cancel processCovered
cancel process hardCovered
cancel process timeoutCovered
orphan processCovered
spawn_in_group waitCovered
spawn_in_group cancelCovered
Process::waitCovered
Process::try_waitCovered
Process:cancelCovered
merge stdout and stderrCovered
merge multipleCovered
redirect to fileCovered
kill children on hard cancelSkiprequires custom test program + Windows
do not kill orphan children on hard cancelCovered
windows command line arg escapeSkipWindows-specific

#HTTP Server Operations (Node.js)

#Reverse proxy / TLS termination

When running @http.Server behind a reverse proxy (Nginx/Caddy/ALB), terminate TLS at the proxy and forward plain HTTP to the MoonBit process.

  • Enable proxy-aware client address resolution with:
    • @http.Server::new(..., trust_proxy=true)
  • Current JS behavior:
    • client_addr() uses x-forwarded-for (first hop) and x-forwarded-port
    • falls back to x-forwarded-host port when x-forwarded-port is absent

Security note: set trust_proxy=true only when requests are guaranteed to come from trusted proxy hops.

#SSE shutdown benchmark

Run this JS-only benchmark test to check concurrent SSE shutdown behavior:

moon test --target js src/http --filter 'bench(js): graceful close time under concurrent sse'

The test prints a measurement line like:

bench(js): sse_clients=24 graceful_close_ms=1507

#k6 benchmark (default: JS vs Native)

Use the dedicated benchmark server package and k6 scenarios. By default, this runs both js and native targets and prints a comparison summary.

./scripts/k6/run_http_bench.sh

Environment variables:

  • PORT (default 18080)
  • VUS (default 128)
  • DURATION (default 20s)
  • BODY_SIZE (default 16384) for POST /consume
  • TARGETS (default "js native")
    • e.g. TARGETS=js to run JS only
    • e.g. TARGETS=native to run Native only

Individual scenarios:

BASE_URL=http://127.0.0.1:18080 k6 run scripts/k6/http_ping.js BASE_URL=http://127.0.0.1:18080 BODY_SIZE=16384 k6 run scripts/k6/http_consume.js BASE_URL=http://127.0.0.1:18080 BODY_SIZE=16384 k6 run scripts/k6/http_discard.js

#License

Apache-2.0