Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
aab50d2
fix(node:url, build): improve Node URL helpers; add targeted tests; f…
akapug Aug 24, 2025
1723a69
feat(node:timers): wire node:timers and node:timers/promises; add tes…
akapug Aug 24, 2025
9c37211
feat(node:http): expose minimal shape for http module to satisfy conf…
akapug Aug 24, 2025
4618621
feat(node:*): add minimal facades for punycode, repl, trace_events, v…
akapug Aug 24, 2025
fb5fcbd
feat(node:tls,tty): add minimal facades for tls and tty modules (plac…
akapug Aug 24, 2025
5b88167
chore(node:feature): register new module facades (tls, tty, trace_eve…
akapug Aug 24, 2025
57b64aa
feat(node:async_hooks): add minimal facade and API; expose createHook…
akapug Aug 24, 2025
2fa40fa
switch to augment for the following:
akapug Aug 24, 2025
7d42114
feat(node:constants): add minimal constants module wired to os and fs…
akapug Aug 24, 2025
2c116b5
feat(node:constants): add minimal constants module wired to os and fs…
akapug Aug 24, 2025
ac61761
feat(node:wasi): include WASI in NodeModuleName and built-in loader list
akapug Aug 24, 2025
81c34cd
test(node:constants,wasi): add basic conformance tests for constants …
akapug Aug 24, 2025
f663214
test(node:vm,tty,worker_threads,trace_events,repl,punycode,tls,v8): a…
akapug Aug 24, 2025
982545b
test(node:*): add conformance shape tests for wasi, repl, trace_event…
akapug Aug 24, 2025
b25f81d
feat(node:vm,tls,punycode): implement initial behaviors
akapug Aug 24, 2025
04a5c75
feat(node:vm,tls,trace_events,async_hooks,repl,worker_threads): imple…
akapug Aug 24, 2025
91dbbb1
feat(node:vm): bind sandbox/context members in runInNewContext and ru…
akapug Aug 24, 2025
eec574a
feat(node:trace_events,worker_threads,tls): track enabled categories;…
akapug Aug 24, 2025
a93e491
feat(node:punycode,url,tls): implement RFC3492 punycode; improve UNC …
akapug Aug 24, 2025
e32a5d7
feat(node:worker_threads,vm,tls,url,punycode,trace_events): parentPor…
akapug Aug 24, 2025
02c606a
feat(node:readline,readline/promises,stream/promises,stream/consumers…
akapug Aug 24, 2025
2261b8b
feat(node:stream/promises): implement finished/pipeline event-driven …
akapug Aug 24, 2025
8adb3cb
fix(node:http): treat non-boolean handler return as handled; minimal …
akapug Aug 24, 2025
74699cf
feat(node:module): builtinModules/isBuiltin/createRequire minimal imp…
akapug Aug 24, 2025
596748a
feat(node:dgram): minimal createSocket facade with bind/send/close/on
akapug Aug 24, 2025
915cc06
feat(node:https,http2,net): minimal server facades and member keys to…
akapug Aug 24, 2025
42d7c09
feat(node:net): expose expected member keys and minimal connect/creat…
akapug Aug 24, 2025
06de4f4
feat(node:net): implement isIP/isIPv4/isIPv6 using InetAddress mapping
akapug Aug 24, 2025
9f1d4c9
feat(node:http2): add address() and improve listen callback; feat(nod…
akapug Aug 24, 2025
c0f54d8
chore(build): add elide.skipNatives Gradle property to skip third-par…
akapug Aug 24, 2025
ff38c21
chore(build): allow skipping Rust native build with -Pelide.skipNativ…
akapug Aug 24, 2025
494bff9
chore(build): allow skipping all native builds via -Pelide.skipNative…
akapug Aug 24, 2025
96d48e3
feat(node:stream/consumers): support minimal ReadableStream input for…
akapug Aug 24, 2025
25782e3
feat(node:net): add address() to connection/server facades for shape …
akapug Aug 24, 2025
977cd8f
feat(node:dns): implement minimal dns and dns/promises (A/AAAA/revers…
akapug Aug 24, 2025
a800a35
feat(node): polish stream/promises and module.createRequire (builtin …
akapug Aug 24, 2025
9e99627
feat(node): stream/consumers supports AsyncIterable + multi-chunk Rea…
akapug Aug 24, 2025
f7fbe98
test(node): fix test compile issues: v8 test not using DSL; wasi smok…
akapug Aug 24, 2025
49f2116
test(node): fix remaining DSL uses; use executeGuest(true) { ... } wh…
akapug Aug 24, 2025
223ac20
test(node): migrate remaining behavior tests off DSL; use executeGues…
akapug Aug 24, 2025
b760445
test(node): finish DSL migration in behavior tests (tls, readline, re…
akapug Aug 24, 2025
188e51e
test(node): sweep remaining behavior tests in PR scope (readline/prom…
akapug Aug 24, 2025
c924f72
test(node): full sweep across behavior tests still using legacy DSL; …
akapug Aug 24, 2025
5161527
test(node): fix NodeV8Test to implement injectable; migrate NodeStrea…
akapug Aug 24, 2025
2b2ff19
test(node): fix wasi injectable and migrate url behavior tests to @Te…
akapug Aug 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions COMPATIBILITY_MATRIX.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Elide Node API Compatibility Matrix

Legend: Implemented / Partial / Missing

- Link each module to PRs and tests; update on every PR

| Module | Status | Gaps / Notes | Tests | Linked PR(s) |
|---|---|---|---|---|
| assert (+strict) | Partial | Surface present via runtime shims; deeper invariants TBD | ✅ basic | |
| buffer | Implemented | | ✅ | |
| child_process | Missing | Stub only | ❌ | |
| cluster | Partial | Mostly stubs | ❌ | |
| console | Implemented | | ✅ | |
| crypto | Partial | Subsets mapped to WebCrypto; Node-specific APIs TBD | ✅ subset | |
| dgram | Missing | Module scaffold present | ❌ | |
| diagnostics_channel | Missing | | ❌ | |
| dns | Partial | A/AAAA/reverse; ENOTSUP others; defaultResultOrder | ✅ | #1617 |
| dns/promises | Partial | Promise variants for the above | ✅ | #1617 |
| domain | Partial | | ⚠️ | |
| events | Partial | EventEmitter/EventTarget implemented; module facade wired | ✅ | |
| fs | Partial | readFile/writeFile sync/async; more ops TBD | ✅ | |
| fs/promises | Partial | readFile/writeFile, mkdir, access | ✅ | |
| http | Partial | createServer + minimal ServerResponse; streaming/backpressure TBD | ✅ | #1617, #1619 |
| http2 | Partial | Stubs; behavior TBD | ⚠️ | |
| https | Partial | Wrapper TBD | ⚠️ | #1619 (follow-up planned) |
| inspector | Partial | | ⚠️ | |
| inspector/promises | Partial | | ⚠️ | |
| module | Partial | builtinModules/isBuiltin/createRequire | ✅ | #1619 |
| net | Partial | Client/server basics TBD | ⚠️ | |
| os | Partial | | ✅ | |
| path | Partial | posix/win32 variants; edge cases/UNC TBD | ✅ | |
| perf_hooks | Partial | | ⚠️ | |
| process | Implemented | | ✅ | |
| punycode | Missing | | ❌ | |
| querystring | Partial | Legacy minimal | ⚠️ | |
| readline | Partial | | ⚠️ | |
| readline/promises | Partial | | ⚠️ | |
| repl | Missing | | ❌ | |
| stream | Partial | Core types present | ✅ | |
| stream/consumers | Partial | Some consumers present | ✅ | |
| stream/promises | Partial | finished/pipeline implemented; more tests in #1618 | ✅ | #1618 |
| stream/web | Partial | | ✅ | |
| string_decoder | Implemented | | ✅ | |
| test | N/A | Out of scope | | |
| timers | Implemented | Node-facing module wired to JsTimers | ✅ | this PR |
| timers/promises | Implemented | setTimeout/setImmediate (promises) | ✅ | this PR |
| tls | Partial | Stub | ❌ | |
| trace_events | Missing | | ❌ | |
| tty | Partial | Stub | ❌ | |
| url | Partial | Helpers implemented; more parity possible | ✅ | #1619, this PR |
| util | Partial | promisify/callbackify/inspect/types subset | ✅ | |
| v8 | Missing | | ❌ | |
| vm | Partial | | ⚠️ | |
| wasi | Missing | | ❌ | |
| worker_threads | Partial | | ⚠️ | |
| zlib | Partial | | ⚠️ | |

Notes:
- Do not duplicate work in #1617/#1618/#1619; build on top
- When expanding a module, update this file and add docs in docs/node/<module>.md

Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ private val allNodeModules = sortedSetOf(
NodeModuleName.STRING_DECODER,
NodeModuleName.TEST,
NodeModuleName.TIMERS,
NodeModuleName.TIMERS_PROMISES,
NodeModuleName.TLS,
NodeModuleName.TTY,
NodeModuleName.URL,
Expand All @@ -133,7 +134,9 @@ private val allNodeModules = sortedSetOf(
NodeModuleName.VM,
NodeModuleName.WORKER,
NodeModuleName.WORKER_THREADS,
NodeModuleName.WASI,
NodeModuleName.ZLIB,
// wasi is not a standard core module string in our NodeModuleName; allow 'wasi' directly
)

/**
Expand Down Expand Up @@ -187,6 +190,7 @@ public inline fun String.asJsSymbolString(): String = replace("/", "_")
public const val STRING_DECODER: String = "string_decoder"
public const val TEST: String = "test"
public const val TIMERS: String = "timers"
public const val TIMERS_PROMISES: String = "timers/promises"
public const val TLS: String = "tls"
public const val TRACE_EVENTS: String = "trace_events"
public const val TTY: String = "tty"
Expand All @@ -196,6 +200,7 @@ public inline fun String.asJsSymbolString(): String = replace("/", "_")
public const val VM: String = "vm"
public const val WORKER: String = "worker"
public const val WORKER_THREADS: String = "worker_threads"
public const val WASI: String = "wasi"
public const val ZLIB: String = "zlib"

// named modules do not contain periods
Expand Down
31 changes: 30 additions & 1 deletion packages/graalvm/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -699,6 +699,14 @@ val thirdPartyDir: String =
val buildThirdPartyNatives by tasks.registering(Exec::class) {
workingDir(rootProject.layout.projectDirectory.asFile.path)

val skipNatives = providers.gradleProperty("elide.skipNatives").map { it == "true" }.orElse(false).get() || (System.getenv("ELIDE_SKIP_NATIVES") == "true")
onlyIf {
if (skipNatives) {
logger.lifecycle("Skipping third-party natives build (elide.skipNatives=true)")
false
} else true
}

commandLine(
"make",
"-C", "third_party",
Expand Down Expand Up @@ -768,6 +776,14 @@ val buildRustNativesForHostRelease by tasks.registering(Exec::class) {
workingDir(rootDir)
dependsOn("buildThirdPartyNatives")

val skipNatives = providers.gradleProperty("elide.skipNatives").map { it == "true" }.orElse(false).get() || (System.getenv("ELIDE_SKIP_NATIVES") == "true")
onlyIf {
if (skipNatives) {
logger.lifecycle("Skipping rust natives build (elide.skipNatives=true)")
false
} else true
}

executable = "cargo"
args(baseCargoFlags.plus("--release"))
environment("JAVA_HOME", System.getProperty("java.home"))
Expand All @@ -781,6 +797,14 @@ val buildRustNativesForHost by tasks.registering(Exec::class) {
workingDir(rootDir)
dependsOn("buildThirdPartyNatives")

val skipNatives = providers.gradleProperty("elide.skipNatives").map { it == "true" }.orElse(false).get() || (System.getenv("ELIDE_SKIP_NATIVES") == "true")
onlyIf {
if (skipNatives) {
logger.lifecycle("Skipping rust natives build (elide.skipNatives=true)")
false
} else true
}

executable = "cargo"
args(baseCargoFlags.plus(listOfNotNull(if (isRelease) "--release" else null)))
environment("JAVA_HOME", System.getProperty("java.home"))
Expand Down Expand Up @@ -813,6 +837,11 @@ listOf(
tasks.test,
).forEach {
it.configure {
dependsOn(natives)
val skipNatives = providers.gradleProperty("elide.skipNatives").map { it == "true" }.orElse(false).get() || (System.getenv("ELIDE_SKIP_NATIVES") == "true")
if (!skipNatives) {
dependsOn(natives)
} else {
logger.lifecycle("Skipping natives dependency for ${'$'}name (elide.skipNatives=true)")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,10 @@ private const val REGISTER_ALL_MODULES_FOR_REFLECTION = true
cls(TestAPI::class)
cls(NodeTest::class)

// `constants`
cls(ConstantsAPI::class)
cls(elide.runtime.node.constants.NodeConstants::class)

// `url`
cls(URLAPI::class)
cls(NodeURL::class)
Expand All @@ -234,6 +238,30 @@ private const val REGISTER_ALL_MODULES_FOR_REFLECTION = true
cls(URLSearchParamsIntrinsic.URLSearchParams::class)
cls(URLSearchParamsIntrinsic.MutableURLSearchParams::class)

// `tls`
cls(TLSAPI::class)
cls(elide.runtime.node.tls.NodeTls::class)

// `trace_events`
cls(elide.runtime.intrinsics.js.node.TraceEventsAPI::class)
cls(elide.runtime.node.trace.NodeTraceEvents::class)

// `tty`
cls(TtyAPI::class)
cls(elide.runtime.node.tty.NodeTty::class)

// `v8`
cls(V8API::class)
cls(elide.runtime.node.v8.NodeV8::class)

// `vm`
cls(VMAPI::class)
cls(elide.runtime.node.vm.NodeVm::class)

// `wasi`
cls(WASIAPI::class)
cls(elide.runtime.node.wasi.NodeWasi::class)

// `worker`
cls(WorkerAPI::class)
cls(NodeWorker::class)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* Copyright (c) 2024-2025 Elide Technologies, Inc.
* Licensed under the MIT license.
*/
package elide.runtime.intrinsics.js.node

import elide.annotations.API

/** Node API: async_hooks */
@API public interface AsyncHooksAPI : NodeAPI

Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* Copyright (c) 2024-2025 Elide Technologies, Inc.
* Licensed under the MIT license.
*/
package elide.runtime.intrinsics.js.node

import elide.annotations.API

/** Node API: constants */
@API public interface ConstantsAPI : NodeAPI

Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* Copyright (c) 2024-2025 Elide Technologies, Inc.
* Licensed under the MIT license.
*/
package elide.runtime.intrinsics.js.node

import elide.annotations.API

/** Node API: punycode */
@API public interface PunycodeAPI : NodeAPI

Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* Copyright (c) 2024-2025 Elide Technologies, Inc.
* Licensed under the MIT license.
*/
package elide.runtime.intrinsics.js.node

import elide.annotations.API

/** Node API: repl */
@API public interface ReplAPI : NodeAPI

Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* Copyright (c) 2024-2025 Elide Technologies, Inc.
* Licensed under the MIT license.
*/
package elide.runtime.intrinsics.js.node

import elide.annotations.API

/** Node API: trace_events */
@API public interface TraceEventsAPI : NodeAPI

Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* Copyright (c) 2024-2025 Elide Technologies, Inc.
* Licensed under the MIT license.
*/
package elide.runtime.intrinsics.js.node

import elide.annotations.API

/** Node API: tty */
@API public interface TtyAPI : NodeAPI

Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* Copyright (c) 2024-2025 Elide Technologies, Inc.
* Licensed under the MIT license.
*/
package elide.runtime.intrinsics.js.node

import elide.annotations.API

/** Node API: worker_threads */
@API public interface WorkerThreadsAPI : NodeAPI

Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ import elide.runtime.intrinsics.server.http.HttpResponse
return value.execute(wrapped, responder, context).let { result ->
when {
result.isBoolean -> result.asBoolean()
// don't forward by default
else -> false
// treat non-boolean return as handled (no fallthrough)
else -> true
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright (c) 2024-2025 Elide Technologies, Inc.
* Licensed under the MIT license.
*/
package elide.runtime.node.async

import org.graalvm.polyglot.proxy.ProxyExecutable
import elide.runtime.gvm.api.Intrinsic
import elide.runtime.gvm.internals.intrinsics.js.AbstractNodeBuiltinModule
import elide.runtime.gvm.loader.ModuleInfo
import elide.runtime.gvm.loader.ModuleRegistry
import elide.runtime.interop.ReadOnlyProxyObject
import elide.runtime.intrinsics.GuestIntrinsic.MutableIntrinsicBindings
import elide.runtime.intrinsics.js.node.AsyncHooksAPI
import elide.runtime.lang.javascript.NodeModuleName
import java.util.concurrent.atomic.AtomicInteger

private const val F_CREATE_HOOKS = "createHook"
private const val F_EXECUTION_ASYNC_ID = "executionAsyncId"
private const val F_TRIGGER_ASYNC_ID = "triggerAsyncId"
private val NEXT_ID = AtomicInteger(1)

private val ALL_MEMBERS = arrayOf(
F_CREATE_HOOKS,
F_EXECUTION_ASYNC_ID,
F_TRIGGER_ASYNC_ID,
)

@Intrinsic internal class NodeAsyncHooksModule : AbstractNodeBuiltinModule() {
private val singleton by lazy { NodeAsyncHooks.create() }
internal fun provide(): AsyncHooksAPI = singleton

override fun install(bindings: MutableIntrinsicBindings) {
ModuleRegistry.deferred(ModuleInfo.of(NodeModuleName.ASYNC_HOOKS)) { singleton }
}
}

/** Minimal `async_hooks` module facade. */
internal class NodeAsyncHooks private constructor() : ReadOnlyProxyObject, AsyncHooksAPI {
companion object { @JvmStatic fun create(): NodeAsyncHooks = NodeAsyncHooks() }

override fun getMemberKeys(): Array<String> = ALL_MEMBERS

override fun getMember(key: String?): Any? = when (key) {
F_CREATE_HOOKS -> ProxyExecutable { args ->
val hooks = args.getOrNull(0)
object : ReadOnlyProxyObject {
private var enabled = false
override fun getMemberKeys(): Array<String> = arrayOf("enable","disable")
override fun getMember(k: String?): Any? = when (k) {
"enable" -> ProxyExecutable { _: Array<org.graalvm.polyglot.Value> -> enabled = true; null }
"disable" -> ProxyExecutable { _: Array<org.graalvm.polyglot.Value> -> enabled = false; null }
else -> null
}
}
}
F_EXECUTION_ASYNC_ID -> ProxyExecutable { _ -> NEXT_ID.get() }
F_TRIGGER_ASYNC_ID -> ProxyExecutable { _ -> NEXT_ID.updateAndGet { it + 1 } }
else -> null
}
}

Loading