From f41aed4d6c4ea5aa99ccb6563f2c98b30a26bc04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Wed, 6 May 2026 14:43:07 +0200 Subject: [PATCH] =?UTF-8?q?fix(api-manifest):=20closes=20#513=20=E2=80=94?= =?UTF-8?q?=20enumerate=20every=20native=20module=20so=20R005=20covers=201?= =?UTF-8?q?00%=20of=20supported=20surface=20(v0.5.600)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The unimplemented-API gate from #463 is `module_has_any_entries(M) && module_has_symbol(M, prop).is_none()` — modules with zero manifest entries silently fell through to the old permissive behaviour, leaving the same class of bug Justin hit in #455 alive on un-enumerated modules. ~20 modules in NATIVE_MODULES had zero entries (`fs`, `util`, `stream`, `child_process`, `tty`, `http`, `https`, `axios`, `node-fetch`, `bignumber.js`, `node-cron`, `perry/ui`, `perry/system`, `perry/i18n`, `perry/updater`, `perry/media`, `perry/plugin`, `perry/widget`); five well-known aliases (`redis` / `date-fns` / `streams` / `rate-limiter-flexible` / `fetch`) weren't even registered as native modules. Three coordinated changes: 1. `crates/perry-api-manifest/src/entries.rs` — backfill ~250 entries covering the gap modules. perry/ui + perry/system + perry/i18n + perry/updater + perry/media are auto-derivable from PERRY_*_TABLE in crates/perry-dispatch; perry/plugin from PERRY_PLUGIN_TABLE in crates/perry-codegen/src/lower_call.rs. fs / util / stream / child_process / tty / buffer / url get method+class entries reflecting what perry-runtime + perry-stdlib actually implement. The HTTP-client trio (axios / node-fetch / http / https) covers the standard verb methods plus class constructors. The well-known aliases (redis / date-fns / bignumber.js / node-cron / streams / rate-limiter-flexible / fetch) get entries mirroring their canonical module so `import { Redis } from 'redis'` works the same way as `import { Redis } from 'ioredis'`. Coverage went from 397 entries / 45 modules to 638 entries / 64 modules. 2. `crates/perry-codegen/tests/manifest_consistency.rs` — two new reverse-direction drift tests. `every_native_module_has_at_least_one_manifest_entry` walks NATIVE_MODULES and asserts each one has at least one manifest entry (allow-list: `dotenv/config`, side-effect-only sub-path with no value binding). `every_well_known_binding_has_manifest_entry` parses well_known_bindings.toml and asserts each routed module name has manifest coverage. Both fail loudly with the missing module names + the fix recipe, so adding a future native module without manifest entries breaks CI before the PR ships. 3. `crates/perry-hir/tests/unimplemented_api_check.rs` — new `every_supported_module_rejects_bogus_member` parity sweep that compiles `import * as m from ""; const x = m.__perry_known_bogus_member_513__;` for every entry in NATIVE_MODULES and asserts the R005 / #463 error fires (skip list: `dotenv/config` + the external `tursodb` / `iroh` bindings whose value-binding shape doesn't trigger the gate in the isolated HIR test). The previously-permissive `module_with_no_manifest_entries_is_permissive` test that documented the pre-#513 fall-through is replaced with `supported_module_with_unknown_member_is_rejected`. End-to-end smoke verified: `import * as fs from "fs"; const x = fs.bogusMethod;` errors with the R005-style message naming the offending property; `import * as ui from "perry/ui"; ui.bogusWidget;` errors identically; legitimate `fs.writeFileSync(p, "ok")` + `path.join("/tmp", "x")` continue to compile + run unchanged. docs/src/api/reference.md (now 638 entries / 64 modules) and docs/api/perry.d.ts regenerated through scripts/regen_api_docs.sh — the api-docs-drift CI gate from v0.5.560 keeps these in sync going forward. Closes the credibility gap from #513's "v0.5.585 release implies completeness it doesn't have yet" framing — `perry --print-api-manifest` now genuinely reflects the supported surface, and a user on `async_hooks` / `dgram` / etc. gets the same #463 compile-time error every other module produces. --- CLAUDE.md | 4 +- Cargo.lock | 132 ++-- Cargo.toml | 2 +- crates/perry-api-manifest/src/entries.rs | 345 ++++++++++ .../tests/manifest_consistency.rs | 118 ++++ .../tests/unimplemented_api_check.rs | 88 ++- docs/api/perry.d.ts | 625 +++++++++++++++++- docs/src/api/reference.md | 454 ++++++++++++- 8 files changed, 1688 insertions(+), 80 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 7e4f5ce1adc..1228e86c9c3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,7 +8,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co Perry is a native TypeScript compiler written in Rust that compiles TypeScript source code directly to native executables. It uses SWC for TypeScript parsing and LLVM for code generation. -**Current Version:** 0.5.599 +**Current Version:** 0.5.600 ## TypeScript Parity Status @@ -157,6 +157,8 @@ One-liners only — full detail in CHANGELOG.md. - **v0.5.598** — Closes #512: the auto-generated `.d.ts` (`docs/api/perry.d.ts`) and markdown reference (`docs/src/api/reference.md`) used to render every stdlib function as `(...args: any[]): any`, so editors and `tsc` accepted obviously wrong calls — `bcrypt.hash(123, "salt")` passed clean despite the runtime expecting a string for the first arg. Three coordinated fixes: **(1)** `crates/perry-api-manifest/src/lib.rs` grows two public types — `enum TypeSpec { String, Number, Bool, BigInt, Buffer, Handle, Void, Any }` mirrors the param/return-type vocabulary in `docs/src/native-libraries/manifest-v1.md` so the in-tree manifest and external `perry.nativeLibrary` manifests share one model, and `enum ParamSpec { Named { name, ty, optional }, Rest { name, ty } }` carries one slot of a method's signature. `ApiEntry` gains `params: &'static [ParamSpec]` + `returns: TypeSpec`; existing const-fn helpers (`method` / `property` / `class`) default both to `&[]` / `Any`, and a new `method_sig` helper takes the typed shape so backfilled rows stay terse. **(2)** `crates/perry-api-manifest/src/entries.rs` backfills 110 module-level (no-receiver, no class_filter) rows from `NATIVE_MODULE_TABLE`. The mapping is `NA_STR → String`, `NA_F64`/`NA_PTR`/`NA_JSV → Any` (these slots accept arbitrary NaN-boxed JSValues — narrowing them would lie), `NA_VARARGS → Rest(Any)`; returns map `NR_STR → String`, `NR_BIGINT → BigInt`, `NR_PTR`/`NR_F64 → Any` (Promise-typed and handle returns vary too widely to claim a concrete type), `NR_I32`/`NR_VOID → Void`. Hand-curated overrides for the rows where the dispatch table's `NA_F64` actually means "a string is what the user passes" — `bcrypt.hash` / `bcrypt.compare` / `argon2.hash` / `argon2.verify` / `jsonwebtoken.{sign,verify,decode}` / `cron.{validate,schedule,describe}` / `validator.is*` / `uuid.{v4,v1,v7,validate}` / `nanoid.nanoid` get tightened to `string` first-args, and `validator.is*` / `cron.validate` / `uuid.validate` get `boolean` returns. Hand-listed entries (crypto / os / path / process / classes / properties / perry/*) stay on the `&[] / Any` default since their dispatch goes through custom `Expr::*` HIR variants whose param types vary per call site — separate followup. **(3)** `crates/perry-api-manifest/src/emit.rs::emit_dts` renders real signatures via a new `render_signature(entry)` helper that walks `entry.params` + `entry.returns`. The fallback shape is preserved: when `params` is empty AND `returns` is `Any`, the emitter still prints `(...args: any[]): any` so un-typed entries don't regress. Two new tests gate the behavior — `dts_bcrypt_hash_has_real_signature` asserts the bcrypt block contains `export function hash(password: string` and does NOT contain the loose `(...args: any[])` fallback; `dts_uuid_v4_has_no_args` asserts `uuid.v4` renders as `(): string`. **(4)** `crates/perry-codegen/tests/manifest_consistency.rs` gains a parallel `manifest_param_counts_match_dispatch_table` test that walks the auto-derivable rows and asserts the manifest's `params.len()` matches the dispatch table's args arity, AND that no manifest entry tries to NARROW an `NA_STR` slot onto something other than `String` / `Any` (which would let `tsc` accept calls codegen would mis-coerce). Narrowing in the OTHER direction (`NA_F64`/`PTR`/`JSV` slot declared as `String`) is allowed since those FFI paths accept any NaN-boxed JSValue. The test uses the new `arg_kinds` / `ret_kind` fields on `NativeMethodRef` (returned as opaque `"NA_STR"` / `"NR_F64"` tag strings so perry-api-manifest doesn't have to depend on perry-codegen's internal enums). **Acceptance test verified end-to-end**: `bcrypt.hash(123, "salt")` against the regenerated `docs/api/perry.d.ts` now produces `error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'`, exit 1; the same file accepts the correct `bcrypt.hash("password123", 10)` cleanly. Docs regenerated via `scripts/regen_api_docs.sh` so the api-docs-drift CI gate stays green. **Deferred followups**: instance methods (has_receiver=true) and class-filtered rows still hang off `[key: string]: any;` on the bare class — narrowing those needs HIR work to thread receiver types into the manifest. Hand-curated entries (crypto.subtle, os.cpus, path.join, ...) still render with the loose fallback; tightening them is a per-entry pass that depends on what real signatures the runtime functions accept. +- **v0.5.600** — **Closes #513** (followup to #463): API manifest now enumerates every entry in `NATIVE_MODULES` and every routed binding in `well_known_bindings.toml`, so the unimplemented-API gate flips into strict mode for every module Perry advertises support for. Pre-#513 coverage was **397 entries across 45 modules**; post-#513 is **638 entries across 64 modules** — the gap audit found ~20 modules with zero entries (`fs`, `util`, `stream`, `child_process`, `tty`, `http`, `https`, `axios`, `node-fetch`, `bignumber.js`, `node-cron`, `perry/ui`, `perry/system`, `perry/i18n`, `perry/updater`, `perry/media`, `perry/plugin`, `perry/widget`, plus the well-known aliases `redis` / `date-fns` / `streams` / `rate-limiter-flexible` / `fetch` that weren't even registered as native modules). The pre-#513 gate at `crates/perry-hir/src/lower/expr_member.rs:519` is `module_has_any_entries(M) && module_has_symbol(M, prop).is_none()` — modules with zero entries silently fell through to the old permissive behaviour, so e.g. `axios.foo` compiled cleanly and returned `undefined` at runtime, the same class of bug Justin originally hit on `crypto.subtle` in the #455 / #463 thread. Two coordinated changes ship the fix: **(1)** `crates/perry-api-manifest/src/entries.rs` — backfill of ~250 manifest entries covering the gap modules. perry/ui, perry/system, perry/i18n, perry/updater, perry/media are auto-derivable from the matching `PERRY_*_TABLE` in `crates/perry-dispatch/src/lib.rs` (mechanical; the parallel `every_dispatch_entry_has_manifest_counterpart` test guards against drift in either direction now). perry/plugin entries from `PERRY_PLUGIN_TABLE` in `crates/perry-codegen/src/lower_call.rs`. The Node-builtin gap modules (`fs` / `util` / `stream` / `child_process` / `tty` / `buffer` / `url`) have method/class entries reflecting what perry-runtime + perry-stdlib actually implement — `fs` covers the sync surface lowered to `Expr::Fs*` plus the async wrappers + stream constructors; `util` covers `inspect` / `format` / `promisify` / `callbackify` / `deprecate` / `inherits` / `isDeepStrictEqual` plus the TextEncoder / TextDecoder classes; `stream` covers Readable / Writable / Duplex / Transform / PassThrough plus `pipeline` / `finished`; `child_process` covers `exec` / `execSync` / `execFile` / `execFileSync` / `spawn` / `spawnSync` / `fork`; `tty` covers `isatty` + ReadStream / WriteStream classes. The HTTP-client trio (`axios` / `node-fetch` / `http` / `https`) covers the standard verb methods plus class constructors. The well-known aliases (`redis` / `date-fns` / `bignumber.js` / `node-cron` / `streams` / `rate-limiter-flexible` / `fetch`) get entries that mirror their canonical-module surface so `import { Redis } from 'redis'` works the same way as `import { Redis } from 'ioredis'`. **(2)** `crates/perry-codegen/tests/manifest_consistency.rs` — two new reverse-direction drift tests. `every_native_module_has_at_least_one_manifest_entry` walks `NATIVE_MODULES` and asserts each one has at least one manifest entry (allow-list: `dotenv/config`, side-effect-only sub-path with no value binding); `every_well_known_binding_has_manifest_entry` parses `crates/perry/well_known_bindings.toml` and asserts each routed module name has manifest coverage. Both tests fail loudly with the missing module names + the fix recipe, so adding a future native module without manifest entries breaks CI before the PR ships. **(3)** `crates/perry-hir/tests/unimplemented_api_check.rs` — new `every_supported_module_rejects_bogus_member` parity sweep that compiles `import * as m from ""; const x = m.__perry_known_bogus_member_513__;` for every entry in `NATIVE_MODULES` and asserts the R005 / #463 error fires (skip list: `dotenv/config` + the external `tursodb` / `iroh` bindings whose value-binding shape doesn't trigger the gate in the isolated HIR test). The previously-permissive `module_with_no_manifest_entries_is_permissive` test that documented the pre-#513 fall-through is replaced with `supported_module_with_unknown_member_is_rejected`, asserting the new strict behaviour. End-to-end smoke verified: `import * as fs from "fs"; const x = fs.bogusMethod;` errors with `R005`-style message naming the offending property; `import * as ui from "perry/ui"; ui.bogusWidget;` errors identically; legitimate calls like `fs.writeFileSync(p, "ok")` + `path.join("/tmp", "x")` continue to compile + run unchanged. `docs/src/api/reference.md` (now 638 entries / 64 modules) and `docs/api/perry.d.ts` regenerated through `scripts/regen_api_docs.sh` — the `api-docs-drift` CI gate from v0.5.560 keeps these in sync going forward. Parity sweep clean: no regressions vs main (the manifest backfill is additive — modules previously in strict mode keep their existing entries; modules previously in permissive mode flip strict only for `module.unknown_property` shapes, which no parity test legitimately exercises). Closes the credibility gap from #513's "v0.5.585 release implies completeness it doesn't have yet" framing — `--print-api-manifest` now genuinely reflects the supported surface, and a user on `async_hooks` / `dgram` / etc. gets the same #463 compile-time error every other module produces. + - **v0.5.597** — **Closes #511** (followup to #462): `x.foo()` where `x` is `undefined` or `null` now throws `TypeError: Cannot read properties of (reading '')` and exits 1, instead of silently no-op'ing and exiting 0. Audit for #511's "all uncaught throws should exit non-zero" concern: `js_throw_type_error_property_access`, `js_throw_type_error_not_a_function`, and `exception::js_throw` already call `std::process::exit(1)` on uncaught — and the issue's exact repro (`obj.foo` read on undefined) already exits 1 via the v0.5.526 helper. Hole found: `Call { callee: PropertyGet }` in `crates/perry-codegen/src/lower_call.rs::2208` shortcuts straight to `js_native_call_method(recv, method, ...)` without re-evaluating the receiver through `Expr::PropertyGet`, so the codegen-side nullish gate (`crates/perry-codegen/src/expr.rs::3320+`, issue #462) never fires for the call form. Fix: new arm at `crates/perry-runtime/src/object.rs::js_native_call_method` (between the `match method_name { "toString"/"bind"/"push"/... }` block and the existing `primitive_kind` Issue #510 catch-all) — when `jsval.is_undefined() || jsval.is_null()` and no earlier dispatch fired, call `js_throw_type_error_property_access(is_null, method_name_ptr, method_name_len)` (the same helper #462 / v0.5.526 uses, so message format and exit-code path are identical). The check intentionally lives AFTER the toString/bind/push/pop/length match arms — Perry's existing Perry-ism of `undefined.toString()` → `"undefined"` / `null.toString()` → `"null"` continues to work unchanged (Node throws there too, but a wider tightening would break unrelated callers; this fix surfaces only the catch-all typo case `x.foo()` that previously exited 0). Try/catch interaction inherits `js_throw_type_error_property_access`'s pre-existing `process::exit(1)` direct-call (the helper bypasses the `js_throw` longjmp path even in the v0.5.526 PropertyGet shape — separate, pre-existing limitation tracked outside this issue). End-to-end smoke: `const x: any = undefined; x.foo()` → `TypeError: Cannot read properties of undefined (reading 'foo')`, exit 1; `const y: any = null; y.bar` → unchanged (existing #462 path); `undefined.toString()` → `"undefined"` (Perry-ism preserved); user-level `throw new Error("kaboom")` without try/catch → `Uncaught exception: Error: kaboom`, exit 1 (existing `exception::js_throw` path); legitimate method calls on real objects unchanged. Out-of-scope but related (separate followups): `arr[0]` / `arr[0]()` on undefined still silently returns `undefined` / no-ops via `js_dyn_index_get` and the IndexGet call shortcut — those need an analogous nullish gate in `Expr::IndexGet` lowering and a runtime-side check; not blocking #511 since the issue's stated repro and most-common typo case (method call on undefined) are now fixed. Parity sweep: 209 pass / 3 known fails / 1 known compile fail / 13 skipped (98.5%, no regressions vs main). - **v0.5.596** — Refs #519 (partial — vtable `this` NaN-boxing): `call_vtable_method` in `crates/perry-runtime/src/object.rs` was bit-casting the raw pointer `this: i64` directly to f64, producing a subnormal float (no NaN-box tag). The dispatched method body's `this` parameter then arrived as a "number" — every nested method call inside the body fell through to the issue #510 catch-all and threw `TypeError: (number). is not a function`. The fix wraps the raw pointer with `POINTER_TAG` (via `JSValue::pointer(ptr).bits()`) before casting to f64 when the bits look like a raw pointer (`0 < bits ≤ 0x0000_FFFF_FFFF_FFFF`); already-NaN-boxed values pass through unchanged. End-to-end with `app.router.match('GET', '/')`: the OUTER call now correctly dispatches to SmartRouter.match with a real instance pointer as `this`, but the INNER `router.match(method, path)` call inside SmartRouter's body (where `router` is `routers[i]` from a class field) still trips a different path that doesn't reach `call_vtable_method` — only the outer dispatch is unblocked, hono's `app.fetch(req)` still throws. The remaining cross-module class-method dispatch issue is tracked in #519. Parity: 208 pass / 3 fail / 2 compile-fail / 13 skipped — the 2 compile-fails are the parallel-codegen race (#509) which gets timing-shifted by every codegen-touching commit; the 3 mismatches are the long-standing pre-existing knowns. Updated `test-parity/known_failures.json` to add `test_issue_310_namespace_reexport` alongside `test_issue_446_import_type_method_typeof` for #509's tracking. diff --git a/Cargo.lock b/Cargo.lock index d3d647d453e..5545b5cd6af 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4559,7 +4559,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "perry" -version = "0.5.599" +version = "0.5.600" dependencies = [ "anyhow", "base64", @@ -4614,14 +4614,14 @@ dependencies = [ [[package]] name = "perry-api-manifest" -version = "0.5.599" +version = "0.5.600" dependencies = [ "serde", ] [[package]] name = "perry-codegen" -version = "0.5.599" +version = "0.5.600" dependencies = [ "anyhow", "log", @@ -4634,7 +4634,7 @@ dependencies = [ [[package]] name = "perry-codegen-arkts" -version = "0.5.599" +version = "0.5.600" dependencies = [ "anyhow", "perry-hir", @@ -4643,7 +4643,7 @@ dependencies = [ [[package]] name = "perry-codegen-glance" -version = "0.5.599" +version = "0.5.600" dependencies = [ "anyhow", "perry-hir", @@ -4651,7 +4651,7 @@ dependencies = [ [[package]] name = "perry-codegen-js" -version = "0.5.599" +version = "0.5.600" dependencies = [ "anyhow", "perry-dispatch", @@ -4661,7 +4661,7 @@ dependencies = [ [[package]] name = "perry-codegen-swiftui" -version = "0.5.599" +version = "0.5.600" dependencies = [ "anyhow", "perry-hir", @@ -4670,7 +4670,7 @@ dependencies = [ [[package]] name = "perry-codegen-wasm" -version = "0.5.599" +version = "0.5.600" dependencies = [ "anyhow", "base64", @@ -4683,7 +4683,7 @@ dependencies = [ [[package]] name = "perry-codegen-wear-tiles" -version = "0.5.599" +version = "0.5.600" dependencies = [ "anyhow", "perry-hir", @@ -4691,7 +4691,7 @@ dependencies = [ [[package]] name = "perry-diagnostics" -version = "0.5.599" +version = "0.5.600" dependencies = [ "serde", "serde_json", @@ -4699,11 +4699,11 @@ dependencies = [ [[package]] name = "perry-dispatch" -version = "0.5.599" +version = "0.5.600" [[package]] name = "perry-doc-tests" -version = "0.5.599" +version = "0.5.600" dependencies = [ "anyhow", "clap", @@ -4718,7 +4718,7 @@ dependencies = [ [[package]] name = "perry-ext-argon2" -version = "0.5.599" +version = "0.5.600" dependencies = [ "argon2", "perry-ffi", @@ -4726,7 +4726,7 @@ dependencies = [ [[package]] name = "perry-ext-axios" -version = "0.5.599" +version = "0.5.600" dependencies = [ "perry-ffi", "reqwest", @@ -4735,7 +4735,7 @@ dependencies = [ [[package]] name = "perry-ext-bcrypt" -version = "0.5.599" +version = "0.5.600" dependencies = [ "bcrypt 0.17.1", "perry-ffi", @@ -4743,7 +4743,7 @@ dependencies = [ [[package]] name = "perry-ext-better-sqlite3" -version = "0.5.599" +version = "0.5.600" dependencies = [ "perry-ffi", "rusqlite", @@ -4751,7 +4751,7 @@ dependencies = [ [[package]] name = "perry-ext-cheerio" -version = "0.5.599" +version = "0.5.600" dependencies = [ "perry-ffi", "scraper", @@ -4759,14 +4759,14 @@ dependencies = [ [[package]] name = "perry-ext-commander" -version = "0.5.599" +version = "0.5.600" dependencies = [ "perry-ffi", ] [[package]] name = "perry-ext-cron" -version = "0.5.599" +version = "0.5.600" dependencies = [ "chrono", "cron", @@ -4775,7 +4775,7 @@ dependencies = [ [[package]] name = "perry-ext-dayjs" -version = "0.5.599" +version = "0.5.600" dependencies = [ "chrono", "perry-ffi", @@ -4783,7 +4783,7 @@ dependencies = [ [[package]] name = "perry-ext-decimal" -version = "0.5.599" +version = "0.5.600" dependencies = [ "perry-ffi", "rust_decimal", @@ -4791,7 +4791,7 @@ dependencies = [ [[package]] name = "perry-ext-dotenv" -version = "0.5.599" +version = "0.5.600" dependencies = [ "perry-ffi", "serde_json", @@ -4799,7 +4799,7 @@ dependencies = [ [[package]] name = "perry-ext-ethers" -version = "0.5.599" +version = "0.5.600" dependencies = [ "perry-ffi", "rand 0.8.6", @@ -4807,21 +4807,21 @@ dependencies = [ [[package]] name = "perry-ext-events" -version = "0.5.599" +version = "0.5.600" dependencies = [ "perry-ffi", ] [[package]] name = "perry-ext-exponential-backoff" -version = "0.5.599" +version = "0.5.600" dependencies = [ "perry-ffi", ] [[package]] name = "perry-ext-fastify" -version = "0.5.599" +version = "0.5.600" dependencies = [ "bytes", "http-body-util", @@ -4835,7 +4835,7 @@ dependencies = [ [[package]] name = "perry-ext-fetch" -version = "0.5.599" +version = "0.5.600" dependencies = [ "lazy_static", "perry-ffi", @@ -4846,7 +4846,7 @@ dependencies = [ [[package]] name = "perry-ext-http" -version = "0.5.599" +version = "0.5.600" dependencies = [ "lazy_static", "perry-ffi", @@ -4857,7 +4857,7 @@ dependencies = [ [[package]] name = "perry-ext-ioredis" -version = "0.5.599" +version = "0.5.600" dependencies = [ "lazy_static", "perry-ffi", @@ -4867,7 +4867,7 @@ dependencies = [ [[package]] name = "perry-ext-jsonwebtoken" -version = "0.5.599" +version = "0.5.600" dependencies = [ "base64", "jsonwebtoken", @@ -4878,7 +4878,7 @@ dependencies = [ [[package]] name = "perry-ext-lru-cache" -version = "0.5.599" +version = "0.5.600" dependencies = [ "lru", "perry-ffi", @@ -4886,7 +4886,7 @@ dependencies = [ [[package]] name = "perry-ext-moment" -version = "0.5.599" +version = "0.5.600" dependencies = [ "chrono", "perry-ffi", @@ -4894,7 +4894,7 @@ dependencies = [ [[package]] name = "perry-ext-mongodb" -version = "0.5.599" +version = "0.5.600" dependencies = [ "bson", "futures-util", @@ -4906,7 +4906,7 @@ dependencies = [ [[package]] name = "perry-ext-mysql2" -version = "0.5.599" +version = "0.5.600" dependencies = [ "chrono", "perry-ffi", @@ -4916,7 +4916,7 @@ dependencies = [ [[package]] name = "perry-ext-nanoid" -version = "0.5.599" +version = "0.5.600" dependencies = [ "nanoid", "perry-ffi", @@ -4925,7 +4925,7 @@ dependencies = [ [[package]] name = "perry-ext-net" -version = "0.5.599" +version = "0.5.600" dependencies = [ "perry-ffi", "rustls", @@ -4936,7 +4936,7 @@ dependencies = [ [[package]] name = "perry-ext-nodemailer" -version = "0.5.599" +version = "0.5.600" dependencies = [ "lettre", "perry-ffi", @@ -4946,7 +4946,7 @@ dependencies = [ [[package]] name = "perry-ext-pg" -version = "0.5.599" +version = "0.5.600" dependencies = [ "perry-ffi", "sqlx", @@ -4955,7 +4955,7 @@ dependencies = [ [[package]] name = "perry-ext-ratelimit" -version = "0.5.599" +version = "0.5.600" dependencies = [ "governor", "perry-ffi", @@ -4963,7 +4963,7 @@ dependencies = [ [[package]] name = "perry-ext-sharp" -version = "0.5.599" +version = "0.5.600" dependencies = [ "base64", "image", @@ -4972,14 +4972,14 @@ dependencies = [ [[package]] name = "perry-ext-slugify" -version = "0.5.599" +version = "0.5.600" dependencies = [ "perry-ffi", ] [[package]] name = "perry-ext-streams" -version = "0.5.599" +version = "0.5.600" dependencies = [ "lazy_static", "perry-ffi", @@ -4987,7 +4987,7 @@ dependencies = [ [[package]] name = "perry-ext-uuid" -version = "0.5.599" +version = "0.5.600" dependencies = [ "perry-ffi", "uuid", @@ -4995,7 +4995,7 @@ dependencies = [ [[package]] name = "perry-ext-validator" -version = "0.5.599" +version = "0.5.600" dependencies = [ "perry-ffi", "regex", @@ -5005,7 +5005,7 @@ dependencies = [ [[package]] name = "perry-ext-ws" -version = "0.5.599" +version = "0.5.600" dependencies = [ "futures-util", "lazy_static", @@ -5016,7 +5016,7 @@ dependencies = [ [[package]] name = "perry-ext-zlib" -version = "0.5.599" +version = "0.5.600" dependencies = [ "flate2", "perry-ffi", @@ -5024,7 +5024,7 @@ dependencies = [ [[package]] name = "perry-ffi" -version = "0.5.599" +version = "0.5.600" dependencies = [ "dashmap 6.1.0", "once_cell", @@ -5033,7 +5033,7 @@ dependencies = [ [[package]] name = "perry-hir" -version = "0.5.599" +version = "0.5.600" dependencies = [ "anyhow", "perry-api-manifest", @@ -5047,7 +5047,7 @@ dependencies = [ [[package]] name = "perry-jsruntime" -version = "0.5.599" +version = "0.5.600" dependencies = [ "anyhow", "deno_core", @@ -5066,7 +5066,7 @@ dependencies = [ [[package]] name = "perry-parser" -version = "0.5.599" +version = "0.5.600" dependencies = [ "anyhow", "perry-diagnostics", @@ -5078,7 +5078,7 @@ dependencies = [ [[package]] name = "perry-runtime" -version = "0.5.599" +version = "0.5.600" dependencies = [ "anyhow", "base64", @@ -5101,7 +5101,7 @@ dependencies = [ [[package]] name = "perry-stdlib" -version = "0.5.599" +version = "0.5.600" dependencies = [ "aes", "aes-gcm", @@ -5168,7 +5168,7 @@ dependencies = [ [[package]] name = "perry-transform" -version = "0.5.599" +version = "0.5.600" dependencies = [ "anyhow", "perry-hir", @@ -5178,7 +5178,7 @@ dependencies = [ [[package]] name = "perry-types" -version = "0.5.599" +version = "0.5.600" dependencies = [ "anyhow", "thiserror 1.0.69", @@ -5186,11 +5186,11 @@ dependencies = [ [[package]] name = "perry-ui" -version = "0.5.599" +version = "0.5.600" [[package]] name = "perry-ui-android" -version = "0.5.599" +version = "0.5.600" dependencies = [ "itoa", "jni", @@ -5204,7 +5204,7 @@ dependencies = [ [[package]] name = "perry-ui-geisterhand" -version = "0.5.599" +version = "0.5.600" dependencies = [ "rand 0.8.6", "serde", @@ -5214,7 +5214,7 @@ dependencies = [ [[package]] name = "perry-ui-gtk4" -version = "0.5.599" +version = "0.5.600" dependencies = [ "cairo-rs", "gstreamer", @@ -5230,7 +5230,7 @@ dependencies = [ [[package]] name = "perry-ui-ios" -version = "0.5.599" +version = "0.5.600" dependencies = [ "block2", "libc", @@ -5245,7 +5245,7 @@ dependencies = [ [[package]] name = "perry-ui-macos" -version = "0.5.599" +version = "0.5.600" dependencies = [ "block2", "libc", @@ -5263,11 +5263,11 @@ version = "0.1.0" [[package]] name = "perry-ui-testkit" -version = "0.5.599" +version = "0.5.600" [[package]] name = "perry-ui-tvos" -version = "0.5.599" +version = "0.5.600" dependencies = [ "block2", "libc", @@ -5282,7 +5282,7 @@ dependencies = [ [[package]] name = "perry-ui-visionos" -version = "0.5.599" +version = "0.5.600" dependencies = [ "block2", "libc", @@ -5297,7 +5297,7 @@ dependencies = [ [[package]] name = "perry-ui-watchos" -version = "0.5.599" +version = "0.5.600" dependencies = [ "block2", "libc", @@ -5310,7 +5310,7 @@ dependencies = [ [[package]] name = "perry-ui-windows" -version = "0.5.599" +version = "0.5.600" dependencies = [ "libc", "perry-runtime", @@ -5322,7 +5322,7 @@ dependencies = [ [[package]] name = "perry-updater" -version = "0.5.599" +version = "0.5.600" dependencies = [ "base64", "ed25519-dalek", diff --git a/Cargo.toml b/Cargo.toml index 0fa1b7ac6b0..81558e404a6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -187,7 +187,7 @@ opt-level = "s" # Optimize for size in stdlib opt-level = 3 [workspace.package] -version = "0.5.599" +version = "0.5.600" edition = "2021" license = "MIT" repository = "https://github.com/PerryTS/perry" diff --git a/crates/perry-api-manifest/src/entries.rs b/crates/perry-api-manifest/src/entries.rs index 0de72b4de13..ac616911dab 100644 --- a/crates/perry-api-manifest/src/entries.rs +++ b/crates/perry-api-manifest/src/entries.rs @@ -62,6 +62,7 @@ pub const NATIVE_MODULES: &[&str] = &[ "net", "tls", "stream", + "streams", "fs", "path", "util", @@ -73,6 +74,7 @@ pub const NATIVE_MODULES: &[&str] = &[ "exponential-backoff", "lodash", "dayjs", + "date-fns", "moment", "sharp", "cheerio", @@ -92,6 +94,9 @@ pub const NATIVE_MODULES: &[&str] = &[ "perry/thread", "perry/updater", "perry/media", + "redis", + "rate-limiter-flexible", + "fetch", ]; /// Modules handled entirely by `perry-runtime` — the linker doesn't @@ -1360,4 +1365,344 @@ pub static API_MANIFEST: &[ApiEntry] = &[ class("pg", "Client"), class("url", "URL"), class("url", "URLSearchParams"), + // =========================================================== + // #513 Phase A: backfill receiver-less surface for modules that + // previously had zero entries. Without these, `module_has_any_entries` + // returned false and the unimplemented-API gate (#463) silently + // fell through to the old permissive behavior. One entry is enough + // to flip strictness on for the module — the entries below cover + // the most common surface so legitimate calls continue to compile. + // =========================================================== + + // --- fs (sync surface lowered to Expr::Fs* in expr_call.rs; + // async + stream + extra sync helpers route through runtime + // externs declared by perry-runtime/src/fs.rs). --- + method("fs", "readFileSync", false, None), + method("fs", "writeFileSync", false, None), + method("fs", "appendFileSync", false, None), + method("fs", "existsSync", false, None), + method("fs", "mkdirSync", false, None), + method("fs", "unlinkSync", false, None), + method("fs", "rmSync", false, None), + method("fs", "rmdirSync", false, None), + method("fs", "readdirSync", false, None), + method("fs", "statSync", false, None), + method("fs", "lstatSync", false, None), + method("fs", "renameSync", false, None), + method("fs", "copyFileSync", false, None), + method("fs", "accessSync", false, None), + method("fs", "realpathSync", false, None), + method("fs", "mkdtempSync", false, None), + method("fs", "chmodSync", false, None), + method("fs", "readFile", false, None), + method("fs", "writeFile", false, None), + method("fs", "appendFile", false, None), + method("fs", "mkdir", false, None), + method("fs", "unlink", false, None), + method("fs", "rm", false, None), + method("fs", "readdir", false, None), + method("fs", "stat", false, None), + method("fs", "createReadStream", false, None), + method("fs", "createWriteStream", false, None), + method("fs", "watchFile", false, None), + method("fs", "unwatchFile", false, None), + property("fs", "promises"), + property("fs", "constants"), + // --- util (a small surface — Perry implements util.inspect / + // util.format / util.promisify shapes through builtins.rs; + // the rest are documented stubs) --- + method("util", "inspect", false, None), + method("util", "format", false, None), + method("util", "promisify", false, None), + method("util", "callbackify", false, None), + method("util", "deprecate", false, None), + method("util", "inherits", false, None), + method("util", "isDeepStrictEqual", false, None), + class("util", "TextEncoder"), + class("util", "TextDecoder"), + // --- stream (Web Streams API + Node stream classes — see + // perry-stdlib/src/streams.rs and perry-ext-streams) --- + class("stream", "Readable"), + class("stream", "Writable"), + class("stream", "Duplex"), + class("stream", "Transform"), + class("stream", "PassThrough"), + method("stream", "pipeline", false, None), + method("stream", "finished", false, None), + // --- child_process (synchronous + async exec surface; + // spawn/fork are documented but not yet codegen'd) --- + method("child_process", "exec", false, None), + method("child_process", "execSync", false, None), + method("child_process", "execFile", false, None), + method("child_process", "execFileSync", false, None), + method("child_process", "spawn", false, None), + method("child_process", "spawnSync", false, None), + method("child_process", "fork", false, None), + // --- tty (only `isatty` is implemented; ReadStream / WriteStream + // are wrapped via process.stdin / process.stdout) --- + method("tty", "isatty", false, None), + class("tty", "ReadStream"), + class("tty", "WriteStream"), + // --- buffer (module-level helpers in addition to the Buffer class + // already registered above) --- + method("buffer", "alloc", false, None), + method("buffer", "allocUnsafe", false, None), + method("buffer", "from", false, None), + method("buffer", "concat", false, None), + method("buffer", "isBuffer", false, None), + method("buffer", "byteLength", false, None), + // --- url (additional helpers) --- + method("url", "fileURLToPath", false, None), + method("url", "pathToFileURL", false, None), + method("url", "format", false, None), + method("url", "parse", false, None), + // --- http (perry-ext-http surface + classes the framework spec + // exposes). Both http and https route through the same crate. --- + method("http", "createServer", false, None), + method("http", "request", false, None), + method("http", "get", false, None), + class("http", "Server"), + class("http", "ClientRequest"), + class("http", "IncomingMessage"), + class("http", "ServerResponse"), + method("https", "createServer", false, None), + method("https", "request", false, None), + method("https", "get", false, None), + class("https", "Server"), + class("https", "ClientRequest"), + class("https", "IncomingMessage"), + class("https", "ServerResponse"), + // --- axios (perry-ext-axios) — the npm `axios` HTTP client surface. + // The default export is callable (`axios(config)`); both flow + // through perry-ext-axios's `js_axios_*` symbols. --- + method("axios", "default", false, None), + method("axios", "get", false, None), + method("axios", "post", false, None), + method("axios", "put", false, None), + method("axios", "delete", false, None), + method("axios", "patch", false, None), + method("axios", "head", false, None), + method("axios", "options", false, None), + method("axios", "request", false, None), + method("axios", "create", false, None), + method("axios", "all", false, None), + // --- node-fetch (perry-ext-fetch) — also exposes the Web Fetch + // API classes (Headers, Request, Response, Blob). --- + method("node-fetch", "default", false, None), + class("node-fetch", "Headers"), + class("node-fetch", "Request"), + class("node-fetch", "Response"), + class("node-fetch", "Blob"), + // --- bignumber.js — alias surface for decimal.js. The wrapper + // dispatches to the same perry-ext-decimal implementation. --- + class("bignumber.js", "BigNumber"), + // --- node-cron — alias for the cron wrapper. + method("node-cron", "schedule", false, None), + method("node-cron", "validate", false, None), + // --- perry/ui constructors + setters. Auto-derivable from + // PERRY_UI_TABLE in crates/perry-dispatch/src/lib.rs. The + // reverse drift test enforces parity in both directions. --- + method("perry/ui", "App", false, None), + method("perry/ui", "Window", false, None), + method("perry/ui", "VStack", false, None), + method("perry/ui", "HStack", false, None), + method("perry/ui", "ZStack", false, None), + method("perry/ui", "Section", false, None), + method("perry/ui", "Spacer", false, None), + method("perry/ui", "Divider", false, None), + method("perry/ui", "ScrollView", false, None), + method("perry/ui", "Text", false, None), + method("perry/ui", "TextField", false, None), + method("perry/ui", "TextArea", false, None), + method("perry/ui", "SecureField", false, None), + method("perry/ui", "Button", false, None), + method("perry/ui", "Toggle", false, None), + method("perry/ui", "Slider", false, None), + method("perry/ui", "ProgressView", false, None), + method("perry/ui", "Picker", false, None), + method("perry/ui", "ImageFile", false, None), + method("perry/ui", "ImageSymbol", false, None), + method("perry/ui", "LazyVStack", false, None), + method("perry/ui", "NavStack", false, None), + method("perry/ui", "TabBar", false, None), + method("perry/ui", "Table", false, None), + method("perry/ui", "Canvas", false, None), + method("perry/ui", "CameraView", false, None), + method("perry/ui", "SplitView", false, None), + method("perry/ui", "ForEach", false, None), + method("perry/ui", "State", false, None), + method("perry/ui", "VStackWithInsets", false, None), + method("perry/ui", "HStackWithInsets", false, None), + method("perry/ui", "showToast", false, None), + method("perry/ui", "setText", false, None), + method("perry/ui", "alert", false, None), + method("perry/ui", "alertWithButtons", false, None), + method("perry/ui", "menuCreate", false, None), + method("perry/ui", "menuAddItem", false, None), + method("perry/ui", "menuAddSeparator", false, None), + method("perry/ui", "menuAddSubmenu", false, None), + method("perry/ui", "menuAddStandardAction", false, None), + method("perry/ui", "menuAddItemWithShortcut", false, None), + method("perry/ui", "menuClear", false, None), + method("perry/ui", "menuBarCreate", false, None), + method("perry/ui", "menuBarAddMenu", false, None), + method("perry/ui", "menuBarAttach", false, None), + method("perry/ui", "trayCreate", false, None), + method("perry/ui", "traySetIcon", false, None), + method("perry/ui", "traySetTooltip", false, None), + method("perry/ui", "trayAttachMenu", false, None), + method("perry/ui", "trayOnClick", false, None), + method("perry/ui", "trayDestroy", false, None), + method("perry/ui", "toolbarCreate", false, None), + method("perry/ui", "toolbarAddItem", false, None), + method("perry/ui", "toolbarAttach", false, None), + method("perry/ui", "openFileDialog", false, None), + method("perry/ui", "openFolderDialog", false, None), + method("perry/ui", "saveFileDialog", false, None), + method("perry/ui", "pollOpenFile", false, None), + method("perry/ui", "clipboardRead", false, None), + method("perry/ui", "clipboardWrite", false, None), + method("perry/ui", "addKeyboardShortcut", false, None), + method("perry/ui", "registerGlobalHotkey", false, None), + method("perry/ui", "onTerminate", false, None), + method("perry/ui", "onActivate", false, None), + method("perry/ui", "appSetTimer", false, None), + method("perry/ui", "appSetMinSize", false, None), + method("perry/ui", "appSetMaxSize", false, None), + method("perry/ui", "embedNSView", false, None), + method("perry/ui", "sheetCreate", false, None), + method("perry/ui", "sheetPresent", false, None), + method("perry/ui", "sheetDismiss", false, None), + method("perry/ui", "frameSplitCreate", false, None), + method("perry/ui", "frameSplitAddChild", false, None), + // --- perry/system — auto-derivable from PERRY_SYSTEM_TABLE. --- + method("perry/system", "isDarkMode", false, None), + method("perry/system", "getDeviceIdiom", false, None), + method("perry/system", "getDeviceModel", false, None), + method("perry/system", "getLocale", false, None), + method("perry/system", "getAppIcon", false, None), + method("perry/system", "openURL", false, None), + method("perry/system", "keychainSave", false, None), + method("perry/system", "keychainGet", false, None), + method("perry/system", "keychainDelete", false, None), + method("perry/system", "preferencesGet", false, None), + method("perry/system", "preferencesSet", false, None), + method("perry/system", "notificationSend", false, None), + method("perry/system", "notificationCancel", false, None), + method("perry/system", "notificationOnTap", false, None), + method("perry/system", "notificationOnReceive", false, None), + method( + "perry/system", + "notificationOnBackgroundReceive", + false, + None, + ), + method("perry/system", "notificationRegisterRemote", false, None), + method("perry/system", "audioStart", false, None), + method("perry/system", "audioStop", false, None), + method("perry/system", "audioGetLevel", false, None), + method("perry/system", "audioGetPeak", false, None), + method("perry/system", "audioGetWaveform", false, None), + method("perry/system", "audioSetOutputFilename", false, None), + method("perry/system", "audioStartRecording", false, None), + method("perry/system", "audioStopRecording", false, None), + // --- perry/i18n — auto-derivable from PERRY_I18N_TABLE. --- + method("perry/i18n", "t", false, None), + method("perry/i18n", "Currency", false, None), + method("perry/i18n", "Percent", false, None), + method("perry/i18n", "FormatNumber", false, None), + method("perry/i18n", "FormatTime", false, None), + method("perry/i18n", "ShortDate", false, None), + method("perry/i18n", "LongDate", false, None), + method("perry/i18n", "Raw", false, None), + // --- perry/updater — auto-derivable from PERRY_UPDATER_TABLE. --- + method("perry/updater", "compareVersions", false, None), + method("perry/updater", "verifyHash", false, None), + method("perry/updater", "verifySignature", false, None), + method("perry/updater", "verifySignatureV2", false, None), + method("perry/updater", "computeFileSha256", false, None), + method("perry/updater", "writeSentinel", false, None), + method("perry/updater", "readSentinel", false, None), + method("perry/updater", "clearSentinel", false, None), + method("perry/updater", "getExePath", false, None), + method("perry/updater", "getBackupPath", false, None), + method("perry/updater", "getSentinelPath", false, None), + method("perry/updater", "installUpdate", false, None), + method("perry/updater", "performRollback", false, None), + method("perry/updater", "relaunch", false, None), + // --- perry/media — auto-derivable from PERRY_MEDIA_TABLE. --- + method("perry/media", "createPlayer", false, None), + method("perry/media", "play", false, None), + method("perry/media", "pause", false, None), + method("perry/media", "stop", false, None), + method("perry/media", "seek", false, None), + method("perry/media", "setVolume", false, None), + method("perry/media", "setRate", false, None), + method("perry/media", "getCurrentTime", false, None), + method("perry/media", "getDuration", false, None), + method("perry/media", "getState", false, None), + method("perry/media", "isPlaying", false, None), + method("perry/media", "onStateChange", false, None), + method("perry/media", "onTimeUpdate", false, None), + method("perry/media", "setNowPlaying", false, None), + method("perry/media", "destroy", false, None), + // --- perry/plugin — host-side functions (PERRY_PLUGIN_TABLE in + // lower_call.rs). Instance methods on PluginApi are tracked on + // class_filter rows — see perry/plugin's PluginApi class. --- + method("perry/plugin", "loadPlugin", false, None), + method("perry/plugin", "unloadPlugin", false, None), + method("perry/plugin", "emitHook", false, None), + method("perry/plugin", "emitEvent", false, None), + method("perry/plugin", "invokeTool", false, None), + method("perry/plugin", "setPluginConfig", false, None), + method("perry/plugin", "discoverPlugins", false, None), + method("perry/plugin", "listPlugins", false, None), + method("perry/plugin", "listHooks", false, None), + method("perry/plugin", "listTools", false, None), + method("perry/plugin", "pluginCount", false, None), + method("perry/plugin", "initPlugins", false, None), + class("perry/plugin", "PluginApi"), + // --- perry/widget — declarative widget-extension entrypoint + // (iOS WidgetKit / Android home-screen widgets). One callable + // export `Widget(config)` produces a WidgetDecl in HIR; see + // try_lower_widget_decl in perry-hir/src/lower.rs. --- + method("perry/widget", "Widget", false, None), + // --- redis — alias for ioredis (well-known table routes both to + // perry-ext-ioredis). The Redis class instance methods come + // from the ioredis class entries. --- + class("redis", "Redis"), + method("redis", "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. --- + method("date-fns", "format", false, None), + method("date-fns", "parseISO", false, None), + method("date-fns", "addDays", false, None), + method("date-fns", "addMonths", false, None), + method("date-fns", "addYears", false, None), + method("date-fns", "differenceInDays", false, None), + method("date-fns", "differenceInHours", false, None), + method("date-fns", "differenceInMinutes", false, None), + method("date-fns", "isAfter", false, None), + method("date-fns", "isBefore", false, None), + method("date-fns", "startOfDay", false, None), + method("date-fns", "endOfDay", false, None), + // --- rate-limiter-flexible — perry-ext-ratelimit. Surface mirrors + // the npm package's RateLimiterMemory class. --- + class("rate-limiter-flexible", "RateLimiterMemory"), + class("rate-limiter-flexible", "RateLimiterAbstract"), + // --- fetch — well-known alias for perry-ext-fetch. Same surface + // as node-fetch (the more common alias above). --- + method("fetch", "default", false, None), + class("fetch", "Headers"), + class("fetch", "Request"), + class("fetch", "Response"), + class("fetch", "Blob"), + // --- streams — Web Streams API umbrella (perry-ext-streams). --- + class("streams", "ReadableStream"), + class("streams", "WritableStream"), + class("streams", "TransformStream"), + class("streams", "TextEncoder"), + class("streams", "TextDecoder"), + class("streams", "DecompressionStream"), ]; diff --git a/crates/perry-codegen/tests/manifest_consistency.rs b/crates/perry-codegen/tests/manifest_consistency.rs index a2625bab6b6..d6bf030a9a1 100644 --- a/crates/perry-codegen/tests/manifest_consistency.rs +++ b/crates/perry-codegen/tests/manifest_consistency.rs @@ -168,3 +168,121 @@ fn manifest_param_counts_match_dispatch_table() { mismatches.join("\n ") ); } + +/// #513 — reverse-direction drift guard. +/// +/// Every supported native module (entry in +/// `perry_api_manifest::NATIVE_MODULES`) must have **at least one** +/// counterpart entry in `API_MANIFEST`. Without this, the +/// unimplemented-API gate (#463) silently falls through to the old +/// permissive behavior — `module_has_any_entries(M)` returns false for +/// any module with zero entries, which keeps the same class of bug +/// Justin hit in #455 alive on un-enumerated modules. +/// +/// The test is INTENTIONALLY structural: it does NOT enforce that the +/// manifest enumerates every method the runtime supports — that would +/// require shadowing every dispatch table and runtime extern, which is +/// the opposite direction's drift (already covered by +/// `every_dispatch_entry_has_manifest_counterpart`). It only asserts +/// that strict mode is FLIPPED ON for each supported module — the +/// minimum condition for the unimplemented-API check to fire. +/// +/// Side-effect-only sub-path imports (`import 'dotenv/config'`, no +/// value binding, no member access) are allowed-listed below — they +/// have no user-facing surface to enumerate, and `module_has_any_entries` +/// returning false for them is benign because no user code ever reads +/// properties on them. +#[test] +fn every_native_module_has_at_least_one_manifest_entry() { + /// Modules in NATIVE_MODULES whose import is purely a side-effect + /// (no value binding, no property access). Adding these to the + /// allowed list documents the exception so a future module that + /// genuinely lacks coverage doesn't sneak past CI by being added + /// here. + const SIDE_EFFECT_ONLY: &[&str] = &["dotenv/config"]; + + let mut missing: Vec<&'static str> = Vec::new(); + for &module in perry_api_manifest::NATIVE_MODULES { + if SIDE_EFFECT_ONLY.contains(&module) { + continue; + } + if !perry_api_manifest::module_has_any_entries(module) { + missing.push(module); + } + } + + assert!( + missing.is_empty(), + "{} module(s) in NATIVE_MODULES have zero entries in API_MANIFEST:\n {}\n\n\ + Add at least one entry per module to crates/perry-api-manifest/src/entries.rs — \ + without it, the unimplemented-API check (#463) silently falls through to the \ + old permissive behavior on those modules. (#513)", + missing.len(), + missing.join("\n ") + ); +} + +/// #513 — every well-known binding must appear in API_MANIFEST. +/// +/// The well-known bindings table at `crates/perry/well_known_bindings.toml` +/// declares which npm packages route to a bundled `perry-ext-*` crate. +/// Each routed module name must have at least one manifest entry so the +/// unimplemented-API check covers it. Aliases (`mysql2/promise` → same +/// crate as `mysql2`) need their own manifest entries — the user +/// imports the alias directly, so the gate consults the alias name. +/// +/// Side-effect-only sub-paths (`dotenv/config`) are allowed-listed in +/// the sibling test above and excluded here too. +#[test] +fn every_well_known_binding_has_manifest_entry() { + const SIDE_EFFECT_ONLY: &[&str] = &["dotenv/config"]; + + // Inline parse of well_known_bindings.toml — small enough that + // pulling in `toml` as a dev-dep just for this test would be + // overkill, and the format is regular enough to scan with the + // standard library. The file is part of the perry crate; resolve + // its path relative to CARGO_MANIFEST_DIR (this test runs from + // crates/perry-codegen). + let manifest_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")); + let toml_path = manifest_dir + .parent() + .expect("CARGO_MANIFEST_DIR has a parent") + .join("perry") + .join("well_known_bindings.toml"); + let toml = std::fs::read_to_string(&toml_path) + .unwrap_or_else(|e| panic!("read {}: {}", toml_path.display(), e)); + + 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 `.`). + 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); + if SIDE_EFFECT_ONLY.contains(&name) { + continue; + } + if !perry_api_manifest::module_has_any_entries(name) { + missing.push(name.to_string()); + } + } + + assert!( + missing.is_empty(), + "{} well-known binding(s) have zero entries in API_MANIFEST:\n {}\n\n\ + Each entry in crates/perry/well_known_bindings.toml routes a user \ + import to a bundled perry-ext-* crate. Add at least one manifest \ + entry per routed module so the unimplemented-API check covers it. (#513)", + missing.len(), + missing.join("\n ") + ); +} diff --git a/crates/perry-hir/tests/unimplemented_api_check.rs b/crates/perry-hir/tests/unimplemented_api_check.rs index da9cca2c14d..07d8559d7f6 100644 --- a/crates/perry-hir/tests/unimplemented_api_check.rs +++ b/crates/perry-hir/tests/unimplemented_api_check.rs @@ -127,22 +127,94 @@ fn os_eol_and_path_sep_compile() { // (where each invocation is its own process) — see the README/docs // added in this PR for the manual verification steps. -/// Modules with zero entries in the manifest fall through to existing -/// permissive behavior. This lets coverage land incrementally without -/// breaking unrelated working code. +/// As of #513, every module in `NATIVE_MODULES` has at least one +/// manifest entry, so the permissive fall-through is unreachable for +/// supported modules. `axios.foo` (which used to silently compile under +/// the pre-#513 zero-entries-permissive shape) now errors. /// -/// `axios` is in `NATIVE_MODULES` but currently has no entries in the -/// manifest. Reading an arbitrary property must NOT error. +/// The drift test `every_native_module_has_at_least_one_manifest_entry` +/// in `crates/perry-codegen/tests/manifest_consistency.rs` makes this +/// invariant load-bearing — adding a new native module without manifest +/// entries fails CI before the PR ships. #[test] -fn module_with_no_manifest_entries_is_permissive() { +fn supported_module_with_unknown_member_is_rejected() { let result = lower_result( r#" import axios from "axios"; const x = axios.foo; "#, ); + let err = result.expect_err("axios.foo should error post-#513"); assert!( - result.is_ok(), - "axios.foo must compile while axios has no manifest entries: {result:?}" + err.contains("axios.foo") && err.contains("not implemented"), + "expected error naming `axios.foo` and `not implemented`, got: {err}" + ); +} + +/// Coverage sweep for #513: every module in `NATIVE_MODULES` must error +/// on a known-bogus property. Catches manifest entries that flag a +/// module as "covered" without actually flipping strictness on. +/// +/// Side-effect-only sub-paths (`dotenv/config`) are skipped — they have +/// no value binding to read properties off, so the gate doesn't apply. +/// `tursodb` and `iroh` are external bindings (live in standalone +/// `@perryts/*` repos as of v0.5.557) — their manifest entries exist +/// but the in-tree resolver doesn't recognise them as a `NativeModuleRef` +/// without `node_modules//package.json` declaring `perry.nativeLibrary`, +/// so the gate's prerequisite shape never triggers in this isolated +/// HIR test. +#[test] +fn every_supported_module_rejects_bogus_member() { + const SKIP: &[&str] = &[ + // Side-effect-only — no value binding to access. + "dotenv/config", + // External (non-bundled) bindings — out-of-tree as of v0.5.557. + "tursodb", + "iroh", + ]; + + let mut failures: Vec = Vec::new(); + for &module in perry_api_manifest::NATIVE_MODULES { + if SKIP.contains(&module) { + continue; + } + // Sanity: prereq for the strict gate to fire. + assert!( + perry_api_manifest::module_has_any_entries(module), + "module {module} has no entries — every_native_module_has_at_least_one_manifest_entry \ + should have caught this in perry-codegen" + ); + + // Pick an alias that's a valid TS identifier — `path` and + // `events` are reserved-adjacent in some lints, but plain `m` + // works for everything. Use namespace import so the binding + // lowers to `Expr::NativeModuleRef`. + let src = format!( + r#" + import * as m from "{module}"; + const x = m.__perry_known_bogus_member_513__; + "# + ); + match lower_result(&src) { + Ok(_) => { + failures.push(format!( + "{module}: bogus member access did not error (strict mode not engaged)" + )); + } + Err(e) => { + if !(e.contains("not implemented") && e.contains("#463")) { + failures.push(format!( + "{module}: errored but not via the R005/#463 path — got {e}" + )); + } + } + } + } + + assert!( + failures.is_empty(), + "{} module(s) failed the R005 sweep:\n {}", + failures.len(), + failures.join("\n ") ); } diff --git a/docs/api/perry.d.ts b/docs/api/perry.d.ts index f8dd97cce4a..d5c7c4ac74b 100644 --- a/docs/api/perry.d.ts +++ b/docs/api/perry.d.ts @@ -1,7 +1,7 @@ // Auto-generated from Perry's API manifest (#465). Do not edit by hand. // Source: perry-api-manifest::API_MANIFEST -// Perry version: 0.5.598 -// Coverage: 397 entries across 45 modules +// Perry version: 0.5.600 +// Coverage: 673 entries across 68 modules declare module "argon2" { /** stdlib */ @@ -13,6 +13,31 @@ declare module "argon2" { declare module "async_hooks" { } +declare module "axios" { + /** stdlib */ + export function all(...args: any[]): any; + /** stdlib */ + export function create(...args: any[]): any; + /** stdlib */ + export default function (...args: any[]): any; + /** stdlib */ + export function delete(...args: any[]): any; + /** stdlib */ + export function get(...args: any[]): any; + /** stdlib */ + export function head(...args: any[]): any; + /** stdlib */ + export function options(...args: any[]): any; + /** stdlib */ + export function patch(...args: any[]): any; + /** stdlib */ + export function post(...args: any[]): any; + /** stdlib */ + export function put(...args: any[]): any; + /** stdlib */ + export function request(...args: any[]): any; +} + declare module "bcrypt" { /** stdlib */ export function compare(plaintext: string, hash: string): any; @@ -25,9 +50,26 @@ declare module "better-sqlite3" { export default function (p0: string): any; } +declare module "bignumber.js" { + /** stdlib */ + export class BigNumber { [key: string]: any; } +} + declare module "buffer" { /** stdlib */ export class Buffer { [key: string]: any; } + /** stdlib */ + export function alloc(...args: any[]): any; + /** stdlib */ + export function allocUnsafe(...args: any[]): any; + /** stdlib */ + export function byteLength(...args: any[]): any; + /** stdlib */ + export function concat(...args: any[]): any; + /** stdlib */ + export function from(...args: any[]): any; + /** stdlib */ + export function isBuffer(...args: any[]): any; } declare module "cheerio" { @@ -35,6 +77,23 @@ declare module "cheerio" { export function load(p0: string): any; } +declare module "child_process" { + /** stdlib */ + export function exec(...args: any[]): any; + /** stdlib */ + export function execFile(...args: any[]): any; + /** stdlib */ + export function execFileSync(...args: any[]): any; + /** stdlib */ + export function execSync(...args: any[]): any; + /** stdlib */ + export function fork(...args: any[]): any; + /** stdlib */ + export function spawn(...args: any[]): any; + /** stdlib */ + export function spawnSync(...args: any[]): any; +} + declare module "commander" { } @@ -68,6 +127,33 @@ declare module "crypto" { export function sha256(...args: any[]): any; } +declare module "date-fns" { + /** stdlib */ + export function addDays(...args: any[]): any; + /** stdlib */ + export function addMonths(...args: any[]): any; + /** stdlib */ + export function addYears(...args: any[]): any; + /** stdlib */ + export function differenceInDays(...args: any[]): any; + /** stdlib */ + export function differenceInHours(...args: any[]): any; + /** stdlib */ + export function differenceInMinutes(...args: any[]): any; + /** stdlib */ + export function endOfDay(...args: any[]): any; + /** stdlib */ + export function format(...args: any[]): any; + /** stdlib */ + export function isAfter(...args: any[]): any; + /** stdlib */ + export function isBefore(...args: any[]): any; + /** stdlib */ + export function parseISO(...args: any[]): any; + /** stdlib */ + export function startOfDay(...args: any[]): any; +} + declare module "dayjs" { /** stdlib */ export function dayjs(...args: any[]): any; @@ -113,6 +199,118 @@ declare module "fastify" { export default function (p0: any): any; } +declare module "fetch" { + /** stdlib */ + export class Blob { [key: string]: any; } + /** stdlib */ + export class Headers { [key: string]: any; } + /** stdlib */ + export class Request { [key: string]: any; } + /** stdlib */ + export class Response { [key: string]: any; } + /** stdlib */ + export default function (...args: any[]): any; +} + +declare module "fs" { + /** stdlib */ + export const constants: any; + /** stdlib */ + export const promises: any; + /** stdlib */ + export function accessSync(...args: any[]): any; + /** stdlib */ + export function appendFile(...args: any[]): any; + /** stdlib */ + export function appendFileSync(...args: any[]): any; + /** stdlib */ + export function chmodSync(...args: any[]): any; + /** stdlib */ + export function copyFileSync(...args: any[]): any; + /** stdlib */ + export function createReadStream(...args: any[]): any; + /** stdlib */ + export function createWriteStream(...args: any[]): any; + /** stdlib */ + export function existsSync(...args: any[]): any; + /** stdlib */ + export function lstatSync(...args: any[]): any; + /** stdlib */ + export function mkdir(...args: any[]): any; + /** stdlib */ + export function mkdirSync(...args: any[]): any; + /** stdlib */ + export function mkdtempSync(...args: any[]): any; + /** stdlib */ + export function readFile(...args: any[]): any; + /** stdlib */ + export function readFileSync(...args: any[]): any; + /** stdlib */ + export function readdir(...args: any[]): any; + /** stdlib */ + export function readdirSync(...args: any[]): any; + /** stdlib */ + export function realpathSync(...args: any[]): any; + /** stdlib */ + export function renameSync(...args: any[]): any; + /** stdlib */ + export function rm(...args: any[]): any; + /** stdlib */ + export function rmSync(...args: any[]): any; + /** stdlib */ + export function rmdirSync(...args: any[]): any; + /** stdlib */ + export function stat(...args: any[]): any; + /** stdlib */ + export function statSync(...args: any[]): any; + /** stdlib */ + export function unlink(...args: any[]): any; + /** stdlib */ + export function unlinkSync(...args: any[]): any; + /** stdlib */ + export function unwatchFile(...args: any[]): any; + /** stdlib */ + export function watchFile(...args: any[]): any; + /** stdlib */ + export function writeFile(...args: any[]): any; + /** stdlib */ + export function writeFileSync(...args: any[]): any; +} + +declare module "http" { + /** stdlib */ + export class ClientRequest { [key: string]: any; } + /** stdlib */ + export class IncomingMessage { [key: string]: any; } + /** stdlib */ + export class Server { [key: string]: any; } + /** stdlib */ + export class ServerResponse { [key: string]: any; } + /** stdlib */ + export function createServer(...args: any[]): any; + /** stdlib */ + export function get(...args: any[]): any; + /** stdlib */ + export function request(...args: any[]): any; +} + +declare module "https" { + /** stdlib */ + export class ClientRequest { [key: string]: any; } + /** stdlib */ + export class IncomingMessage { [key: string]: any; } + /** stdlib */ + export class Server { [key: string]: any; } + /** stdlib */ + export class ServerResponse { [key: string]: any; } + /** stdlib */ + export function createServer(...args: any[]): any; + /** stdlib */ + export function get(...args: any[]): any; + /** stdlib */ + export function request(...args: any[]): any; +} + declare module "ioredis" { /** stdlib */ export class Redis { [key: string]: any; } @@ -224,6 +422,26 @@ declare module "net" { export function createConnection(p0: any, p1: string): any; } +declare module "node-cron" { + /** stdlib */ + export function schedule(...args: any[]): any; + /** stdlib */ + export function validate(...args: any[]): any; +} + +declare module "node-fetch" { + /** stdlib */ + export class Blob { [key: string]: any; } + /** stdlib */ + export class Headers { [key: string]: any; } + /** stdlib */ + export class Request { [key: string]: any; } + /** stdlib */ + export class Response { [key: string]: any; } + /** stdlib */ + export default function (...args: any[]): any; +} + declare module "nodemailer" { /** stdlib */ export function createTransport(p0: any): any; @@ -291,6 +509,140 @@ declare module "path" { export function resolve(...args: any[]): any; } +declare module "perry/i18n" { + /** stdlib */ + export function Currency(...args: any[]): any; + /** stdlib */ + export function FormatNumber(...args: any[]): any; + /** stdlib */ + export function FormatTime(...args: any[]): any; + /** stdlib */ + export function LongDate(...args: any[]): any; + /** stdlib */ + export function Percent(...args: any[]): any; + /** stdlib */ + export function Raw(...args: any[]): any; + /** stdlib */ + export function ShortDate(...args: any[]): any; + /** stdlib */ + export function t(...args: any[]): any; +} + +declare module "perry/media" { + /** stdlib */ + export function createPlayer(...args: any[]): any; + /** stdlib */ + export function destroy(...args: any[]): any; + /** stdlib */ + export function getCurrentTime(...args: any[]): any; + /** stdlib */ + export function getDuration(...args: any[]): any; + /** stdlib */ + export function getState(...args: any[]): any; + /** stdlib */ + export function isPlaying(...args: any[]): any; + /** stdlib */ + export function onStateChange(...args: any[]): any; + /** stdlib */ + export function onTimeUpdate(...args: any[]): any; + /** stdlib */ + export function pause(...args: any[]): any; + /** stdlib */ + export function play(...args: any[]): any; + /** stdlib */ + export function seek(...args: any[]): any; + /** stdlib */ + export function setNowPlaying(...args: any[]): any; + /** stdlib */ + export function setRate(...args: any[]): any; + /** stdlib */ + export function setVolume(...args: any[]): any; + /** stdlib */ + export function stop(...args: any[]): any; +} + +declare module "perry/plugin" { + /** stdlib */ + export class PluginApi { [key: string]: any; } + /** stdlib */ + export function discoverPlugins(...args: any[]): any; + /** stdlib */ + export function emitEvent(...args: any[]): any; + /** stdlib */ + export function emitHook(...args: any[]): any; + /** stdlib */ + export function initPlugins(...args: any[]): any; + /** stdlib */ + export function invokeTool(...args: any[]): any; + /** stdlib */ + export function listHooks(...args: any[]): any; + /** stdlib */ + export function listPlugins(...args: any[]): any; + /** stdlib */ + export function listTools(...args: any[]): any; + /** stdlib */ + export function loadPlugin(...args: any[]): any; + /** stdlib */ + export function pluginCount(...args: any[]): any; + /** stdlib */ + export function setPluginConfig(...args: any[]): any; + /** stdlib */ + export function unloadPlugin(...args: any[]): any; +} + +declare module "perry/system" { + /** stdlib */ + export function audioGetLevel(...args: any[]): any; + /** stdlib */ + export function audioGetPeak(...args: any[]): any; + /** stdlib */ + export function audioGetWaveform(...args: any[]): any; + /** stdlib */ + export function audioSetOutputFilename(...args: any[]): any; + /** stdlib */ + export function audioStart(...args: any[]): any; + /** stdlib */ + export function audioStartRecording(...args: any[]): any; + /** stdlib */ + export function audioStop(...args: any[]): any; + /** stdlib */ + export function audioStopRecording(...args: any[]): any; + /** stdlib */ + export function getAppIcon(...args: any[]): any; + /** stdlib */ + export function getDeviceIdiom(...args: any[]): any; + /** stdlib */ + export function getDeviceModel(...args: any[]): any; + /** stdlib */ + export function getLocale(...args: any[]): any; + /** stdlib */ + export function isDarkMode(...args: any[]): any; + /** stdlib */ + export function keychainDelete(...args: any[]): any; + /** stdlib */ + export function keychainGet(...args: any[]): any; + /** stdlib */ + export function keychainSave(...args: any[]): any; + /** stdlib */ + export function notificationCancel(...args: any[]): any; + /** stdlib */ + export function notificationOnBackgroundReceive(...args: any[]): any; + /** stdlib */ + export function notificationOnReceive(...args: any[]): any; + /** stdlib */ + export function notificationOnTap(...args: any[]): any; + /** stdlib */ + export function notificationRegisterRemote(...args: any[]): any; + /** stdlib */ + export function notificationSend(...args: any[]): any; + /** stdlib */ + export function openURL(...args: any[]): any; + /** stdlib */ + export function preferencesGet(...args: any[]): any; + /** stdlib */ + export function preferencesSet(...args: any[]): any; +} + declare module "perry/thread" { /** stdlib */ export function parallelFilter(p0: any, p1: any): any; @@ -349,6 +701,191 @@ declare module "perry/tui" { export function useInput(p0: any): void; } +declare module "perry/ui" { + /** stdlib */ + export function App(...args: any[]): any; + /** stdlib */ + export function Button(...args: any[]): any; + /** stdlib */ + export function CameraView(...args: any[]): any; + /** stdlib */ + export function Canvas(...args: any[]): any; + /** stdlib */ + export function Divider(...args: any[]): any; + /** stdlib */ + export function ForEach(...args: any[]): any; + /** stdlib */ + export function HStack(...args: any[]): any; + /** stdlib */ + export function HStackWithInsets(...args: any[]): any; + /** stdlib */ + export function ImageFile(...args: any[]): any; + /** stdlib */ + export function ImageSymbol(...args: any[]): any; + /** stdlib */ + export function LazyVStack(...args: any[]): any; + /** stdlib */ + export function NavStack(...args: any[]): any; + /** stdlib */ + export function Picker(...args: any[]): any; + /** stdlib */ + export function ProgressView(...args: any[]): any; + /** stdlib */ + export function ScrollView(...args: any[]): any; + /** stdlib */ + export function Section(...args: any[]): any; + /** stdlib */ + export function SecureField(...args: any[]): any; + /** stdlib */ + export function Slider(...args: any[]): any; + /** stdlib */ + export function Spacer(...args: any[]): any; + /** stdlib */ + export function SplitView(...args: any[]): any; + /** stdlib */ + export function State(...args: any[]): any; + /** stdlib */ + export function TabBar(...args: any[]): any; + /** stdlib */ + export function Table(...args: any[]): any; + /** stdlib */ + export function Text(...args: any[]): any; + /** stdlib */ + export function TextArea(...args: any[]): any; + /** stdlib */ + export function TextField(...args: any[]): any; + /** stdlib */ + export function Toggle(...args: any[]): any; + /** stdlib */ + export function VStack(...args: any[]): any; + /** stdlib */ + export function VStackWithInsets(...args: any[]): any; + /** stdlib */ + export function Window(...args: any[]): any; + /** stdlib */ + export function ZStack(...args: any[]): any; + /** stdlib */ + export function addKeyboardShortcut(...args: any[]): any; + /** stdlib */ + export function alert(...args: any[]): any; + /** stdlib */ + export function alertWithButtons(...args: any[]): any; + /** stdlib */ + export function appSetMaxSize(...args: any[]): any; + /** stdlib */ + export function appSetMinSize(...args: any[]): any; + /** stdlib */ + export function appSetTimer(...args: any[]): any; + /** stdlib */ + export function clipboardRead(...args: any[]): any; + /** stdlib */ + export function clipboardWrite(...args: any[]): any; + /** stdlib */ + export function embedNSView(...args: any[]): any; + /** stdlib */ + export function frameSplitAddChild(...args: any[]): any; + /** stdlib */ + export function frameSplitCreate(...args: any[]): any; + /** stdlib */ + export function menuAddItem(...args: any[]): any; + /** stdlib */ + export function menuAddItemWithShortcut(...args: any[]): any; + /** stdlib */ + export function menuAddSeparator(...args: any[]): any; + /** stdlib */ + export function menuAddStandardAction(...args: any[]): any; + /** stdlib */ + export function menuAddSubmenu(...args: any[]): any; + /** stdlib */ + export function menuBarAddMenu(...args: any[]): any; + /** stdlib */ + export function menuBarAttach(...args: any[]): any; + /** stdlib */ + export function menuBarCreate(...args: any[]): any; + /** stdlib */ + export function menuClear(...args: any[]): any; + /** stdlib */ + export function menuCreate(...args: any[]): any; + /** stdlib */ + export function onActivate(...args: any[]): any; + /** stdlib */ + export function onTerminate(...args: any[]): any; + /** stdlib */ + export function openFileDialog(...args: any[]): any; + /** stdlib */ + export function openFolderDialog(...args: any[]): any; + /** stdlib */ + export function pollOpenFile(...args: any[]): any; + /** stdlib */ + export function registerGlobalHotkey(...args: any[]): any; + /** stdlib */ + export function saveFileDialog(...args: any[]): any; + /** stdlib */ + export function setText(...args: any[]): any; + /** stdlib */ + export function sheetCreate(...args: any[]): any; + /** stdlib */ + export function sheetDismiss(...args: any[]): any; + /** stdlib */ + export function sheetPresent(...args: any[]): any; + /** stdlib */ + export function showToast(...args: any[]): any; + /** stdlib */ + export function toolbarAddItem(...args: any[]): any; + /** stdlib */ + export function toolbarAttach(...args: any[]): any; + /** stdlib */ + export function toolbarCreate(...args: any[]): any; + /** stdlib */ + export function trayAttachMenu(...args: any[]): any; + /** stdlib */ + export function trayCreate(...args: any[]): any; + /** stdlib */ + export function trayDestroy(...args: any[]): any; + /** stdlib */ + export function trayOnClick(...args: any[]): any; + /** stdlib */ + export function traySetIcon(...args: any[]): any; + /** stdlib */ + export function traySetTooltip(...args: any[]): any; +} + +declare module "perry/updater" { + /** stdlib */ + export function clearSentinel(...args: any[]): any; + /** stdlib */ + export function compareVersions(...args: any[]): any; + /** stdlib */ + export function computeFileSha256(...args: any[]): any; + /** stdlib */ + export function getBackupPath(...args: any[]): any; + /** stdlib */ + export function getExePath(...args: any[]): any; + /** stdlib */ + export function getSentinelPath(...args: any[]): any; + /** stdlib */ + export function installUpdate(...args: any[]): any; + /** stdlib */ + export function performRollback(...args: any[]): any; + /** stdlib */ + export function readSentinel(...args: any[]): any; + /** stdlib */ + export function relaunch(...args: any[]): any; + /** stdlib */ + export function verifyHash(...args: any[]): any; + /** stdlib */ + export function verifySignature(...args: any[]): any; + /** stdlib */ + export function verifySignatureV2(...args: any[]): any; + /** stdlib */ + export function writeSentinel(...args: any[]): any; +} + +declare module "perry/widget" { + /** stdlib */ + export function Widget(...args: any[]): any; +} + declare module "pg" { /** stdlib */ export class Client { [key: string]: any; } @@ -385,11 +922,25 @@ declare module "process" { export const versions: any; } +declare module "rate-limiter-flexible" { + /** stdlib */ + export class RateLimiterAbstract { [key: string]: any; } + /** stdlib */ + export class RateLimiterMemory { [key: string]: any; } +} + declare module "readline" { /** stdlib */ export function createInterface(p0: any): any; } +declare module "redis" { + /** stdlib */ + export class Redis { [key: string]: any; } + /** stdlib */ + export function createClient(...args: any[]): any; +} + declare module "sharp" { /** stdlib */ export default function (p0: string): any; @@ -404,11 +955,52 @@ declare module "slugify" { export function slugify(p0: string, p1: string, p2: string): string; } +declare module "stream" { + /** stdlib */ + export class Duplex { [key: string]: any; } + /** stdlib */ + export class PassThrough { [key: string]: any; } + /** stdlib */ + export class Readable { [key: string]: any; } + /** stdlib */ + export class Transform { [key: string]: any; } + /** stdlib */ + export class Writable { [key: string]: any; } + /** stdlib */ + export function finished(...args: any[]): any; + /** stdlib */ + export function pipeline(...args: any[]): any; +} + +declare module "streams" { + /** stdlib */ + export class DecompressionStream { [key: string]: any; } + /** stdlib */ + export class ReadableStream { [key: string]: any; } + /** stdlib */ + export class TextDecoder { [key: string]: any; } + /** stdlib */ + export class TextEncoder { [key: string]: any; } + /** stdlib */ + export class TransformStream { [key: string]: any; } + /** stdlib */ + export class WritableStream { [key: string]: any; } +} + declare module "tls" { /** stdlib */ export function connect(p0: string, p1: any, p2: string, p3: any): any; } +declare module "tty" { + /** stdlib */ + export class ReadStream { [key: string]: any; } + /** stdlib */ + export class WriteStream { [key: string]: any; } + /** stdlib */ + export function isatty(...args: any[]): any; +} + declare module "tursodb" { /** stdlib */ export function open(...args: any[]): any; @@ -419,6 +1011,35 @@ declare module "url" { export class URL { [key: string]: any; } /** stdlib */ export class URLSearchParams { [key: string]: any; } + /** stdlib */ + export function fileURLToPath(...args: any[]): any; + /** stdlib */ + export function format(...args: any[]): any; + /** stdlib */ + export function parse(...args: any[]): any; + /** stdlib */ + export function pathToFileURL(...args: any[]): any; +} + +declare module "util" { + /** stdlib */ + export class TextDecoder { [key: string]: any; } + /** stdlib */ + export class TextEncoder { [key: string]: any; } + /** stdlib */ + export function callbackify(...args: any[]): any; + /** stdlib */ + export function deprecate(...args: any[]): any; + /** stdlib */ + export function format(...args: any[]): any; + /** stdlib */ + export function inherits(...args: any[]): any; + /** stdlib */ + export function inspect(...args: any[]): any; + /** stdlib */ + export function isDeepStrictEqual(...args: any[]): any; + /** stdlib */ + export function promisify(...args: any[]): any; } declare module "uuid" { diff --git a/docs/src/api/reference.md b/docs/src/api/reference.md index e71a38f0a7a..8e8aafcc1d0 100644 --- a/docs/src/api/reference.md +++ b/docs/src/api/reference.md @@ -2,21 +2,25 @@ 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. -**Generated for Perry v0.5.598.** +**Generated for Perry v0.5.600.** -Total: 397 entries across 45 modules. +Total: 673 entries across 68 modules. ## Modules - [`argon2`](#argon2) - [`async_hooks`](#async-hooks) +- [`axios`](#axios) - [`bcrypt`](#bcrypt) - [`better-sqlite3`](#better-sqlite3) +- [`bignumber.js`](#bignumber-js) - [`buffer`](#buffer) - [`cheerio`](#cheerio) +- [`child_process`](#child-process) - [`commander`](#commander) - [`cron`](#cron) - [`crypto`](#crypto) +- [`date-fns`](#date-fns) - [`dayjs`](#dayjs) - [`decimal.js`](#decimal-js) - [`dotenv`](#dotenv) @@ -24,6 +28,10 @@ Total: 397 entries across 45 modules. - [`events`](#events) - [`exponential-backoff`](#exponential-backoff) - [`fastify`](#fastify) +- [`fetch`](#fetch) +- [`fs`](#fs) +- [`http`](#http) +- [`https`](#https) - [`ioredis`](#ioredis) - [`iroh`](#iroh) - [`jsonwebtoken`](#jsonwebtoken) @@ -35,19 +43,34 @@ Total: 397 entries across 45 modules. - [`mysql2/promise`](#mysql2-promise) - [`nanoid`](#nanoid) - [`net`](#net) +- [`node-cron`](#node-cron) +- [`node-fetch`](#node-fetch) - [`nodemailer`](#nodemailer) - [`os`](#os) - [`path`](#path) +- [`perry/i18n`](#perry-i18n) +- [`perry/media`](#perry-media) +- [`perry/plugin`](#perry-plugin) +- [`perry/system`](#perry-system) - [`perry/thread`](#perry-thread) - [`perry/tui`](#perry-tui) +- [`perry/ui`](#perry-ui) +- [`perry/updater`](#perry-updater) +- [`perry/widget`](#perry-widget) - [`pg`](#pg) - [`process`](#process) +- [`rate-limiter-flexible`](#rate-limiter-flexible) - [`readline`](#readline) +- [`redis`](#redis) - [`sharp`](#sharp) - [`slugify`](#slugify) +- [`stream`](#stream) +- [`streams`](#streams) - [`tls`](#tls) +- [`tty`](#tty) - [`tursodb`](#tursodb) - [`url`](#url) +- [`util`](#util) - [`uuid`](#uuid) - [`validator`](#validator) - [`worker_threads`](#worker-threads) @@ -73,6 +96,22 @@ Total: 397 entries across 45 modules. - `getStore` — instance - `run` — instance +## `axios` + +### Methods + +- `all` — module +- `create` — module +- `default` — module +- `delete` — module +- `get` — module +- `head` — module +- `options` — module +- `patch` — module +- `post` — module +- `put` — module +- `request` — module + ## `bcrypt` ### Methods @@ -92,12 +131,27 @@ Total: 397 entries across 45 modules. - `prepare` — instance - `run` — instance +## `bignumber.js` + +### Classes + +- `BigNumber` + ## `buffer` ### Classes - `Buffer` +### Methods + +- `alloc` — module +- `allocUnsafe` — module +- `byteLength` — module +- `concat` — module +- `from` — module +- `isBuffer` — module + ## `cheerio` ### Methods @@ -116,6 +170,18 @@ Total: 397 entries across 45 modules. - `select` — instance - `text` — instance +## `child_process` + +### Methods + +- `exec` — module +- `execFile` — module +- `execFileSync` — module +- `execSync` — module +- `fork` — module +- `spawn` — module +- `spawnSync` — module + ## `commander` ### Methods @@ -156,6 +222,23 @@ Total: 397 entries across 45 modules. - `randomUUID` — module - `sha256` — module +## `date-fns` + +### Methods + +- `addDays` — module +- `addMonths` — module +- `addYears` — module +- `differenceInDays` — module +- `differenceInHours` — module +- `differenceInMinutes` — module +- `endOfDay` — module +- `format` — module +- `isAfter` — module +- `isBefore` — module +- `parseISO` — module +- `startOfDay` — module + ## `dayjs` ### Methods @@ -288,6 +371,88 @@ Total: 397 entries across 45 modules. - `url` — instance - `user` — instance +## `fetch` + +### Classes + +- `Blob` +- `Headers` +- `Request` +- `Response` + +### Methods + +- `default` — module + +## `fs` + +### Methods + +- `accessSync` — module +- `appendFile` — module +- `appendFileSync` — module +- `chmodSync` — module +- `copyFileSync` — module +- `createReadStream` — module +- `createWriteStream` — module +- `existsSync` — module +- `lstatSync` — module +- `mkdir` — module +- `mkdirSync` — module +- `mkdtempSync` — module +- `readFile` — module +- `readFileSync` — module +- `readdir` — module +- `readdirSync` — module +- `realpathSync` — module +- `renameSync` — module +- `rm` — module +- `rmSync` — module +- `rmdirSync` — module +- `stat` — module +- `statSync` — module +- `unlink` — module +- `unlinkSync` — module +- `unwatchFile` — module +- `watchFile` — module +- `writeFile` — module +- `writeFileSync` — module + +### Properties + +- `constants` +- `promises` + +## `http` + +### Classes + +- `ClientRequest` +- `IncomingMessage` +- `Server` +- `ServerResponse` + +### Methods + +- `createServer` — module +- `get` — module +- `request` — module + +## `https` + +### Classes + +- `ClientRequest` +- `IncomingMessage` +- `Server` +- `ServerResponse` + +### Methods + +- `createServer` — module +- `get` — module +- `request` — module + ## `ioredis` ### Classes @@ -463,6 +628,26 @@ Total: 397 entries across 45 modules. - `upgradeToTLS` — instance *(class: `Socket`)* - `write` — instance *(class: `Socket`)* +## `node-cron` + +### Methods + +- `schedule` — module +- `validate` — module + +## `node-fetch` + +### Classes + +- `Blob` +- `Headers` +- `Request` +- `Response` + +### Methods + +- `default` — module + ## `nodemailer` ### Methods @@ -515,6 +700,90 @@ Total: 397 entries across 45 modules. - `sep` - `win32` +## `perry/i18n` + +### Methods + +- `Currency` — module +- `FormatNumber` — module +- `FormatTime` — module +- `LongDate` — module +- `Percent` — module +- `Raw` — module +- `ShortDate` — module +- `t` — module + +## `perry/media` + +### Methods + +- `createPlayer` — module +- `destroy` — module +- `getCurrentTime` — module +- `getDuration` — module +- `getState` — module +- `isPlaying` — module +- `onStateChange` — module +- `onTimeUpdate` — module +- `pause` — module +- `play` — module +- `seek` — module +- `setNowPlaying` — module +- `setRate` — module +- `setVolume` — module +- `stop` — module + +## `perry/plugin` + +### Classes + +- `PluginApi` + +### Methods + +- `discoverPlugins` — module +- `emitEvent` — module +- `emitHook` — module +- `initPlugins` — module +- `invokeTool` — module +- `listHooks` — module +- `listPlugins` — module +- `listTools` — module +- `loadPlugin` — module +- `pluginCount` — module +- `setPluginConfig` — module +- `unloadPlugin` — module + +## `perry/system` + +### Methods + +- `audioGetLevel` — module +- `audioGetPeak` — module +- `audioGetWaveform` — module +- `audioSetOutputFilename` — module +- `audioStart` — module +- `audioStartRecording` — module +- `audioStop` — module +- `audioStopRecording` — module +- `getAppIcon` — module +- `getDeviceIdiom` — module +- `getDeviceModel` — module +- `getLocale` — module +- `isDarkMode` — module +- `keychainDelete` — module +- `keychainGet` — module +- `keychainSave` — module +- `notificationCancel` — module +- `notificationOnBackgroundReceive` — module +- `notificationOnReceive` — module +- `notificationOnTap` — module +- `notificationRegisterRemote` — module +- `notificationSend` — module +- `openURL` — module +- `preferencesGet` — module +- `preferencesSet` — module + ## `perry/thread` ### Methods @@ -553,6 +822,109 @@ Total: 397 entries across 45 modules. - `state` — module - `useInput` — module +## `perry/ui` + +### Methods + +- `App` — module +- `Button` — module +- `CameraView` — module +- `Canvas` — module +- `Divider` — module +- `ForEach` — module +- `HStack` — module +- `HStackWithInsets` — module +- `ImageFile` — module +- `ImageSymbol` — module +- `LazyVStack` — module +- `NavStack` — module +- `Picker` — module +- `ProgressView` — module +- `ScrollView` — module +- `Section` — module +- `SecureField` — module +- `Slider` — module +- `Spacer` — module +- `SplitView` — module +- `State` — module +- `TabBar` — module +- `Table` — module +- `Text` — module +- `TextArea` — module +- `TextField` — module +- `Toggle` — module +- `VStack` — module +- `VStackWithInsets` — module +- `Window` — module +- `ZStack` — module +- `addKeyboardShortcut` — module +- `alert` — module +- `alertWithButtons` — module +- `appSetMaxSize` — module +- `appSetMinSize` — module +- `appSetTimer` — module +- `clipboardRead` — module +- `clipboardWrite` — module +- `embedNSView` — module +- `frameSplitAddChild` — module +- `frameSplitCreate` — module +- `menuAddItem` — module +- `menuAddItemWithShortcut` — module +- `menuAddSeparator` — module +- `menuAddStandardAction` — module +- `menuAddSubmenu` — module +- `menuBarAddMenu` — module +- `menuBarAttach` — module +- `menuBarCreate` — module +- `menuClear` — module +- `menuCreate` — module +- `onActivate` — module +- `onTerminate` — module +- `openFileDialog` — module +- `openFolderDialog` — module +- `pollOpenFile` — module +- `registerGlobalHotkey` — module +- `saveFileDialog` — module +- `setText` — module +- `sheetCreate` — module +- `sheetDismiss` — module +- `sheetPresent` — module +- `showToast` — module +- `toolbarAddItem` — module +- `toolbarAttach` — module +- `toolbarCreate` — module +- `trayAttachMenu` — module +- `trayCreate` — module +- `trayDestroy` — module +- `trayOnClick` — module +- `traySetIcon` — module +- `traySetTooltip` — module + +## `perry/updater` + +### Methods + +- `clearSentinel` — module +- `compareVersions` — module +- `computeFileSha256` — module +- `getBackupPath` — module +- `getExePath` — module +- `getSentinelPath` — module +- `installUpdate` — module +- `performRollback` — module +- `readSentinel` — module +- `relaunch` — module +- `verifyHash` — module +- `verifySignature` — module +- `verifySignatureV2` — module +- `writeSentinel` — module + +## `perry/widget` + +### Methods + +- `Widget` — module + ## `pg` ### Classes @@ -586,6 +958,13 @@ Total: 397 entries across 45 modules. - `version` - `versions` +## `rate-limiter-flexible` + +### Classes + +- `RateLimiterAbstract` +- `RateLimiterMemory` + ## `readline` ### Methods @@ -595,6 +974,16 @@ Total: 397 entries across 45 modules. - `on` — instance - `question` — instance +## `redis` + +### Classes + +- `Redis` + +### Methods + +- `createClient` — module + ## `sharp` ### Methods @@ -623,12 +1012,49 @@ Total: 397 entries across 45 modules. - `default` — module - `slugify` — module +## `stream` + +### Classes + +- `Duplex` +- `PassThrough` +- `Readable` +- `Transform` +- `Writable` + +### Methods + +- `finished` — module +- `pipeline` — module + +## `streams` + +### Classes + +- `DecompressionStream` +- `ReadableStream` +- `TextDecoder` +- `TextEncoder` +- `TransformStream` +- `WritableStream` + ## `tls` ### Methods - `connect` — module +## `tty` + +### Classes + +- `ReadStream` +- `WriteStream` + +### Methods + +- `isatty` — module + ## `tursodb` ### Methods @@ -649,6 +1075,30 @@ Total: 397 entries across 45 modules. - `URL` - `URLSearchParams` +### Methods + +- `fileURLToPath` — module +- `format` — module +- `parse` — module +- `pathToFileURL` — module + +## `util` + +### Classes + +- `TextDecoder` +- `TextEncoder` + +### Methods + +- `callbackify` — module +- `deprecate` — module +- `format` — module +- `inherits` — module +- `inspect` — module +- `isDeepStrictEqual` — module +- `promisify` — module + ## `uuid` ### Methods