Skip to content

fix: #811 — net.isIP/isIPv4/isIPv6 + auto-select-family defaults#818

Merged
proggeramlug merged 2 commits into
mainfrom
worktree-fix-811-net-isip
May 16, 2026
Merged

fix: #811 — net.isIP/isIPv4/isIPv6 + auto-select-family defaults#818
proggeramlug merged 2 commits into
mainfrom
worktree-fix-811-net-isip

Conversation

@proggeramlug
Copy link
Copy Markdown
Contributor

Summary

  • Implements net.isIP, net.isIPv4, net.isIPv6, and the four *DefaultAutoSelectFamily* accessors in perry-ext-net.
  • Wires them into NATIVE_MODULE_TABLE (NA_STR/NA_F64 → NR_F64, since the runtime fns return already-NaN-boxed JSValue bits) and adds matching perry-api-manifest entries so the Compile-time error for unimplemented Node / Web APIs #463 strict-API gate accepts them.
  • is_ipv6_str rejects bracketed and zone-id (%) forms to match Node's net.isIPv6.
  • Default autoSelectFamilyAttemptTimeout is 500ms — Node v20+ default; byte-for-byte parity required this.

Test plan

  • 6-line repro from the issue matches Node (isIP 127.0.0.1: 4, isIP ::1: 6, isIPv4: true, isIPv6: true, getDefaultAutoSelectFamily: true).
  • Extended round-trip — setDefault*(getDefault*()) runs without error, attempt-timeout default = 500.
  • cargo build --release -p perry-ext-net -p perry succeeds.

Notes

  • Out of scope: net.BlockList, net.SocketAddress, net.Server class — those are the larger socket-layer gap from test_parity_net.ts. Only the pure string/global-flag helpers from the issue are implemented here.

Closes #811.

`net.isIP`, `net.isIPv4`, `net.isIPv6`, `net.getDefaultAutoSelectFamily`,
`net.setDefaultAutoSelectFamily`, `net.getDefaultAutoSelectFamilyAttemptTimeout`,
and `net.setDefaultAutoSelectFamilyAttemptTimeout` previously returned
`undefined` because they had no NATIVE_MODULE_TABLE entries and no
perry-ext-net runtime implementations.

Adds:

- perry-ext-net runtime fns:
  - `js_net_is_ip` — returns 0/4/6 via `std::net::Ipv4Addr` /
    `std::net::Ipv6Addr` parse. `is_ipv6_str` rejects bracketed and
    zone-id (`%`) forms to match Node.
  - `js_net_is_ipv4` / `js_net_is_ipv6` — boolean.
  - `js_net_get_default_auto_select_family` /
    `js_net_set_default_auto_select_family` — read/write
    `AUTO_SELECT_FAMILY` (AtomicBool, default `true`).
  - `js_net_get_default_auto_select_family_attempt_timeout` /
    `js_net_set_default_auto_select_family_attempt_timeout` — read/write
    `AUTO_SELECT_FAMILY_ATTEMPT_TIMEOUT_MS` (AtomicI32, default 500ms
    to match Node v20+).
- NATIVE_MODULE_TABLE entries dispatch `net.<method>(args)` to the new
  runtime fns via the standard NA_STR/NA_F64 + NR_F64 protocol (return
  values are already NaN-boxed JSValues).
- perry-api-manifest entries flip the #463 strict-API gate from "not
  implemented" to "supported".

6-line repro from the issue now matches Node byte-for-byte; full
auto-select-family round-trip works.
cargo fmt --all and ./scripts/regen_api_docs.sh — fixes the lint and
api-docs-drift checks on PR #818.
@proggeramlug proggeramlug merged commit e18cdb0 into main May 16, 2026
9 checks passed
@proggeramlug proggeramlug deleted the worktree-fix-811-net-isip branch May 16, 2026 06:37
proggeramlug added a commit that referenced this pull request May 18, 2026
… fallback

The V8-fallback bundler (`__perry_js_bundle.js`) only included the first
JS module it landed on — pure-ESM packages whose entry re-exports
siblings (hono's `dist/index.js` → `./hono.js` → `./hono-base.js` → …
twenty-some submodules) ended up with one entry, dropping the rest.
Compiled binaries still worked when their `node_modules/` tree happened
to sit next to them (V8 reads files off disk), but the realistic hono
call path (`app.fetch(req)` running cross-thread) cascaded into rc=139
segfaults when those resolved paths weren't co-located, and shipping the
binary on its own surfaced `Cannot resolve module` for every missing
sibling.

Root cause was the JS branch of `collect_modules` bailing after one
insert with the comment "V8 will handle that at runtime". Native
TypeScript imports already recursed via `cached_resolve_import`; JS did
not. Added a lightweight regex-based scanner
(`collect_js_module_imports`) that pulls out static `import` /
`export ... from` / string-literal `import("...")` specifiers, resolves
each relative / absolute path via the existing `resolve_with_extensions`
helper, and re-enters `collect_modules` for each — re-running the
JS/native classification so cross-package landings into a
`compilePackages`-overridden TS file are still handled correctly. Bare
specifiers (`react`, `@foo/bar`) stay the entry walker's job since the
top-level TS path already pulls package deps; the relative walk only
needs to cover the inside-a-package case, which is always relative-path.

