Roadmap
What is not in Lumen yet, and why. Everything shipped so far is in the
standard library reference, each function with a stability
pill; this page is the other half of that list.
How Lumen grows
The surface grows in explicit, conformance-backed slices: a feature
lands with its checker rule, its emitter, its runtime support, a spec folder and a
conformance case, or it does not land. Names follow the conventional Node and
TypeScript ones so that what you already know keeps working. Each module below
lists the APIs still missing and the concrete reason, which is usually a language
feature the module is waiting on rather than a lack of interest.
Progress is visible on the releases page
and in the issue tracker.
If something here blocks you, say so on an issue: the order is driven by real
programs.
Language-level gaps the modules wait on
| Gap | What it unblocks |
Growable arrays (push, pop, sort) | the remaining Array methods, Buffer.concat, simpler collection code |
A general Stream abstraction | process.stdout-style objects, Console instances, streaming zlib, streaming HTTP bodies, child_process.spawn |
A locking primitive (a Mutex-shaped type) | safe shared state across a concurrent http.createServer and across Worker threads |
| A cross-thread message channel | new Worker(...) with postMessage, worker pools, non-scalar results from Worker.run |
| Async on the wasm target | async/await, timers and Worker.run in the browser playground |
| Signal delivery into user code | process.on('SIGINT', …) and friends |
Standard library, module by module
console
| API | Notes |
console.assert(cond, msg) · console.time(label)/timeEnd(label) · console.count(label)/countReset(label) | real, scoped follow-up (small global timer/counter state in the generated runtime); not attempted in the pass that shipped warn/info/debug/trace |
console.group/groupEnd | needs indentation state threaded through every log call site, a bigger, cross-cutting change |
console.table · console.dir | need structured/tabular object formatting; today's printFormat is a flat per-type format-string lookup |
console.clear | terminal-control-code territory, low value |
Console class / custom-stream constructor (new Console(stdout, stderr)) | needs a Stream abstraction Lumen doesn't have (the same gap blocking process.stdout/stdin/stderr) |
fs
| API | Notes |
fs.promises.* / most other async callback variants (fs.rename, fs.chmod, fs.readdir, ... most of Node's ~54) | fs.readFile/writeFile/appendFile are true async (io_uring, no thread); fs.unlink/mkdir/rmdir/stat are thread-pool-backed (spec 047) -- every other one needs the same thread-pool wrapping, mechanically the same pattern now that it's proven, just not done yet |
fs.watch recursive watching, non-Linux platforms; fs.watchFile/unwatchFile | fs.watch(path, listener) itself and its create/change/rename event-type distinction shipped (Linux, flat, one filename per event); each of these is a real, separate follow-up |
fs.opendirSync(path) · fs.globSync(pattern) | need a directory-iterator class / a real glob algorithm |
fs.statfsSync(path) | filesystem-level stats; platform-specific, low value for now |
fs.openAsBlob(path) | no Blob type in the language |
path
| API | Notes |
path.relative(from, to) | not attempted yet, though path.resolve shipping real cwd access removes what used to be the blocker -- a real, separate follow-up now, not a new gap |
path.matchesGlob(path, pattern) | needs a real glob algorithm |
path.win32 / path.posix / path.toNamespacedPath | Lumen targets POSIX only |
process
| API | Notes |
process.stdout / stdin / stderr | no Stream abstraction in the language |
signal events, process.on(...) | process.kill() can send a signal now, but nothing can receive one back into user code — no event/listener infrastructure yet |
process.nextTick(fn) | needs a design decision about what happens when it's called in a program with no other async machinery running (a plain sync-looking program never starts the event loop today) |
process.resourceUsage() · process.cpuUsage() · process.threadCpuUsage() | no per-thread/per-process CPU-time accounting primitive vetted yet; memoryUsage() covers the highest-value single field |
process.send() / .disconnect() / .channel (IPC) | no persistent child-process channel — child_process.spawnSync is a synchronous one-shot |
process.report.* · .permission.* · .finalization.* | advanced/niche Node-internals surface, out of scope |
process.versions · .release · .config · .features.* | Node-build-metadata specific (V8/OpenSSL/ICU versions); not meaningful here. process.version() itself ships as a distinct Lumen-specific marker |
process.title · .execPath · .argv0 · .mainModule · .dlopen · .execve | niche/process-replacement-level operations |
os
| API | Notes |
os.cpus() | needs a record array parsed from /proc/cpuinfo and /proc/stat; no single-syscall shortcut |
os.networkInterfaces() | needs interface enumeration (a getifaddrs equivalent) |
os.userInfo() | needs a passwd-database lookup, the same libc gap as homedir()'s Node fallback |
os.getPriority() · os.setPriority() | no wrapped primitive available; would need a raw syscall number |
os.constants | low value without the functions that consume it |
crypto
| API | Notes |
crypto.createCipheriv/streaming AEAD | a stateful encrypt/decrypt object is a real, separate shape (associated-data ordering, partial-block buffering) from createHash/createHmac's pure hash accumulator (spec 060); not attempted yet |
crypto.sign()/verify() | asymmetric/keyed crypto, a much larger surface than this milestone's scope |
crypto.pbkdf2() · crypto.scrypt() | deliberately deferred rather than rushed; picking the right default cost parameters deserves its own pass |
| Algorithms beyond md5/sha1/sha256/sha512 | createHash/createHmac (spec 060) cover those four; sha224/sha384/blake2/blake3/etc. are a real, separable expansion once there's a concrete need |
url
| API | Notes |
a real URL class / .searchParams / .toString() | Node's modern API is class-based; the older function-based parse/format fit the static-function stdlib pattern better for v1 |
username/password/auth fields | niche; straightforward to add later |
relative-to-base URL resolution (new URL(relative, base)) | a separate feature from parsing one string |
child_process
| API | Notes |
exec/execSync (shell-string form) | needs shell-quoting/escaping logic to be safe; spawnSync's array-of-args form sidesteps this for v1 |
spawn (async, streaming stdio) | needs the same event-loop integration the async fs trio got, plus a design for streaming stdio |
cwd/env/timeout options | the underlying primitive already supports these; just not exposed as spawnSync parameters yet |
| stdin piping · signal-based exit status | spawnSync's one-shot model doesn't need stdin; no optional-string-or-int union exists yet to represent a signal-based exit |
Array (more)
| API | Notes |
a.push/pop/sort | needs growable arrays |
http
| API | Notes |
| Idle keep-alive connection timeouts | a connection stays open indefinitely as long as the client keeps sending requests and doesn't send Connection: close |
| Response headers on the client side | http.request's own request headers and http.createServer's response headers both shipped, via Map<string, string>; reading real response headers on the client needs a lower-level request flow than what's wired up yet -- confirmed reachable, deliberately deferred rather than rewriting an already-working, already-benchmarked call under time pressure |
| Case-insensitive header lookup, repeated header/query values as an array | Map<string, string>'s exact-match, last-write-wins lookup is what's available now |
| A general-purpose locking primitive for handler-shared state | concurrent serving (shipped) means a handler now genuinely runs on multiple OS threads; a handler that mutates shared global state without its own synchronization has a real data race. Map/Set now detect an overlapping access and fail loudly instead of corrupting or crashing unpredictably (spec 492), but that is a diagnostic, not a fix -- a handler still cannot safely share mutable state across a concurrent server's connections, since Lumen has no Mutex-shaped stdlib type yet |
Real Server/IncomingMessage/ServerResponse/ClientRequest/Agent classes | Node's classes bundle request/response data with an event mechanism; EventEmitter alone doesn't supply the data half, which still needs the header-collection/streaming gaps closed first |
Server lifecycle events ('error', 'close') via EventEmitter<T> | genuinely reachable now that EventEmitter exists — a real follow-up |
| Streaming request/response bodies | needs a network-backed ReadableStream/WritableStream (spec 046 shipped file-backed streams only, a deliberately separate backing type -- see spec 046's "why file-backed only") |
| HTTPS/TLS-specific configuration | std.http.Client already handles basic TLS internally for https:// URLs; exposing it as configurable is a real, separate feature, disproportionately large for a single pass |
| WebSocket upgrade | a full protocol implementation (handshake, frame parsing/masking, ping/pong) -- a separate feature on the scale of http itself, not an extension of it |
zlib
| API | Notes |
zlib.gzip/unzip (async, callback or Promise-based) | every other module's *Sync functions have shipped first before their async twins get a dedicated pass (see fs.readFileSync predating fs.readFile's thread-pool design); compression is comparatively rare on a hot path, sync-first is the right order here too |
zlib.createGzip()/createDeflate() streaming transform | needs a stateful stream/duplex object the language doesn't have yet; one-shot whole-buffer compression covers the common case |
Brotli (zlib.brotliCompressSync/etc.) | not in std.compress at all in this Zig version (only flate, zstd, lzma/lzma2, xz exist) — would need vendoring a C library or a from-scratch implementation |
| zstd/xz/lzma wrappers | std.compress ships these too, but Node's own zlib module only covers gzip/deflate/brotli — out of scope for a module matching that name/surface |
| A compression-level parameter | the stdlib's wrappers so far are plain positional-arg functions (see crypto.randomBytes(n)), not an options-object style; can be added as an optional second argument later |
Buffer
| API | Notes |
crypto.* returning Buffer instead of hex/base64 strings | a real audit of every existing crypto call site and its current string-returning contract -- a breaking change to an already-shipped API, deliberately not bundled into the pass that first introduces the type it would return |
ReadableStream/WritableStream chunks as Buffer instead of string | spec 046 already shipped and is exercised as string-chunked; switching its chunk type is a separate, real migration |
JSON (de)serialization of Buffer | no existing convention in this codebase for how a byte array should round-trip through JSON (Node uses {"type":"Buffer","data":[...]}); not decided here |
Buffer.concat([...]) | needs a decision on how a Lumen array of Buffer values is spelled (Buffer[] -- an untested combination of a new bare type with array-of syntax) before it's worth building |
buf[i] index syntax | the indexing checker is array-type-specific; .at(i) covers the same need today |
Worker
| API | Notes |
new Worker(...) with postMessage/on('message')/terminate() | needs a real cross-thread mutable message channel Lumen has no primitive for yet; shipping the constructor shape without the instance methods it implies would be worse than the flat Worker.run shape actually shipped |
Non-scalar T (strings, arrays, Map/Set/Buffer, objects, class instances) crossing the boundary | not verified safe this pass without a real capture/deep-copy-semantics analysis; scalars alone are enough to prove the thread-spawn + promise-handback mechanism works correctly |
| Static prevention of unsafe reference-shaped captures | would need a real capture/Send-style analysis in the checker; documented as a known hazard instead, the same way http.createServer's multi-threaded-handler trade-off above is documented rather than solved |
A persistent worker pool / reusable Worker handle | no concrete use case needs one yet; one detached thread per Worker.run() call is simpler and sufficient |
| Worker cancellation / timeouts | not exercised by the v1 function set — each call is a single run-to-completion function, no cancellation hook exists to wire up |
--wasm support | Worker.run requires the async/Promise event-loop machinery, which already hits the existing wasm-vs-@import("xev") gate — wasm32-wasi has no real OS threads anyway |