diff --git a/changelog.d/7159-manifest-consistency-iovalkey-drift.md b/changelog.d/7159-manifest-consistency-iovalkey-drift.md new file mode 100644 index 0000000000..9c883ddaed --- /dev/null +++ b/changelog.d/7159-manifest-consistency-iovalkey-drift.md @@ -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. diff --git a/crates/perry-api-manifest/src/entries/part_1.rs b/crates/perry-api-manifest/src/entries/part_1.rs index aa4b99fe13..18f572a2bd 100644 --- a/crates/perry-api-manifest/src/entries/part_1.rs +++ b/crates/perry-api-manifest/src/entries/part_1.rs @@ -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")), @@ -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), @@ -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", @@ -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")), @@ -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), diff --git a/crates/perry-api-manifest/src/entries/part_3.rs b/crates/perry-api-manifest/src/entries/part_3.rs index 0f7e0f893c..70b9642ac3 100644 --- a/crates/perry-api-manifest/src/entries/part_3.rs +++ b/crates/perry-api-manifest/src/entries/part_3.rs @@ -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. @@ -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` diff --git a/crates/perry-api-manifest/src/entries/part_4.rs b/crates/perry-api-manifest/src/entries/part_4.rs index fa8269c7e2..c2ea42014a 100644 --- a/crates/perry-api-manifest/src/entries/part_4.rs +++ b/crates/perry-api-manifest/src/entries/part_4.rs @@ -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. --- diff --git a/crates/perry-codegen/tests/manifest_consistency.rs b/crates/perry-codegen/tests/manifest_consistency.rs index 38a8ed56cb..a0ee87a780 100644 --- a/crates/perry-codegen/tests/manifest_consistency.rs +++ b/crates/perry-codegen/tests/manifest_consistency.rs @@ -287,19 +287,36 @@ fn every_well_known_binding_has_manifest_entry() { let mut missing: Vec = Vec::new(); for line in toml.lines() { let line = line.trim(); - // `[bindings.]` or `[bindings."">]` (quoted form - // used for names with `/` or `.`). + // `[bindings.]` or `[bindings.""]` (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..upstream]` — those + // carry per-binding provenance metadata (version/sha256/repo), not + // a routed module name. Only the bare top-level `[bindings.]` + // 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; } diff --git a/docs/api/perry.d.ts b/docs/api/perry.d.ts index de0a0738c2..9cbf60421d 100644 --- a/docs/api/perry.d.ts +++ b/docs/api/perry.d.ts @@ -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 }; @@ -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; diff --git a/docs/src/api/reference.md b/docs/src/api/reference.md index 54fbc4e236..202fd18e3a 100644 --- a/docs/src/api/reference.md +++ b/docs/src/api/reference.md @@ -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 @@ -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) @@ -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`)* @@ -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 @@ -1821,6 +1828,16 @@ Total: 2912 entries across 123 modules. - `quit` — instance - `set` — instance +## `iovalkey` + +### Classes + +- `Redis` + +### Methods + +- `createClient` — module + ## `iroh` ### Methods @@ -1888,6 +1905,7 @@ Total: 2912 entries across 123 modules. - `delete` — instance - `get` — instance - `has` — instance +- `peek` — instance - `set` — instance - `size` — instance @@ -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 @@ -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`)*