x

Cross-platform MoonBit compatibility facades with native delegation and accelerated JS implementations for async IO, bytes, regexp, and JSON.

moonbit
async
backend
compatibility
nodejs
process
filesystem
http
gzip
tls
socket
tcp
udp
signal
raw-fd
queue
semaphore
websocket
wasi
bytes
bytes-view
string-view
array-view
simd
Download zip
Author
Version
0.6.0
License
Apache-2.0
Last updated
13 hours ago
Downloads
191K

#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.20.5
moonbitlang/x0.4.50
moonbitlang/regexp0.3.5
@mizchi/jsimd-moonbit-interop (npm, JS runtime)0.1.0

#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, Watcher, 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/server upgrade (connect, from_http_server, 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)
mizchi/x/regexpRegular expressions mirroring moonbitlang/regexp (compile, Regexp::execute, match_, group_by_name, group_count, group_names, MatchResult::matched/get/groups/results/before/after)
mizchi/x/jsonJSON mirroring moonbitlang/core/json (parse, valid, stringify) over the builtin Json value type
mizchi/x/cryptoHashes / HMAC / hex mirroring moonbitlang/x/crypto (md5, sha1, sha224, sha256, sm3, *_from_iter, hmac, MD5/SHA256/SM3 contexts, bytes_to_hex_string, uints_to_hex_string)
mizchi/x/bytesByte search, reverse search, equality, shortlex comparison, and non-ASCII detection; JS uses @mizchi/jsimd-moonbit-interop
mizchi/x/bytes_viewCopy-free BytesView byte/sequence search, reverse byte search, equality, shortlex comparison, and non-ASCII detection
mizchi/x/string_viewStringView substring/code-unit search, reverse search, and UTF-16 shortlex comparison through native JS string operations
mizchi/x/array_viewByte-specialized ArrayView forward/reverse byte and sequence search through native JS array operations

#Platform Support

Packagenativejswasmwasm-gc
processYesYesYes*stub
fsYesYesYes*stub
httpYesYesYesstub
gzipYesYesYesYes
tlsYesYesYesstub
socketTCP/UDPTCP/UDPTCP/UDPstub
signalYesconstants/no-opYesconstants/no-op
raw_fdYesYesstubstub
aqueueYesYesYesYes
cond_varYesYesYesYes
semaphoreYesYesYesYes
websocketYesYesclientstub
stdioYesYesYesstub
pipeYesYesYesstub
sysYesYesYesYes*
regexpYesYes (FFI)YesYes
jsonYesYes (FFI)YesYes
cryptoYesYesYesYes
bytesYesYes (Wasm SIMD)YesYes
bytes_viewYesYes (Wasm SIMD)YesYes
string_viewYesYes (native UTF-16)YesYes
array_viewYesYes (native byte search)YesYes

  • Yes — Full upstream-backed implementation.
  • Yes* — Implemented, with explicitly unsupported host-specific APIs.
  • client — WebSocket client support; HTTP server upgrades are unavailable on wasm.
  • stub — Compiles but aborts at runtime with "not supported" message.
  • Yes* — Environment variables and arguments use moonbitlang/core/env; process exit remains unavailable on wasm-gc.

On wasm, filesystem, process, socket, TLS, HTTP, WebSocket client, stdio, and pipe operations delegate to moonbitlang/async's WASM host integration and retain the native/JS async signatures. They require capabilities granted by the WASM host (for example, moonrun --policy for filesystem, networking, and process spawning). fs.Watcher, raw_fd, and process groups, tree termination, and signal-based process termination are explicitly unsupported. Opaque WASM handles exposed as public Int file descriptors abort if they cannot be represented without truncation.

#Upstream Compatibility

Compatibility with moonbitlang/async 0.20.5 / moonbitlang/x 0.4.50.

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/regexp mirrors the public surface of moonbitlang/regexp. On native, wasm, and wasm-gc it delegates to the pure-MoonBit engine. On JS it drives the host RegExp through FFI (using the d flag for capture offsets and scanning the source to number named/anonymous groups), returning the same Regexp / MatchResult API. Flag letters match upstream: i (ignore case), m (multiline), s (dot matches newline). The JS backend reports Err::InternalError for any pattern the host rejects, since it cannot classify parse failures as precisely as the native parser.

mizchi/x/json mirrors parse / valid / stringify from moonbitlang/core/json, reusing the builtin Json value type. On native, wasm, and wasm-gc it delegates to the pure-MoonBit implementation. On JS it uses the host JSON.parse / JSON.stringify through FFI and converts to/from Json. max_nesting_depth (default 1024) and escape_slash / indent are honored on both backends; the JS backend maps JSON.parse failures to ParseError::InvalidEof or InvalidChar with a best-effort position. All backends pass the ported moonbitlang/core/json parse suite (json_upstream_test.mbt).

