Serverless-first. Built for cold starts, tight memory, and one binary.Deploy guide →

Compatibility

Fairyfly targets web-standard APIs (fetch, URL, WebSocket, Headers) with a deliberately small Node-shaped subset (process, fs, console). This page is the gap list.

What is present

Every global in Object.getOwnPropertyNames(globalThis):

Web standard: fetch · Request · Response · Headers · URL · URLSearchParams · Blob · FormData · TextEncoder · TextDecoder · WebSocket · crypto (randomUUID, getRandomValues, subtle.digest) · btoa · atob · performance · queueMicrotask · DOMException

Fairyfly: http · fs · process · child_process · Database · sql · SQL · Worker · ffi · console

Engine (QuickJS-ng): Promise · Map/Set/WeakMap/WeakSet · Proxy · Reflect · Symbol · Iterator · Atomics · SharedArrayBuffer · FinalizationRegistry · WeakRef · AggregateError · DisposableStack/AsyncDisposableStack · Float16Array · BigInt · escape/unescape · eval

Missing vs Node

Node APIStatusUse instead
BufferabsentUint8Array, ArrayBuffer
require() / module.exportsabsentimport / export (ESM only)
__dirname / __filenameabsentnew URL(".", import.meta.url)
process.nextTickabsentqueueMicrotask(fn)
process.stdout.writeabsentconsole.log → stderr
process.exitCodeabsentprocess.exit(n)
process.on("exit"…)absentnothing to subscribe to
setImmediateabsentsetTimeout(fn, 0)
structuredCloneabsent as globalclone via postMessage
path / os / util / eventsabsentstring ops, URL, arrays
streamabsentarrays, Response.text()
child_processpartialspawn / exec / execSync — no streams, stdin, or fork
net / tls / http modulesabsentglobal http.serve (+ TLS)
worker_threadsabsentglobal Worker
zlibabsentcrypto.subtle.digest won't help — use ffi
fs.promisesabsentfs.readFileAsync() and friends
crypto.createHash / createHmacabsentcrypto.subtle.digest
AbortController / signalabsentnot supported by fetch
setTimeout().unref() (Node Timer)partialreturns a Timeout object: t.unref(), t.refresh(), t.hasRef()
DOMExceptionpresent(QuickJS builtin)

Missing vs browsers

Browser APIStatusUse instead
window, document, DOMabsentbackend runtime
addEventListenerabsenton… properties (ws.onmessage = …)
localStorage / sessionStorageabsentfs or SQLite
ReadableStream / WritableStreamabsentarrayBuffer() / text()
TextEncoder#encodeIntoabsentencode()
EventSource (SSE)absentraw http.serve with content-type: text/event-stream
structuredCloneabsentpostMessage
URL.canParse / URL.parsepresent
Response.json / Response.redirectpresent
fetch signalabsenttimeouts: Promise.race

Behavioural deviations

These are the ones that bite during a port:

AreaDeviation
console.*Writes to stderr; all arguments printed; warn is yellow, error red — non-standard colours
Unhandled rejectionsSilent, exit 0 (Node exits 1)
Sync fs failuresReturn undefined, never throw — the error is lost
fetch rejectionRejects with a string ("Network error"), not an Error
btoaNon-Latin-1 input does not throw — it UTF-8-encodes first (btoa("😀") → "8J+YgA==")
atobThrows TypeError: atob: invalid base64 input
Headers.size / URLSearchParams.sizeMethods — h.size(), not h.size
Headers.getAll(name)Non-standard; returns string[]
TextDecoderAlways lossy (fatal: true ignored); labels ignored — always UTF-8
performance.now()Origin is the first call, not navigation/start
process.exit(300)Clamps to 255; -5 → 0; non-numeric → 0
Uncaught error formatError: TypeError: msg + stack, exit 1
setTimeout(fn, …)Returns a Timeout object, not a number
WebSocketSingle on… handler per event; no addEventListener
Worker filesEvaluated as classic scripts — import/export throws
child_processNo streams/events — use wait() + captured stdout/stderr; children are not killed on exit

Porting checklist

- const chunks = [];
- res.on("data", (c) => chunks.push(c));
- res.on("end", () => Buffer.concat(chunks));
+ const buf = await res.bytes();

- process.stdout.write("hello");
+ console.log("hello");              // stderr — redirect explicitly

- try { fs.readFileSync(p); } catch { … }
+ if (fs.exists(p)) fs.readFileSync(p);   // sync fs never throws

- require("./util")
+ import { helper } from "./util.js";     // extensions are probed

- process.nextTick(fn)
+ queueMicrotask(fn)

See also