Node.js Compatibility
Node.js feature support and usage limitations at a glance.
OneScript scripts run in WKWebView. Node.js polyfills aim to reproduce the APIs and behavior of the Node.js runtime as closely as possible. The compatibility baseline is Node.js v24.21.0.
Feature support is listed below.
| Node.js feature | Support status | Notes |
|---|---|---|
assert / assert/strict | Supported | Uses the Node.js v24.21.0 lib/assert.js source directly, including diffs, CallTracker, and partial deep comparisons. Messages for assert(value) quote the failing expression when the source line is available. |
buffer | Supported | Uses the Node.js v24.21.0 lib/buffer.js source with a ported native binding: all encodings, numeric read/write methods, transcode, isUtf8, isAscii, atob / btoa. Covered by the official test-buffer-* suite (see the official-test baseline). |
Blob / File / FormData | Partial | Uses native WebKit files, forms, Blob.stream(), and body consumption. Older systems receive a bytes() fallback using arrayBuffer(). Exact behavior depends on the system WebKit version. |
fetch | Partial | Uses native Web objects and URLSession for cross-origin HTTP(S). Supports response streams, SSE, explicit ReadableStream uploads, and in-flight cancellation. Ordinary bodies are natively encoded to determine Content-Length; replay copies are kept in memory. HTTP integrity verification reports an explicit unsupported-feature error. Cache, response-header semantics, and version differences remain documented in official-test results. Small text/plain responses may encounter initial system buffering on iOS 18.2. |
events | Supported | Uses the Node.js v24.21.0 lib/events.js source, including error-event semantics, once, on, getEventListeners, and EventEmitterAsyncResource. Global EventTarget, Event, CustomEvent, AbortController, and AbortSignal are Node.js implementations; signals passed to native fetch / Request are bridged automatically. |
fs / fs/promises | Partial | API gaps. Promise, callback, and synchronous (*Sync) APIs cover readFile, writeFile, appendFile, readdir (including withFileTypes), mkdir, mkdtemp, rename, copyFile, rm, rmdir, unlink, stat, lstat, access, realpath, and truncate, plus existsSync. File descriptors are supported through open, read, write, writev, fstat, ftruncate, fsync, and close, and fs.promises.open returns a FileHandle. createReadStream and createWriteStream use the Node.js stream implementation and read and write in chunks. fs.constants, Stats, and Dirent are available, and errors carry Node.js code, errno, syscall, and path. watch, opendir, symbolic link creation, and permission and timestamp changes (chmod, utimes) are not available yet. Relative paths resolve against the project directory (process.cwd()); use the project's persistent data directory for data that must survive rebuilds. System and other apps’ data remain protected by the iOS sandbox. |
http | Supported | Uses the Node.js v24.21.0 lib/http.js and _http_* sources; both server and client run on net: createServer, Agent (keep-alive connection reuse), chunked encoding, pipelining, trailers, 100-continue, upgrade, CONNECT, headersTimeout / requestTimeout / keepAliveTimeout, maxHeaderSize, and insecureHTTPParser behave as in Node.js. The HTTP parser is WebAssembly compiled from the same Node.js version's deps/llhttp and matches the semantics of Node.js's internalBinding('http_parser'); when the system disables WebAssembly (for example in Lockdown Mode), using http throws ERR_FEATURE_UNAVAILABLE_ON_PLATFORM. Covered by the official test-http-* tests (see the official test baseline). Command-line options (such as --max-http-header-size and --insecure-http-parser) cannot be set and keep their defaults; use the corresponding API options instead. Global fetch still uses URLSession and can reach the script's own server; http.WebSocket and related classes are the platform's native globals. |
https | Supported | Uses the Node.js v24.21.0 lib/https.js on top of http and tls: createServer, request / get, keep-alive Agent with session reuse, ca / checkServerIdentity / rejectUnauthorized, and NODE_TLS_REJECT_UNAUTHORIZED behave as in Node.js. Covered by the official test-https-* tests. Same limits as tls. Global fetch still uses URLSession (with the system trust store) and is unaffected by https.globalAgent. |
os | Supported | Uses the Node.js v24.21.0 lib/os.js source. Host name, kernel version, CPU times, memory, network interfaces, and user info come from the device. homedir() / tmpdir() follow HOME / TMPDIR: HOME defaults to the project's persistent data directory, and TMPDIR to the project's temporary directory. getPriority / setPriority are not yet available. |
path | Supported | Uses the Node.js v24.21.0 lib/path.js source, including path.win32 and path.posix. path.matchesGlob requires the file-system glob implementation and is not yet available. |
process | Partial | Node.js process object built from the v24.21.0 bootstrap: nextTick, exitCode, beforeExit / exit events, hrtime, memoryUsage, emitWarning, uncaught-exception and unhandled-rejection modes. process.env includes the project's environment variables. A script stays alive while referenced timers or pending I/O exist, like Node.js. stdout / stderr are writable streams forwarded to the console. Signals are delivered only to the script itself; process.report and chdir are not yet available. |
querystring | Supported | Uses the Node.js v24.21.0 lib/querystring.js source. |
sqlite | Supported | Ports the JavaScript-facing part of Node.js v24.21.0 src/node_sqlite.cc. SQL runs on a native thread for each JS thread, using the same SQLite version Node.js pins (3.53.4), built into the app. Includes DatabaseSync, StatementSync (run / get / all / iterate, named parameters, readBigInts), user-defined and aggregate functions, setAuthorizer, sessions and changesets, backup(), serialize() / deserialize(), limits, and createTagStore(). Relative database paths (including ATTACH, URIs, and VACUUM INTO) resolve against the project directory; keep databases that must survive rebuilds in the project's persistent data directory. :memory: and temporary databases are always available. Covered by the official test-sqlite-* tests (12 of 18 pass; the rest differ in garbage-collection timing, the --no-experimental-sqlite flag, and one data-type comparison). Limits: loadExtension() throws ERR_FEATURE_UNAVAILABLE_ON_PLATFORM because iOS cannot load unsigned native code. As in Node.js, it can only be imported with the node: prefix. |
stream | Supported | Uses the Node.js v24.21.0 stream implementation (Readable, Writable, Duplex, Transform, pipeline, finished, compose, stream/promises, stream/consumers, async iteration and backpressure). Web-stream adapters (toWeb / fromWeb) are not yet available. |
string_decoder | Supported | Uses the Node.js v24.21.0 source with a ported incremental decoder binding. |
timers | Supported | Uses the Node.js v24.21.0 timer implementation: Timeout / Immediate objects, ref / unref, refresh, and timers/promises. The event loop follows Node.js phase order (timers → I/O → check → close) and nextTick / microtask ordering. |
url | Supported | Uses the Node.js v24.21.0 lib/url.js source (legacy url.parse / format / resolve, fileURLToPath, pathToFileURL, domainToASCII). The URL / URLSearchParams classes are WebKit’s native WHATWG implementation. URLPattern depends on system support. |
util | Supported | Uses the Node.js v24.21.0 lib/util.js source, including inspect, format, parseArgs, styleText, MIMEType, parseEnv, and types. JavaScriptCore does not expose promise state, proxy internals, or iterator previews, so inspect shows promises as <pending> and cannot reveal proxies. |
async_hooks | Partial | Uses the Node.js v24.21.0 source. AsyncResource, executionAsyncId, and createHook work for resources created by the runtime; JavaScriptCore has no promise hooks, so promise events do not fire and AsyncLocalStorage context is not propagated across await. |
diagnostics_channel / console / domain / punycode | Supported | Use the Node.js v24.21.0 sources. Console output keeps Node.js formatting (console.table, groups, counters, timers) and forwards to the OneScript console. |
zlib | Partial | Implementation gap; the compression adapter can be extended. Provides only deflate, inflate, and synchronous variants. gzip, Brotli, Zstd, streaming compression, and Node.js compression options require corresponding algorithms and API adaptations. |
crypto | Partial | Coverage depends on the compatibility layer and backend; further adaptation is possible. OpenSSL 3.5.8 backs hashing, randomness, derivation, encryption, keys, signatures, DH/ECDH, KEM, and certificates. Streaming objects inherit current stream limitations; secureHeapUsed() returns zero placeholders. Algorithms depend on the bundled backend; see the separate FIPS and Engine entries below. |
| WebCrypto | Partial | Implementation gap; the crypto adapter can be extended. Global crypto, keys, digests, signatures, derivation, and selected encryption operations are available. Recognized algorithm names and implemented operations have different coverage; AES-KW key wrapping, for example, is yet to be connected. Algorithms, operations, and parameters need individual adaptation to the compatibility layer and backend. |
| FIPS | Unsupported | The current build has no configured FIPS Provider. getFips() returns 0; setFips() throws ERR_FEATURE_UNAVAILABLE_ON_PLATFORM. FIPS mode requires an appropriate Provider under the OpenSSL FIPS requirements and assessment of its validation scope, version, and operating environment. Adding JavaScript APIs alone cannot establish FIPS compliance. |
| Dynamic OpenSSL Engine loading | Unsupported | Dynamic module loading is disabled in the current backend build. setEngine() throws ERR_FEATURE_UNAVAILABLE_ON_PLATFORM. Support requires native build and adapter changes; loading external libraries also faces iOS code-signing constraints. Specific implementations require porting, signing, and app integration. |
child_process | Unsupported | Current iOS app sandbox and runtime architecture constraints. spawn / exec require launching OS executables; fork requires another Node.js process. Execution permissions and sandbox constraints in an ordinary iOS app prevent reproducing this arbitrary-child-process model through polyfills. Importing currently throws ERR_FEATURE_UNAVAILABLE_ON_PLATFORM. For multi-process servers, use cluster (child processes emulated with threads) or worker_threads. |
cluster | Supported | Uses the Node.js v24.21.0 lib/cluster.js and internal/cluster/* sources. iOS cannot create processes, so fork() workers are child processes emulated with threads: each has its own event loop, process.env, and process ID (process.pid / process.ppid), and appears to Node.js APIs as the main thread of that process (isMainThread is true). IPC with the primary supports json / advanced serialization and handle passing. Both round-robin (the default) and SCHED_NONE scheduling work: connections and listening sockets are passed between threads by the built-in network core, so multiple workers share one http / net server port. disconnect, kill, exit events, and setupPrimary({ exec, args, silent, stdio, serialization }) match Node.js. Covered by the official test-cluster-* tests. Limits: all workers share one WebContent process with the primary (they exit together if memory runs out); signals are only emulated between the primary and its workers — a worker listening for the signal receives the event, otherwise it ends immediately; cwd, uid / gid, and extra stdio file descriptors are unavailable; UDP (dgram) is unavailable. |
worker_threads | Supported | Uses the Node.js v24.21.0 lib/worker_threads.js and internal/worker* sources. Each Worker is a separate platform Worker thread with its own event loop, process, and native network thread, so CPU-heavy work truly runs in parallel; terminate() stops synchronous infinite loops. Supports file entries (project .ts / .js files are bundled on demand at run time, with import.meta and __filename pointing at the source file), eval code, and data: URLs; workerData, MessageChannel / MessagePort (including transfer), receiveMessageOnPort, BroadcastChannel, SharedArrayBuffer and Atomics, markAsUntransferable, setEnvironmentData, resourceLimits, stdout / stderr / stdin, execArgv (limited to options OneScript supports), process.exit ending only the current thread, and uncaught exceptions emitting error in the parent. v8.serialize / deserialize and structured cloning use the V8 serialization format. Covered by the official test-worker-* and test-message* tests. Limits: SHARE_ENV only copies the environment at creation; while blocked in Atomics.wait, receiveMessageOnPort cannot read messages that have not arrived yet; heap snapshots, CPU / heap profiling, and vm contexts are unavailable; Promise microtasks run at macrotask boundaries. |
net | Supported | Uses the Node.js v24.21.0 lib/net.js source. TCP and Unix domain sockets run on the bundled libuv (the same version as Node.js) on a dedicated IO thread: listen() without an address binds :: (dual stack), plus allowHalfOpen, autoSelectFamily, setTimeout, setNoDelay / setKeepAlive, resetAndDestroy, BlockList, and SocketAddress, with Node.js error codes. Covered by the official test-net-* tests (see the official test baseline). Limits: file descriptors are not exposed ({ fd } and net.Socket({ handle }) are unavailable); Unix socket paths resolve against process.cwd() to absolute paths of at most 103 bytes (iOS container paths are long, so choose short paths); reaching LAN devices requires the user to grant Local Network access; the system may interrupt connections when the app moves to the background. As in Node.js, the relative order of events from different sockets depends on kernel timing. |
tls | Supported | Uses the Node.js v24.21.0 lib/tls.js and internal/tls/* sources. The TLS engine is WebAssembly compiled from the same Node.js version's deps/openssl (OpenSSL 3.5) and deps/ncrypto, with SecureContext and TLSWrap ported from crypto_context.cc / crypto_tls.cc; it runs on the script thread and does not count against the memory limit of widget and Shortcuts extensions. It loads (about 2.8 MB) the first time TLS is used. TLS 1.2 / 1.3, ALPN and ALPNCallback, SNICallback, session resumption and tickets, client certificates, PSK, OCSP, keylog, exportKeyingMaterial, and setDefaultCACertificates are supported with Node.js error codes; the default CA set is the Node.js bundled root certificates (tls.rootCertificates). Covered by the official test-tls-* tests. Limits: the system trust store (--use-system-ca) and NODE_EXTRA_CA_CERTS are unavailable; OpenSSL engines and certificate compression (certificateCompression) are unavailable; encryption throughput is lower than native Node.js; when the system disables WebAssembly (for example in Lockdown Mode), using tls / https throws ERR_FEATURE_UNAVAILABLE_ON_PLATFORM; Promise microtasks run at macrotask boundaries, so work deferred with await inside TLS callbacks takes effect later than in Node.js. |
dns / dns/promises | Partial | lookup and lookupService match Node.js v24.21.0 and resolve through the system getaddrinfo / getnameinfo (honoring VPN and system DNS settings). The c-ares based resolve*, Resolver, and setServers are unavailable and throw ERR_FEATURE_UNAVAILABLE_ON_PLATFORM; getServers() returns an empty array. |
| WebAssembly (WASM) | Supported | Uses WKWebView’s native WebAssembly API to instantiate bytes with WebAssembly.instantiate(bytes) and execute exported functions. Feature availability depends on the iOS / WebKit version. Importing a .wasm file returns its bundled asset path; read the bytes and instantiate the module explicitly. |
Geolocation (navigator.geolocation) | Supported | Implements the Web Geolocation API on Core Location: getCurrentPosition, watchPosition, clearWatch, GeolocationPositionError, and navigator.permissions.query({ name: "geolocation" }). A pending watch keeps the script running until clearWatch. Updates run only while the app is in the foreground. See geolocation for the OneScript API. |
wasi | Unsupported | WASI host imports missing; a restricted subset can be adapted. WASM execution is available. WASI additionally needs imports for file descriptors, filesystems, clocks, and randomness. JavaScript and authorized system capabilities can supply these within sandbox permissions; a browser WASI subset implementation demonstrates the approach. Importing currently throws ERR_FEATURE_UNAVAILABLE_ON_PLATFORM. |
Native addons (.node / node-gyp) | Unsupported | Current ABI, build environment, and code-signing constraints. .node addons depend on Node-API or V8 / Node.js interfaces. The WebView runtime has no corresponding ABI or loader, and the device environment has no node-gyp toolchain. Arbitrarily downloaded native binaries also face iOS signing restrictions; individual libraries require iOS porting, signing, and integration into the app. |
Note: “Supported” means the functionality described in the notes is implemented. “Partial” means an API subset is available or behavior and platform limitations apply. “Unsupported” means the feature is unavailable in the current runtime. “Can be extended” describes a technical implementation path; availability is determined by the current release. Platform constraints refer to the sandbox and signing permissions of an ordinary iOS app.
Built-in modules support standard Node.js imports, such as import … from 'node:crypto' and require('crypto'). We recommend explicitly using the node: prefix.
How is this guide?
Last updated on