mizchi/x/crypto re-exposes the synchronous hash / HMAC / hex surface of moonbitlang/x/crypto under the mizchi/x namespace, delegating to it on every target. Because moonbitlang/x/crypto is pure MoonBit it already runs on native, wasm, wasm-gc, and JS (including browsers), so the same synchronous signatures hold everywhere with no FFI. The ByteSource / CryptoHasher bounds and the MD5 / SHA256 / SM3 contexts are the upstream types themselves (re-exported via using), so code written against moonbitlang/x/crypto is source-compatible. Note: the Web Crypto crypto.subtle API was intentionally not used — its digest / HMAC are async-only and cannot back this synchronous surface.

mizchi/x/bytes preserves the builtin Bytes contracts while replacing JS scalar hot paths with @mizchi/jsimd-moonbit-interop. The MoonBit JS backend imports this npm ESM package with #module; the interop package owns MoonBit-specific shortlex and reverse-substring semantics and delegates hot paths to @mizchi/jsimd/bytes. jsimd selects native JavaScript for small inputs and copies larger inputs into reusable prebuilt Wasm SIMD scratch memory.

mizchi/x/bytes_view passes the MoonBit JS runtime's { buf, start, end } view directly to the interop package. It creates a zero-copy Uint8Array.subarray and uses the same jsimd Wasm SIMD kernels. Multi-byte reverse search is intentionally omitted because benchmarks showed no improvement over the builtin implementation.

mizchi/x/string_view preserves UTF-16 code-unit offsets and view bounds while using native JavaScript indexOf / lastIndexOf and string comparison. mizchi/x/array_view is byte-specialized and uses native array candidate searches without converting the backing JS Array to Wasm memory. Only measured wins are wrapped; StringView equality, generic FixedArray, and scalar-only ArrayView equality/comparison/non-ASCII scans are intentionally omitted.

The SIMD facade packages are optional. Applications only need the runtime package when they import mizchi/x/bytes, mizchi/x/bytes_view, mizchi/x/string_view, or mizchi/x/array_view on the JS target:

pnpm add @mizchi/jsimd-moonbit-interop@0.1.0

Other mizchi/x packages neither resolve nor load this module, so applications that do not use these facades can leave it uninstalled. CI verifies that case with pnpm install --no-optional and just test-js-without-jsimd.

This repository declares the same exact version as an optionalDependency. During development it overrides that dependency with the adjacent ghq checkout at ../jsimd/packages/moonbit-interop; build that package with just build-moonbit-interop-package in mizchi/jsimd, then run pnpm install here.

#Benchmarks (JS target)

*_bench_wbtest.mbt files compare each package against its upstream engine with moon bench --target js. On native both sit on the same engine, so the numbers there are at parity (confirming the wrapper cost is negligible); the speedups below come from delegating to the host engine on JS. Representative run (Node.js, mean ns/iter, lower is better):

Benchmarkmizchi/x (JS)upstream (pure MoonBit)Speedup
regexp execute (scan + match)~4.9 µs~167 µs~34×
regexp execute + capture~5.1 µs~181 µs~35×
regexp compile~0.95 µs~1.2 µs~1.3×
json valid~26 µs~73 µs~2.8×
json stringify~30 µs~66 µs~2.2×
json parse~67 µs~74 µs~on par
bytes find byte (4 KiB miss)~0.40 µs~1.94 µs~4.9×
bytes equality (equal 4 KiB)~0.35 µs~1.54 µs~4.4×
bytes_view find byte (4 KiB miss)~0.22 µs~1.14 µs~5.1×
bytes_view equality (equal 4 KiB)~0.25 µs~1.49 µs~6.1×
string_view find substring (4 KiB miss)~0.09 µs~3.57 µs~42×
string_view compare (late difference)~0.20 µs~2.94 µs~15×
array_view find sequence (4 KiB miss)~1.03 µs~2.01 µs~2.0×

json parse lands roughly on par rather than faster: JSON.parse is fast, but the dominant cost is rebuilding the MoonBit Json ADT (which the pure-MoonBit parser also pays), so the host parser's edge is largely consumed by marshalling. valid and stringify win clearly because they avoid that rebuild.

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 delegates to the upstream host-backed implementation; wasm-gc remains a stub.

mizchi/x/websocket mirrors the client API and exposes from_http_server for native mizchi/x/http server connections. JS and WASM support the client API; HTTP server upgrades are native-only.

mizchi/x/signal mirrors the async signal constants and delegates global cancellation signal setup to upstream on native and WASM. JS exposes portable numeric constants with a no-op global setup.

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