Validation: hono@4.12.19 repro under `/tmp/perry-hono/` — pre-fix
bundle had 1 module entry, post-fix has 24 (every `dist/**/*.js`
reachable from `index.js` through the full re-export graph). New
fixture `test-files/test_hono_bundle.ts` documents the smoke shape;
the bundle-walks-recursively assertion lives in the PR description
because the parity suite doesn't install npm packages.

Next blocker (out of scope): V8's `ModuleLoader::load` in
`crates/perry-jsruntime/src/modules.rs` still reads from disk via
`std::fs::read_to_string` and never consults `__COMPILETS_MODULES`, so
the binary isn't yet self-contained even with the correct bundle.
proggeramlug added a commit that referenced this pull request May 18, 2026
… fallback (#987)

The V8-fallback bundler (`__perry_js_bundle.js`) only included the first
JS module it landed on — pure-ESM packages whose entry re-exports
siblings (hono's `dist/index.js` → `./hono.js` → `./hono-base.js` → …
twenty-some submodules) ended up with one entry, dropping the rest.
Compiled binaries still worked when their `node_modules/` tree happened
to sit next to them (V8 reads files off disk), but the realistic hono
call path (`app.fetch(req)` running cross-thread) cascaded into rc=139
segfaults when those resolved paths weren't co-located, and shipping the
binary on its own surfaced `Cannot resolve module` for every missing
sibling.

Root cause was the JS branch of `collect_modules` bailing after one
insert with the comment "V8 will handle that at runtime". Native
TypeScript imports already recursed via `cached_resolve_import`; JS did
not. Added a lightweight regex-based scanner
(`collect_js_module_imports`) that pulls out static `import` /
`export ... from` / string-literal `import("...")` specifiers, resolves
each relative / absolute path via the existing `resolve_with_extensions`
helper, and re-enters `collect_modules` for each — re-running the
JS/native classification so cross-package landings into a
`compilePackages`-overridden TS file are still handled correctly. Bare
specifiers (`react`, `@foo/bar`) stay the entry walker's job since the
top-level TS path already pulls package deps; the relative walk only
needs to cover the inside-a-package case, which is always relative-path.

Validation: hono@4.12.19 repro under `/tmp/perry-hono/` — pre-fix
bundle had 1 module entry, post-fix has 24 (every `dist/**/*.js`
reachable from `index.js` through the full re-export graph). New
fixture `test-files/test_hono_bundle.ts` documents the smoke shape;
the bundle-walks-recursively assertion lives in the PR description
because the parity suite doesn't install npm packages.

Next blocker (out of scope): V8's `ModuleLoader::load` in
`crates/perry-jsruntime/src/modules.rs` still reads from disk via
`std::fs::read_to_string` and never consults `__COMPILETS_MODULES`, so
the binary isn't yet self-contained even with the correct bundle.
proggeramlug added a commit that referenced this pull request May 18, 2026
v0.5.993 closed half of #818: the bundle now contains every transitive
ESM sibling. The other half — `NodeModuleLoader::load` still reading
sources via `std::fs::read_to_string` and ignoring the bundle — meant
binaries still required `node_modules/` to live next to them at
runtime. This commit makes V8-fallback binaries truly self-contained.

Three pieces:

1. `perry-jsruntime/src/modules.rs` grows two `RwLock<HashMap>`s —
   `EMBEDDED_MODULES` (build-time canonical path -> source) and
   `EMBEDDED_ALIASES` (bare specifier -> path) — plus `#[no_mangle]`
   FFIs `js_register_embedded_module` / `js_register_embedded_alias`
   and Rust-facing wrappers.

2. `NodeModuleLoader::resolve_module_path` consults the alias map for
   bare specs and the path map (with extension/index fallback) when
   on-disk probes fail. `NodeModuleLoader::load` checks the map before
   `std::fs::read_to_string`. Keys are build-time canonical paths used
   as opaque identifiers — `canonicalize()` in `resolve()` already
   gracefully falls back when the path doesn't exist on the runtime
   filesystem, and the `file://` URL handed to V8 works either way.

3. New `targets::generate_embedded_js_object` writes a generated `.c`
   file with one `static const char[]` per JS module source + length
   pair (and matching pairs for every TS import edge whose
   `resolved_path` is in `ctx.js_modules`), wrapped in a
   `__attribute__((constructor(101)))` that calls the registration
   FFIs at process start. `cc -c` produces an `.o`; compile.rs appends
   it to `obj_paths` whenever `needs_js_runtime && !js_modules.is_empty()`.
   Octal escapes for every non-printable byte keep the generated C
   ASCII-clean regardless of the JS file's encoding; `?` is escaped to
   defang trigraph hazards.

Validation: `import { Hono } from 'hono'` compiles, the resulting
binary moved to an isolated directory with no `node_modules/` and no
source still prints `object function`. The on-disk
`__perry_js_bundle.js` is retained as a build-time debugging artifact
but is no longer needed at runtime.

New fixture: `test-files/test_v8_self_contained.ts`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

net.isIP / net.isIPv4 / net.isIPv6 / net.getDefaultAutoSelectFamily return undefined

1 participant