Limitations & gotchas
An honest list of what NodeAmiga doesn't do (yet) and where it differs from Node.js running on a modern PC. Read this before porting something and wondering why it misbehaves.
On this page
Language features not supported
| Feature | Status / workaround |
|---|---|
Proxy |
Not implemented. Use a plain object + explicit getter/setter methods. |
Reflect |
Not implemented. Use Object.* statics. |
Spec-compliant with statement |
Parses, but behaves as if the with wrapper doesn't exist — body runs in the current scope. |
| Real async/await concurrency | await spin-waits the event loop synchronously. Execution of other tasks while a Promise is pending doesn't really interleave — the awaiting function just sits there draining microtasks/timers. |
| Tail-call optimization | Not implemented; deep recursion hits the 512-frame call-stack limit (RangeError). |
| Named regex capture groups | Syntax (?<name>...) parses (the name is consumed) but the name mapping isn't exposed on matches — captures are still indexed numerically. |
| UTF-16 surrogate pairs in some spots | String.fromCharCode(cp) for cp > 0xFFFF produces only the low byte. The parser handles "\u{1F600}" correctly. punycode.ucs2 and string_decoder also don't handle surrogate pairs. |
Private class fields with # |
Parses as regular identifiers — no actual access restriction. Convention only. |
| Decorators | Not implemented (not finalized in ES anyway). |
| Web Workers / threads | AmigaOS is single-tasking per process in the Node sense. Use child_process+execSync for sub-processes. |
Performance
Not built for speed on a stock A500
On a 7 MHz 68000 with software-emulated floats, a tight loop that does trig will be orders of magnitude slower than modern Node.js. NodeAmiga is usable on an A500 for CLI tools, text processing, and small GUIs — but not for anything CPU-bound. Accelerators (68030+, 68040, 68060, Vampire) make a dramatic difference because floats hit the FPU.
- Array.prototype.sort is O(n²) bubble sort. Fast enough for a few hundred items; sluggish around 10,000; don't try it on 100k. Implement a faster sort in JS if you need one.
- Typed-array sort is O(n²) insertion sort (same concern).
- Object keys and Map/Set lookups are O(n) (linear). Fine for small collections. For large (thousands+) use a plain object with many keys, which has a 32-bucket hash table.
- Regex lookbehind is O(pos) — tries every earlier position. Avoid it on long strings.
- Regex catastrophic backtracking is capped at 100,000 steps to avoid hanging. Pathological patterns silently return "no match".
- zlib.deflate doesn't compress — the encoder emits "stored" DEFLATE blocks. Output is larger than input. Inflate (reading gzip/deflate) works correctly.
Memory model
- Reference counting, not garbage collection. Cycles that don't go through the environment won't be broken — e.g. two objects holding references to each other never get collected. Keep your data structures acyclic (or explicit-null after use) for long-running programs.
- Buffer bytes live outside the JS heap and are freed in a cascade pass at process exit, not when the Buffer object becomes unreachable. If you allocate millions of Buffers in a loop, clear references but still expect steady memory use until exit.
- Intuition window titles malloc'd and freed in
win.close(). Always callclose()explicitly — leaving a window open until process exit leaks the title buffer (negligible; small amounts). - Call stack limit: 512 frames. Deeply recursive code throws
RangeError: Maximum call stack size exceeded. - NodeAmiga allocates its own 65 KB task stack via
AllocMem+StackSwapat startup; you don't need to adjust the shell's stack. - REPL holds on to up to 256 AST arenas. Long REPL sessions (>256 lines) may lose closures to cleanup after this limit is reached; restart if you notice oddities.
Timers & async
- setTimeout / setInterval have 1-second resolution via
time(NULL)*1000.setTimeout(fn, 50)may fire at 0 ms or ≥ 1000 ms — nothing in between. Useperformance.now()if you need fine-grained timing. - No ordering guarantee between timers scheduled with the same delay — they're stored in a simple linked list.
awaittimeout: ~100 seconds. If an awaited Promise never settles within about 100 seconds,awaitthrows anError(as of 0.21.0; earlier silently returned undefined).- Promise.all / race / any are simplified — they only inspect immediate state. A promise still pending when the function runs is treated as resolved-undefined (all/race) or skipped (any). Fine for short fire-and-forget flows; not spec-compliant.
- Microtask errors are swallowed — an uncaught throw in a microtask callback gets cleared so the next microtask can run, but the error doesn't surface. Wrap callbacks you care about in try/catch.
Networking
- TCP/IP stack is optional. Without
bsdsocket.library, any HTTP/DNS call fails at module load. Install Miami / Roadshow / AmiTCP. - HTTPS needs AmiSSL. Built-in or plugin depending on your NodeAmiga binary. Test with
http.hasSSL(). - Redirects capped at 5 hops. After that, the last response (usually 3xx) is returned as-is.
- HTTP/1.0 only,
Connection: close. No keep-alive, no persistent connections. For high request rates use a custom FFI-based client. - No chunked transfer encoding. HTTP/1.1 servers that respond with
Transfer-Encoding: chunkedwill fail to parse. Most will fall back to HTTP/1.0 withContent-Length. - Socket timeout: 10 seconds, not configurable. Slow servers will fail.
- Server is non-blocking but simple: up to 8 concurrent clients, request parsed as a single buffer (no streaming),
Ctrl+Cstops the loop via AmigaOS signals. - DNS is synchronous — even the callback-style APIs invoke the callback immediately.
Text & encoding
- Default encoding is effectively ISO-8859-1 for everything Amiga — filenames, CLI arguments, console output. UTF-8 works if both sides agree (e.g. in your JS source file and on stdout), but AmigaOS itself doesn't treat bytes as Unicode.
- No locale / Intl support.
String.prototype.localeCompareis a plainstrcmp.Date.toLocaleStringreturns the same astoString. NoIntl.NumberFormatetc. - Source-code
\u{...}escapes produce correct UTF-8 multi-byte sequences.String.fromCharCodedoes not. - CSI (0x9B) is a valid byte in strings on AmigaOS — don't try to display untrusted strings in the Shell without filtering them (they can move the cursor).
Module-specific differences
| Module | Where it differs from Node.js |
|---|---|
fs | Sync-first; async versions call synchronously. Read streams read the whole file up front (highWaterMark only sets chunk size for emission). |
http | HTTP/1.0 only; no chunked encoding; 10s timeout; servers max 8 clients. |
net | Currently a thin alias over http. No net.Socket / net.Server classes. |
zlib | deflate doesn't compress (stored blocks). Decoder is fine. |
crypto | Only md5 and sha256. No HMAC, no ciphers, no key exchange. Encoding is hex only. |
stream | Complete API, but implemented in JS on top of EventEmitter — not backed by native buffers. Readable.push in flowing mode emits synchronously. |
events | emit passes at most 5 arguments to listeners. once wrappers don't preserve this. |
os | totalmem() == freemem(). uptime() returns 0. networkInterfaces() returns {}. CPU speed is 0. |
assert | deepEqual is as strict as deepStrictEqual. Array/object replacers in JSON aren't supported. |
url | new URL(...) doesn't throw on invalid input (parses permissively). URLSearchParams.keys/values/entries return arrays, not iterators. |
child_process | Only exec and execSync. No spawn, no pipes, no fork. stderr is always empty. |
dns | Only IPv4. All methods are synchronous internally. |
readline | No line-editing / history in the module itself. |
punycode, string_decoder | Don't handle UTF-16 surrogate pairs. |
AmigaOS-specific quirks
- Paths use colons for volumes:
SYS:,RAM:,DH0:.path.isAbsolutereturns true for any path containing:, not just leading/. - Forward slash
/means "parent directory" at the start of a path (/foo=../foo). NodeAmiga passes paths through to AmigaDOS, which handles them; be aware if you construct paths manually. - The RAM disk (
RAM:) is extremely fast — use it for caches, temp files, and small databases. But remember it's wiped on reboot. T:is the system temp directory.child_process.execSyncusesT:nodeamiga_exec_tmpfor output capture, so don't modify that file.- Intuition events use AmigaOS key codes (CSI 0x9B for cursor keys), not ANSI. The
intuitionmodule maps the common ones (arrows, F-keys, Help, Esc) to readable event fields. - ARexx ports live in
SysBase->PortList;arexx.listPorts()enumerates all public ports, not just ARexx-compatible ones. - AmiSSL must be installed separately (standard Aminet archive). The NodeAmiga binary includes stubs that probe at startup.
- Small Buffer indices use
sprintfwith 16-char scratch; this imposes a practical limit around 1015 on indices, far beyond any real-world use.
Node.js compatibility summary
If you're porting a Node.js library:
- Good chance it works: pure-JS libraries that use only standard library methods (string / array / regex / JSON), modules of type "parser", "formatter", "algorithm".
events,stream,path,url,querystring,util,assert,buffer— all present. - Might work with minor edits: CLI tools using
fs/http. Watch forrequirepath assumptions (some libs look upnode_modules), HTTP/2, TLS-specific options. - Won't work as-is: anything using Proxy / Reflect, native C++ addons,
worker_threads,cluster, streams in object mode with complex backpressure,fs.watch, WebSocket libraries (no raw TCP client class yet),tlslow-level. - Amiga-specific libraries (
gui,intuition,clipboard,arexx,amiga,iff) have no Node equivalents, so your code that uses them runs only on NodeAmiga.
Reporting issues
Found a bug or missing feature that bites you? Drop a note to the author at biuro@cdlabel.info. This list grows and shrinks with every release.