Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions changelog.d/7159-manifest-consistency-iovalkey-drift.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fix(api-manifest): backfill missing `API_MANIFEST` rows so it stays in sync with the codegen native dispatch table and `NATIVE_MODULES` — adds the `iovalkey` alias surface (`Redis` class + `createClient`, mirroring `redis`), the `dgram` `Socket.sendto` receiver method, and the `sqlite` `DatabaseSync.serialize`/`deserialize` receiver methods. Also fixes the `every_well_known_binding_has_manifest_entry` drift test to skip the `[bindings.*.upstream]` provenance sub-tables in `well_known_bindings.toml`, which its naive parser had been misreading as binding names. Also covers the dispatch rows that landed while this was in review — `inspector.console.log`/`info`/`debug`/`warn`/`error` and `inspector/promises`'s no-`new` `Session` shim (#7090), `cluster.listeners`/`rawListeners`/`setMaxListeners`/`getMaxListeners` (#7105), and `lru-cache`'s `peek` (#7136) — so `manifest_consistency` is fully green rather than merely less red.
24 changes: 24 additions & 0 deletions crates/perry-api-manifest/src/entries/part_1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,12 @@ pub(crate) const API_MANIFEST_PART_1: &[ApiEntry] = &[
method("sqlite", "@@__perry_wk_dispose", true, None),
method("sqlite", "exec", true, None),
method("sqlite", "prepare", true, None),
// DatabaseSync.serialize()/deserialize() — dispatched by
// js_node_sqlite_database_sync_{serialize,deserialize}. Receiver
// methods on the base DatabaseSync instance (no class_filter), so
// they mirror the `open`/`close`/`exec`/`prepare` rows above.
method("sqlite", "serialize", true, None),
method("sqlite", "deserialize", true, None),
method("sqlite", "function", true, Some("DatabaseSync")),
method("sqlite", "aggregate", true, Some("DatabaseSync")),
method("sqlite", "enableDefensive", true, Some("DatabaseSync")),
Expand Down Expand Up @@ -327,6 +333,14 @@ pub(crate) const API_MANIFEST_PART_1: &[ApiEntry] = &[
"returns immediately after open(); there is no debugger to wait for (#4916)",
),
property("inspector", "console"),
// `inspector.console.*` — the Chrome-DevTools console forwarders (#7090).
// Dispatch carries `class_filter: Some("console")`, so these hang off the
// `console` property above rather than being named exports of the module.
method("inspector", "log", false, Some("console")),
method("inspector", "info", false, Some("console")),
method("inspector", "debug", false, Some("console")),
method("inspector", "warn", false, Some("console")),
method("inspector", "error", false, Some("console")),
property("inspector", "Network"),
class("inspector", "Session"),
method("inspector", "Session", false, None),
Expand Down Expand Up @@ -355,6 +369,11 @@ pub(crate) const API_MANIFEST_PART_1: &[ApiEntry] = &[
property("inspector/promises", "default"),
class("inspector/promises", "Session"),
method("inspector/promises", "Session", false, None),
// Synthetic dispatch row for `Session(...)` invoked without `new`
// (js_node_inspector_session_call_without_new, #7090). It is not a public
// named export, so it stays `internal_method` — the .d.ts must keep
// advertising `Session`, not `SessionCall`.
internal_method("inspector/promises", "SessionCall", false, None),
method("inspector/promises", "connect", true, Some("Session")),
method(
"inspector/promises",
Expand Down Expand Up @@ -534,6 +553,9 @@ pub(crate) const API_MANIFEST_PART_1: &[ApiEntry] = &[
class("dgram", "Socket"),
method("dgram", "Socket", false, None),
method("dgram", "send", true, Some("Socket")),
// Socket.sendto() — dispatched by js_dgram_socket_sendto (sibling of
// `send`); same Socket-filtered receiver shape.
method("dgram", "sendto", true, Some("Socket")),
method("dgram", "bind", true, Some("Socket")),
method("dgram", "close", true, Some("Socket")),
method("dgram", "address", true, Some("Socket")),
Expand Down Expand Up @@ -991,6 +1013,8 @@ pub(crate) const API_MANIFEST_PART_1: &[ApiEntry] = &[
method("lru-cache", "delete", true, None),
method("lru-cache", "clear", true, None),
method("lru-cache", "size", true, None),
// `peek(key)` — read without refreshing recency (#7136).
method("lru-cache", "peek", true, None),
method("commander", "name", true, None),
method("commander", "description", true, None),
method("commander", "version", true, None),
Expand Down
11 changes: 11 additions & 0 deletions crates/perry-api-manifest/src/entries/part_3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,13 @@ pub(crate) const API_MANIFEST_PART_3: &[ApiEntry] = &[
internal_method("cluster", "removeListener", false, None),
internal_method("cluster", "off", false, None),
internal_method("cluster", "removeAllListeners", false, None),
// #7105 (Node 26 cluster semantics) added these four to
// NATIVE_MODULE_TABLE; they belong to the same EventEmitter surface, so
// they are `internal_method` like their siblings above.
internal_method("cluster", "listeners", false, None),
internal_method("cluster", "rawListeners", false, None),
internal_method("cluster", "setMaxListeners", false, None),
internal_method("cluster", "getMaxListeners", false, None),
// Keep property reads registered so the #463 strict gate accepts the
// namespace-export shape; `get_native_module_constant` returns undefined
// for these names at runtime.
Expand All @@ -619,6 +626,10 @@ pub(crate) const API_MANIFEST_PART_3: &[ApiEntry] = &[
internal_property("cluster", "emit"),
internal_property("cluster", "eventNames"),
internal_property("cluster", "listenerCount"),
internal_property("cluster", "listeners"),
internal_property("cluster", "rawListeners"),
internal_property("cluster", "setMaxListeners"),
internal_property("cluster", "getMaxListeners"),
// ===========================================================
// #513 Phase A: backfill receiver-less surface for modules that
// previously had zero entries. Without these, `module_has_any_entries`
Expand Down
8 changes: 8 additions & 0 deletions crates/perry-api-manifest/src/entries/part_4.rs
Original file line number Diff line number Diff line change
Expand Up @@ -699,6 +699,14 @@ pub(crate) const API_MANIFEST_PART_4: &[ApiEntry] = &[
// from the ioredis class entries. ---
class("redis", "Redis"),
method("redis", "createClient", false, None),
// --- iovalkey — the Valkey fork of ioredis (valkey-io/iovalkey),
// served by the same perry-ext-ioredis surface. Dispatch
// normalizes `iovalkey` → `ioredis` (see native_module_dispatch.rs),
// so there are no iovalkey rows in NATIVE_MODULE_TABLE; these
// entries exist so the module clears the #513 strict-mode gate
// (module_has_any_entries) and mirror the `redis` alias above. ---
class("iovalkey", "Redis"),
method("iovalkey", "createClient", false, None),
// --- date-fns — alias for dayjs (well-known routes both to
// perry-ext-dayjs). Surface methods are the date-fns
// functional API exposed by the wrapper. ---
Expand Down
31 changes: 24 additions & 7 deletions crates/perry-codegen/tests/manifest_consistency.rs
Original file line number Diff line number Diff line change
Expand Up @@ -287,19 +287,36 @@ fn every_well_known_binding_has_manifest_entry() {
let mut missing: Vec<String> = Vec::new();
for line in toml.lines() {
let line = line.trim();
// `[bindings.<name>]` or `[bindings."<name>">]` (quoted form
// used for names with `/` or `.`).
// `[bindings.<name>]` or `[bindings."<name>"]` (quoted form used
// for names with `/` or `.`).
let Some(rest) = line.strip_prefix("[bindings.") else {
continue;
};
let Some(name_with_bracket) = rest.strip_suffix(']') else {
continue;
};
// Strip optional surrounding double quotes.
let name = name_with_bracket
.strip_prefix('"')
.and_then(|s| s.strip_suffix('"'))
.unwrap_or(name_with_bracket);
// Skip nested sub-tables like `[bindings.<name>.upstream]` — those
// carry per-binding provenance metadata (version/sha256/repo), not
// a routed module name. Only the bare top-level `[bindings.<name>]`
// header names a module the unimplemented-API gate must cover.
let name = if let Some(after_open_quote) = name_with_bracket.strip_prefix('"') {
// Quoted key such as `"decimal.js"`. A top-level binding header
// ends immediately after the closing quote; anything trailing
// (`"decimal.js".upstream`) is a sub-table.
let Some((inner, trailing)) = after_open_quote.split_once('"') else {
continue; // malformed / not a plain `[bindings."..."]` header
};
if !trailing.is_empty() {
continue; // sub-table of a quoted binding key
}
inner
} else {
// Bare key. A dotted path (`dotenv.upstream`) is a sub-table.
if name_with_bracket.contains('.') {
continue;
}
name_with_bracket
};
if SIDE_EFFECT_ONLY.contains(&name) {
continue;
}
Expand Down
9 changes: 8 additions & 1 deletion docs/api/perry.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Auto-generated from Perry's API manifest (#465). Do not edit by hand.
// Source: perry-api-manifest::API_MANIFEST
// Coverage: 1998 entries across 121 modules
// Coverage: 2000 entries across 122 modules

type PerryU32 = number & { readonly __perryU32?: never };
type PerryU64 = number & { readonly __perryU64?: never };
Expand Down Expand Up @@ -1934,6 +1934,13 @@ declare module "ioredis" {
export function createClient(p0: any): any;
}

declare module "iovalkey" {
/** stdlib */
export class Redis { [key: string]: any; }
/** stdlib */
export function createClient(...args: any[]): any;
}

declare module "iroh" {
/** stdlib */
export function bind(...args: any[]): any;
Expand Down
22 changes: 21 additions & 1 deletion docs/src/api/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

This page is auto-generated from Perry's compile-time API manifest (`perry-api-manifest::API_MANIFEST`). It is the source of truth for what `perry compile` accepts; references to symbols not listed here produce `R005 UnimplementedApi` (issue #463). Stubs (#464) are flagged ⚠ — they link cleanly but no-op at runtime on the chosen target.

Total: 2912 entries across 123 modules.
Total: 2923 entries across 124 modules.

## Modules

Expand Down Expand Up @@ -50,6 +50,7 @@ Total: 2912 entries across 123 modules.
- [`inspector`](#inspector)
- [`inspector/promises`](#inspectorpromises)
- [`ioredis`](#ioredis)
- [`iovalkey`](#iovalkey)
- [`iroh`](#iroh)
- [`jsonwebtoken`](#jsonwebtoken)
- [`lodash`](#lodash)
Expand Down Expand Up @@ -948,6 +949,7 @@ Total: 2912 entries across 123 modules.
- `remoteAddress` — instance *(class: `Socket`)*
- `removeListener` — instance *(class: `Socket`)*
- `send` — instance *(class: `Socket`)*
- `sendto` — instance *(class: `Socket`)*
- `setBroadcast` — instance *(class: `Socket`)*
- `setMulticastInterface` — instance *(class: `Socket`)*
- `setMulticastLoopback` — instance *(class: `Socket`)*
Expand Down Expand Up @@ -1767,13 +1769,18 @@ Total: 2912 entries across 123 modules.
- `close` — module
- `connect` — instance *(class: `Session`)*
- `connectToMainThread` — instance *(class: `Session`)*
- `debug` — module *(class: `console`)*
- `disconnect` — instance *(class: `Session`)*
- `error` — module *(class: `console`)*
- `info` — module *(class: `console`)*
- `log` — module *(class: `console`)*
- `on` — instance *(class: `Session`)*
- `once` — instance *(class: `Session`)*
- `open` — module ⚠ **stub** — accepts port/host but binds no real WebSocket inspector endpoint; sessions are in-process fakes (#4916)
- `post` — instance *(class: `Session`)* ⚠ **stub** — only Runtime.enable and a canned Runtime.evaluate subset respond; every other protocol method throws Inspector error -32601 (#4916)
- `url` — module ⚠ **stub** — always undefined: Perry never exposes a real inspector endpoint (#4916)
- `waitForDebugger` — module ⚠ **stub** — returns immediately after open(); there is no debugger to wait for (#4916)
- `warn` — module *(class: `console`)*

### Properties

Expand Down Expand Up @@ -1821,6 +1828,16 @@ Total: 2912 entries across 123 modules.
- `quit` — instance
- `set` — instance

## `iovalkey`

### Classes

- `Redis`

### Methods

- `createClient` — module

## `iroh`

### Methods
Expand Down Expand Up @@ -1888,6 +1905,7 @@ Total: 2912 entries across 123 modules.
- `delete` — instance
- `get` — instance
- `has` — instance
- `peek` — instance
- `set` — instance
- `size` — instance

Expand Down Expand Up @@ -3146,6 +3164,7 @@ Total: 2912 entries across 123 modules.
- `createSession` — instance
- `createTagStore` — instance *(class: `DatabaseSync`)*
- `db` — instance *(class: `SQLTagStore`)*
- `deserialize` — instance
- `enableDefensive` — instance *(class: `DatabaseSync`)*
- `enableLoadExtension` — instance
- `exec` — instance
Expand All @@ -3165,6 +3184,7 @@ Total: 2912 entries across 123 modules.
- `prepare` — instance
- `run` — instance *(class: `SQLTagStore`)*
- `run` — instance
- `serialize` — instance
- `setAllowBareNamedParameters` — instance
- `setAllowUnknownNamedParameters` — instance
- `setAuthorizer` — instance *(class: `DatabaseSync`)*
Expand Down
Loading