diff --git a/Cargo.lock b/Cargo.lock index 37062ae16c..b6b0cfce43 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4875,15 +4875,6 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13d2233c9842d08cfe13f9eac96e207ca6a2ea10b80259ebe8ad0268be27d2af" -[[package]] -name = "nanoid" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8628de41fe064cc3f0cf07f3d299ee3e73521adaff72278731d5c8cae3797873" -dependencies = [ - "rand 0.9.4", -] - [[package]] name = "ndk-context" version = "0.1.1" @@ -6050,15 +6041,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "perry-ext-nanoid" -version = "0.5.1606" -dependencies = [ - "nanoid", - "perry-ffi", - "rand 0.10.2", -] - [[package]] name = "perry-ext-net" version = "0.5.1606" @@ -6382,7 +6364,6 @@ dependencies = [ "md-5 0.11.0", "ml-kem", "mongodb", - "nanoid", "once_cell", "p256", "p384", diff --git a/Cargo.toml b/Cargo.toml index 766712ef22..336f2b35af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,6 @@ members = [ "crates/perry-runtime", "crates/perry-ffi", "crates/perry-native-registration", - "crates/perry-ext-nanoid", "crates/perry-ext-bcrypt", "crates/perry-ext-argon2", "crates/perry-perex", @@ -471,7 +470,6 @@ perry-dispatch = { path = "crates/perry-dispatch" } perry-runtime = { path = "crates/perry-runtime", version = "0.5.1011", default-features = false } perry-ffi = { path = "crates/perry-ffi", version = "0.5.1011" } perry-native-registration = { path = "crates/perry-native-registration", version = "0.5.1534" } -perry-ext-nanoid = { path = "crates/perry-ext-nanoid" } perry-ext-bcrypt = { path = "crates/perry-ext-bcrypt" } perry-ext-argon2 = { path = "crates/perry-ext-argon2" } perry-perex = { path = "crates/perry-perex" } diff --git a/changelog.d/10693-nanoid-native-binding-removal.md b/changelog.d/10693-nanoid-native-binding-removal.md new file mode 100644 index 0000000000..6bdd6dfe35 --- /dev/null +++ b/changelog.d/10693-nanoid-native-binding-removal.md @@ -0,0 +1,14 @@ +Removed the native `nanoid` binding: `customAlphabet(alphabet, size)` is +documented to return a generator function, but the native implementation +returned the generated id string directly, so the only documented usage +(`const gen = customAlphabet(...); gen();`) crashed with `TypeError: value +is not a function`. `import { nanoid, customAlphabet } from "nanoid"` (no +`perry.compilePackages` entry) now compiles the real npm package from +source, matching Node exactly. + +Deleted both duplicate hand-written implementations +(`crates/perry-ext-nanoid` and `crates/perry-stdlib/src/nanoid.rs`, which +independently exported the same `js_nanoid_*` symbols — #10678). +`customAlphabet` turned out to have no call-site wiring anywhere in +`perry-codegen` at all — only plain `nanoid(size)` had a dispatch row, +consistent with the reported crash. diff --git a/crates/perry-api-manifest/src/entries.rs b/crates/perry-api-manifest/src/entries.rs index 53e9dbe339..1ac5de2cb5 100644 --- a/crates/perry-api-manifest/src/entries.rs +++ b/crates/perry-api-manifest/src/entries.rs @@ -43,7 +43,6 @@ pub const NATIVE_MODULES: &[&str] = &[ "ws", // WebSocket client/server "zlib", // (Node builtin) gzip/deflate/brotli/zstd compression "crypto", // (Node builtin) hashing, HMAC, cipher, sign/verify, WebCrypto - "nanoid", // compact URL-safe ID generation "ethers", // Ethereum library (utils/wallet/ABI) "mongodb", // MongoDB driver "better-sqlite3", // synchronous SQLite (replaces the N-API addon) diff --git a/crates/perry-api-manifest/src/entries/part_1.rs b/crates/perry-api-manifest/src/entries/part_1.rs index 9dc26c00a7..05bc326238 100644 --- a/crates/perry-api-manifest/src/entries/part_1.rs +++ b/crates/perry-api-manifest/src/entries/part_1.rs @@ -1111,18 +1111,6 @@ pub(crate) const API_MANIFEST_PART_1: &[ApiEntry] = &[ ), method("nodemailer", "sendMail", true, None), method("nodemailer", "verify", true, None), - method_sig( - "nanoid", - "nanoid", - false, - None, - &[ParamSpec::Named { - name: "size", - ty: TypeSpec::Number, - optional: false, - }], - TypeSpec::String, - ), // #4917 — real retry semantics: options (numOfAttempts/startingDelay/ // timeMultiple/maxDelay/delayFirstAttempt/jitter/retry) honored; // Promise-returning tasks retry on rejection via promise reactions. diff --git a/crates/perry-codegen/src/lower_call/native_table/utils_crypto.rs b/crates/perry-codegen/src/lower_call/native_table/utils_crypto.rs index 4b41aa587c..83189264ff 100644 --- a/crates/perry-codegen/src/lower_call/native_table/utils_crypto.rs +++ b/crates/perry-codegen/src/lower_call/native_table/utils_crypto.rs @@ -29,18 +29,6 @@ pub(super) const UTILS_CRYPTO_ROWS: &[NativeModSig] = &[ args: &[], ret: NR_GCPTR, }, - // ========== nanoid ========== - // js_nanoid_sized(NaN) → size=0 → falls back to js_nanoid() (21-char default), - // so nanoid() and nanoid(N) both route through the same entry safely. - NativeModSig { - module: "nanoid", - has_receiver: false, - method: "nanoid", - class_filter: None, - runtime: "js_nanoid_sized", - args: &[NA_F64], - ret: NR_STR, - }, // ========== exponential-backoff ========== NativeModSig { module: "exponential-backoff", diff --git a/crates/perry-codegen/src/runtime_decls/stdlib_ffi/data_stores.rs b/crates/perry-codegen/src/runtime_decls/stdlib_ffi/data_stores.rs index d9da94b533..13a2d481e2 100644 --- a/crates/perry-codegen/src/runtime_decls/stdlib_ffi/data_stores.rs +++ b/crates/perry-codegen/src/runtime_decls/stdlib_ffi/data_stores.rs @@ -288,8 +288,4 @@ pub(crate) fn declare_data_stores(module: &mut LlModule) { module.declare_function("js_crypto_x25519_shared_secret", I64, &[I64, I64]); module.declare_function("js_keccak256_native", I64, &[I64]); module.declare_function("js_keccak256_native_bytes", I64, &[I64]); - - // ========== Nanoid ========== - module.declare_function("js_nanoid", I64, &[DOUBLE]); - module.declare_function("js_nanoid_custom", I64, &[I64, DOUBLE]); } diff --git a/crates/perry-ext-nanoid/Cargo.toml b/crates/perry-ext-nanoid/Cargo.toml deleted file mode 100644 index e264ea3e8b..0000000000 --- a/crates/perry-ext-nanoid/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "perry-ext-nanoid" -version.workspace = true -edition.workspace = true -license.workspace = true -description = "Native bindings for the npm `nanoid` package — uses only `perry-ffi`. Second port under #466 Phase 5 (after dotenv)." - -[lints] -workspace = true - -[lib] -crate-type = ["staticlib", "rlib"] - -[dependencies] -perry-ffi.workspace = true -nanoid = "0.5" -rand = "0.10" - -[dev-dependencies] -perry-ffi = { workspace = true, features = ["runtime-link"] } diff --git a/crates/perry-ext-nanoid/src/lib.rs b/crates/perry-ext-nanoid/src/lib.rs deleted file mode 100644 index 8b58aecc9a..0000000000 --- a/crates/perry-ext-nanoid/src/lib.rs +++ /dev/null @@ -1,106 +0,0 @@ -//! Native bindings for the npm `nanoid` package. -//! -//! Functionally identical to `crates/perry-stdlib/src/nanoid.rs`. The -//! point of this crate is that it depends only on [`perry_ffi`], not -//! on `perry-runtime` internals — proving the perry-ffi v0.5 surface -//! still suffices for the second wrapper port (#466 Phase 5 step 2). - -use nanoid::nanoid; -use perry_ffi::{alloc_string, read_string, JsString, StringHeader}; - -/// `nanoid()` — 21-char URL-safe id with the default alphabet. -#[no_mangle] -pub extern "C" fn js_nanoid() -> *mut StringHeader { - let id = nanoid!(); - alloc_string(&id).as_raw() -} - -/// `nanoid(size)` — id with a custom length. -#[no_mangle] -pub extern "C" fn js_nanoid_sized(size: f64) -> *mut StringHeader { - let size = size as usize; - if size == 0 { - return js_nanoid(); - } - let id = nanoid!(size); - alloc_string(&id).as_raw() -} - -/// `customAlphabet(alphabet, size)()` — id with a user-supplied -/// alphabet. Perry collapses this into a single call rather than the -/// curried form Node uses, so the FFI surface stays flat. -/// -/// # Safety -/// -/// `alphabet_ptr` must be null or a pointer to a Perry-runtime -/// `StringHeader`. -#[no_mangle] -pub unsafe extern "C" fn js_nanoid_custom( - alphabet_ptr: *const StringHeader, - size: f64, -) -> *mut StringHeader { - let handle = JsString::from_raw(alphabet_ptr as *mut StringHeader); - let alphabet = match read_string(handle) { - Some(a) => a, - None => return js_nanoid(), - }; - - let size = if size <= 0.0 { 21 } else { size as usize }; - let alphabet_chars: Vec = alphabet.chars().collect(); - - if alphabet_chars.is_empty() { - return js_nanoid(); - } - - use rand::RngExt; - let mut rng = rand::rng(); - let id: String = (0..size) - .map(|_| { - let idx = rng.random_range(0..alphabet_chars.len()); - alphabet_chars[idx] - }) - .collect(); - - alloc_string(&id).as_raw() -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn default_id_is_21_chars() { - let handle = unsafe { JsString::from_raw(js_nanoid()) }; - let s = read_string(handle).expect("non-null"); - assert_eq!(s.chars().count(), 21); - } - - #[test] - fn sized_id_honors_length() { - for n in [1, 5, 16, 100] { - let handle = unsafe { JsString::from_raw(js_nanoid_sized(n as f64)) }; - let s = read_string(handle).expect("non-null"); - assert_eq!(s.chars().count(), n, "size={}", n); - } - } - - #[test] - fn sized_zero_falls_back_to_default() { - let handle = unsafe { JsString::from_raw(js_nanoid_sized(0.0)) }; - let s = read_string(handle).expect("non-null"); - assert_eq!(s.chars().count(), 21); - } - - #[test] - fn custom_alphabet_round_trips_through_perry_ffi() { - // Allocate the alphabet through perry-ffi so the FFI is - // exercised end-to-end. - let alphabet = alloc_string("abc"); - let handle = unsafe { js_nanoid_custom(alphabet.as_raw() as *const _, 8.0) }; - let s = read_string(unsafe { JsString::from_raw(handle) }).expect("non-null"); - assert_eq!(s.chars().count(), 8); - for c in s.chars() { - assert!("abc".contains(c), "char `{}` not in alphabet", c); - } - } -} diff --git a/crates/perry-stdlib/Cargo.toml b/crates/perry-stdlib/Cargo.toml index 0357575ca8..bbf85a49a5 100644 --- a/crates/perry-stdlib/Cargo.toml +++ b/crates/perry-stdlib/Cargo.toml @@ -282,14 +282,12 @@ rate-limit = ["bundled-ratelimit"] bundled-ratelimit = ["dep:governor", "async-runtime"] -# UUID/nanoid — `ids` stays as the umbrella for backwards compat; -# from v0.5.534 onwards the per-binding split (`bundled-uuid` / -# `bundled-nanoid`) is what the well-known bindings flip (#466 -# Phase 4 step 2) toggles. Each sub-feature pulls in its own -# optional dep + gates its own module so a wrapper port can strip -# exactly one binding from perry-stdlib without affecting the other. -ids = ["bundled-nanoid"] -bundled-nanoid = ["dep:nanoid"] +# `ids` is now an empty umbrella kept only for backwards compat: +# both members are gone — `bundled-uuid` with the uuid binding +# (#10701) and `bundled-nanoid` with the nanoid binding (#10693). +# Real `uuid` / `nanoid` now compile from npm source, so nothing +# needs to be toggled here; enabling `ids` is a harmless no-op. +ids = [] # Async runtime (tokio) - internal feature async-runtime = ["dep:tokio"] @@ -435,7 +433,6 @@ governor = { version = "0.10", optional = true } # never optional, regardless of the bundled-uuid npm-binding feature # (removed; see #10678/#466). uuid = { version = "1.23", features = ["v4", "v1", "v3", "v5", "v7"] } -nanoid = { version = "0.5", optional = true } # LRU Cache — optional from v0.5.539 so the well-known flip can # strip the perry-stdlib copy when `import 'lru-cache'` resolves diff --git a/crates/perry-stdlib/src/lib.rs b/crates/perry-stdlib/src/lib.rs index 7c4e9ed331..2c64ee8296 100644 --- a/crates/perry-stdlib/src/lib.rs +++ b/crates/perry-stdlib/src/lib.rs @@ -398,16 +398,11 @@ pub mod ratelimit; pub use ratelimit::*; // === IDs === -// `bundled-uuid` / `bundled-nanoid` (v0.5.534) replace the old -// `ids` umbrella so the well-known flip (#466 Phase 4) can toggle -// each binding independently. The umbrella stays as -// `ids = ["bundled-uuid", "bundled-nanoid"]` so existing -// `--features ids` callers keep working byte-identically. - -#[cfg(feature = "bundled-nanoid")] -pub mod nanoid; -#[cfg(feature = "bundled-nanoid")] -pub use nanoid::*; +// Nothing left to gate: `bundled-uuid` went with the uuid binding +// (#10701) and `bundled-nanoid` with the nanoid binding (#10693); +// real `uuid` / `nanoid` now compile from npm source. The `ids` +// umbrella stays (empty) in Cargo.toml so existing +// `--features ids` callers keep working. // === Container Module === #[cfg(feature = "container")] diff --git a/crates/perry-stdlib/src/nanoid.rs b/crates/perry-stdlib/src/nanoid.rs deleted file mode 100644 index b39c64441a..0000000000 --- a/crates/perry-stdlib/src/nanoid.rs +++ /dev/null @@ -1,62 +0,0 @@ -//! NanoID module (nanoid compatible) -//! -//! Native implementation of the 'nanoid' npm package. -//! Generates short, URL-friendly unique IDs. - -use nanoid::nanoid; -use perry_runtime::{js_string_from_bytes, StringHeader}; - -use crate::common::string_from_header; - -/// Generate a nanoid with default settings (21 chars, URL-safe alphabet) -/// nanoid() -> string -#[no_mangle] -pub extern "C" fn js_nanoid() -> *mut StringHeader { - let id = nanoid!(); - js_string_from_bytes(id.as_ptr(), id.len() as u32) -} - -/// Generate a nanoid with custom length -/// nanoid(size) -> string -#[no_mangle] -pub extern "C" fn js_nanoid_sized(size: f64) -> *mut StringHeader { - let size = size as usize; - if size == 0 { - return js_nanoid(); - } - let id = nanoid!(size); - js_string_from_bytes(id.as_ptr(), id.len() as u32) -} - -/// Generate a nanoid with custom alphabet and size -/// customAlphabet(alphabet, size)() -> string -/// For simplicity, we combine this into one call: nanoid.custom(alphabet, size) -#[no_mangle] -pub unsafe extern "C" fn js_nanoid_custom( - alphabet_ptr: *const StringHeader, - size: f64, -) -> *mut StringHeader { - let alphabet = match string_from_header(alphabet_ptr) { - Some(a) => a, - None => return js_nanoid(), - }; - - let size = if size <= 0.0 { 21 } else { size as usize }; - let alphabet_chars: Vec = alphabet.chars().collect(); - - if alphabet_chars.is_empty() { - return js_nanoid(); - } - - // Generate ID using custom alphabet - use rand::RngExt; - let mut rng = rand::rng(); - let id: String = (0..size) - .map(|_| { - let idx = rng.random_range(0..alphabet_chars.len()); - alphabet_chars[idx] - }) - .collect(); - - js_string_from_bytes(id.as_ptr(), id.len() as u32) -} diff --git a/crates/perry-ui-android/src/stdlib_stubs.rs b/crates/perry-ui-android/src/stdlib_stubs.rs index 6da9579d68..0fc7b58452 100644 --- a/crates/perry-ui-android/src/stdlib_stubs.rs +++ b/crates/perry-ui-android/src/stdlib_stubs.rs @@ -1267,14 +1267,6 @@ pub extern "C" fn js_mysql2_pool_query() -> i64 { 0 } #[no_mangle] -pub extern "C" fn js_nanoid() -> i64 { - 0 -} -#[no_mangle] -pub extern "C" fn js_nanoid_custom() -> i64 { - 0 -} -#[no_mangle] pub extern "C" fn js_new_instance() -> i64 { 0 } diff --git a/crates/perry/src/commands/compile/well_known.rs b/crates/perry/src/commands/compile/well_known.rs index 4307c829bc..dab94870d9 100644 --- a/crates/perry/src/commands/compile/well_known.rs +++ b/crates/perry/src/commands/compile/well_known.rs @@ -476,18 +476,6 @@ mod tests { } } - #[test] - fn shipped_unproven_bindings_are_partial() { - for name in ["nanoid"] { - let b = lookup_well_known(name).unwrap_or_else(|| panic!("{name} registered")); - assert_eq!( - b.compat, - BindingCompat::Partial, - "{name} omits upstream API/behavior and must stay partial" - ); - } - } - #[test] fn aliases_inherit_target_compat_and_cycles_fail_closed() { let raw = r#" diff --git a/crates/perry/src/commands/stdlib_features.rs b/crates/perry/src/commands/stdlib_features.rs index b1b39d6359..6ca239fa23 100644 --- a/crates/perry/src/commands/stdlib_features.rs +++ b/crates/perry/src/commands/stdlib_features.rs @@ -160,13 +160,12 @@ pub fn module_to_features(module: &str) -> &'static [&'static str] { "argon2" => &["bundled-argon2"], // ── IDs (uuid / nanoid) ─────────────────────────────────────── - // Per-binding split as of v0.5.534 (#466 Phase 4 step 2) - // so the well-known flip can swap each one out - // independently. The `ids` umbrella stays in - // perry-stdlib/Cargo.toml as `bundled-uuid + bundled-nanoid` - // for backwards compat, but feature-set computation goes - // straight to the per-binding feature. - "nanoid" => &["bundled-nanoid"], + // No entries: the uuid binding (#10701) and the nanoid + // binding (#10693) are gone, so `import "uuid"` / + // `import "nanoid"` compile the real npm packages from + // source and need no perry-stdlib feature. The `ids` + // umbrella survives (empty) in perry-stdlib/Cargo.toml + // for backwards compat only. // ── Container ───────────────────────────────────────────────── "perry/container" | "perry/container-compose" | "perry/compose" | "perry/workloads" => { diff --git a/crates/perry/well_known_bindings.toml b/crates/perry/well_known_bindings.toml index 4721c63bc0..1a913b4cff 100644 --- a/crates/perry/well_known_bindings.toml +++ b/crates/perry/well_known_bindings.toml @@ -36,21 +36,6 @@ # requires every ext crate and package mapping to have an explicit decision # (#5716). -[bindings.nanoid] -crate = "perry-ext-nanoid" -lib = "perry_ext_nanoid" -tracking = "#466" -# Partial: the wrapper covers nanoid + custom alphabets, but not the complete -# upstream export set and flattens the curried customAlphabet contract. -compat = "partial" - -[bindings.nanoid.upstream] -version = "6.0.0" -sha256 = "5cade80a39ccf4fd174c8e412eca13accfce36c9c2a0982b4ea23403d729a8d8" -repo = "https://github.com/ai/nanoid" -ref = "4dacb107b54ffd0e1abfe91b7ef452e0fd5a8e12" -ported-at = "6.0.0" -date = "2026-07-30" [bindings.qs] crate = "perry-ext-qs" lib = "perry_ext_qs" diff --git a/docs/api/perry.d.ts b/docs/api/perry.d.ts index 47a24f494b..78c924c802 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: 2065 entries across 131 modules +// Coverage: 2064 entries across 130 modules type PerryI8 = number & { readonly __perryI8?: never }; type PerryI16 = number & { readonly __perryI16?: never }; @@ -2215,11 +2215,6 @@ declare module "mysql2/promise" { export function createPool(p0: any): any; } -declare module "nanoid" { - /** stdlib */ - export function nanoid(size: number): string; -} - declare module "net" { /** stdlib */ export class BlockList { [key: string]: any; } diff --git a/docs/src/api/reference.md b/docs/src/api/reference.md index c0dd3c2f51..5ea2875952 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: 3007 entries across 133 modules. +Total: 3006 entries across 132 modules. ## Modules @@ -69,7 +69,6 @@ Total: 3007 entries across 133 modules. - [`mongodb`](#mongodb) - [`mysql2`](#mysql2) - [`mysql2/promise`](#mysql2promise) -- [`nanoid`](#nanoid) - [`net`](#net) - [`node-cron`](#node-cron) - [`node-fetch`](#node-fetch) @@ -2174,12 +2173,6 @@ Total: 3007 entries across 133 modules. - `release` — instance - `rollback` — instance -## `nanoid` - -### Methods - -- `nanoid` — module - ## `net` ### Classes diff --git a/docs/src/native-libraries/governance.md b/docs/src/native-libraries/governance.md index 3939cc0e72..d893a096a4 100644 --- a/docs/src/native-libraries/governance.md +++ b/docs/src/native-libraries/governance.md @@ -104,7 +104,6 @@ from `well_known_bindings.toml`. Regenerate this table with | `perry-ext-moment` | `moment` | Source package | Compile the upstream package source | Bundled; migration pending | | `perry-ext-mongodb` | `mongodb` | Source package | Compile the upstream package source | Bundled; migration pending | | `perry-ext-mysql2` | `mysql2`
`mysql2/promise` | Source package | Compile the upstream package source | Bundled; migration pending | -| `perry-ext-nanoid` | `nanoid` | Source package | Compile the upstream package source | Bundled; migration pending | | `perry-ext-net` | `net` | Runtime API | Keep near core; consolidate when practical | Bundled; retained | | `perry-ext-node-forge` | `node-forge` | Source package | Compile the upstream package source | Bundled; migration pending | | `perry-ext-nodemailer` | `nodemailer` | Source package | Compile the upstream package source | Bundled; migration pending | diff --git a/workspace-architecture.json b/workspace-architecture.json index 85139f6cfb..7de6b0d0a6 100644 --- a/workspace-architecture.json +++ b/workspace-architecture.json @@ -25,7 +25,7 @@ ] }, "baseline": { - "workspace_members": 77, + "workspace_members": 76, "default_dependency_closure": [ "perry", "perry-api-manifest", @@ -68,7 +68,7 @@ "perry-updater" ], "decision_counts": { - "externalize": 28, + "externalize": 27, "keep": 44, "merge": 1, "remove": 1, @@ -255,11 +255,6 @@ "decision": "externalize", "migration": "compile-source" }, - "perry-ext-nanoid": { - "category": "binding", - "decision": "externalize", - "migration": "compile-source" - }, "perry-ext-net": { "category": "binding", "decision": "keep",