A MoonBit port of the NanoID library
Note: MoonBit core still does not expose a standard system-entropy API. Native and llvm builds use OS entropy directly (getrandom//dev/urandom, arc4random_buf, or BCryptGenRandom), and JS builds use Web Crypto or Node crypto.randomBytes. WASM and wasm-gc keep the runtime-seeded ChaCha8 fallback so moon test --target all remains self-contained; for security-sensitive WASM IDs/tokens, pass a host-crypto-backed source via custom_random. Thread safety: The default global RNG used by the wasm fallback is not thread-safe.
moon update
moon add hustcer/nanoidimport {
"hustcer/nanoid",
}// Generate a URL-safe ID with default length (21 characters)
let id_result = @nanoid.nanoid()
match id_result {
Ok(id) => println(id) // => "rMf19KHCD5GQw0wzQnEKd"
Err(e) => println("Error: \{e.to_string()}")
}
// Generate an ID with custom length
let short_id_result = @nanoid.nanoid(size=10)
match short_id_result {
Ok(short_id) => println(short_id) // => "yue8fl8f9X"
Err(e) => println("Error: \{e.to_string()}")
}
// For convenience, use the backward-compatible functions
let id = @nanoid.nanoid_or_empty()
println(id) // => "rMf19KHCD5GQw0wzQnEKd" or "" on error// Create a generator with custom alphabet
let hex_generator_result = @nanoid.custom_alphabet(@nanoid.hex, size=8)
match hex_generator_result {
Ok(hex_generator) => {
match hex_generator() {
Ok(hex_id) => println(hex_id) // => "a1b2c3d4"
Err(e) => println("Generation error: \{e.to_string()}")
}
}
Err(e) => println("Setup error: \{e.to_string()}")
}
// For convenience, use the backward-compatible functions
let safe_generator = @nanoid.custom_alphabet_or_empty(@nanoid.nolookalikes, size=12)
let safe_id = safe_generator()
println(safe_id) // => "6B9CMnpqrt7w" or "" on error
// Unicode alphabets are fully supported
let emoji_gen = @nanoid.custom_alphabet("😀😁😂🤣😃😄", size=5)let id1 = @nanoid.nanoid() // Ok("...") with 21 characters
let id2 = @nanoid.nanoid(size=10) // Ok("...") with 10 characters
let id3 = @nanoid.nanoid(size=0) // Err(SizeTooSmall(0))let id1 = @nanoid.nanoid_or_empty() // 21 characters or ""
let id2 = @nanoid.nanoid_or_empty(size=10) // 10 characters or ""
let id3 = @nanoid.nanoid_or_empty(size=0) // ""let generator_result = @nanoid.custom_alphabet("0123456789", size=8)
match generator_result {
Ok(generator) => {
match generator() {
Ok(numeric_id) => println(numeric_id) // => "12345678"
Err(e) => println("Generation error: \{e.to_string()}")
}
}
Err(e) => println("Setup error: \{e.to_string()}")
}
let bad_result = @nanoid.custom_alphabet("", size=8)
// Returns Err(EmptyAlphabet)pub(all) enum NanoidError {
EmptyAlphabet // Alphabet cannot be empty
OversizedAlphabet(Int) // Alphabet exceeds 256 characters
DuplicateCharacter(Char, Int, Int) // Duplicate character found at positions
SizeTooSmall(Int) // Size must be greater than 0
SizeTooLarge(Int) // Size exceeds maximum allowed (1,000,000)
RandomGenerationError(String) // Random number generation failed
} derive(Debug, Eq)// Handle errors explicitly
match @nanoid.nanoid(size=-1) {
Ok(id) => println("Generated: \{id}")
Err(SizeTooSmall(size)) => println("Invalid size: \{size}")
Err(e) => println("Other error: \{e.to_string()}")
}
// Detect duplicate characters in alphabet
match @nanoid.custom_alphabet("ABCA", size=5) {
Err(DuplicateCharacter(char, first, dup)) =>
println("'\{char}' duplicated at \{dup}, first at \{first}")
_ => ()
}
// Use convenience functions for backward compatibility
let id = @nanoid.nanoid_or_empty(size=-1) // Returns "" on error
// Propagate errors from a custom random source (e.g. an HSM that may fail)
let rng = fn(size : Int) -> Result[Array[Int], @nanoid.NanoidError] {
match read_from_hsm(size) {
Ok(bytes) => Ok(bytes)
Err(_) => Err(@nanoid.RandomGenerationError("HSM offline"))
}
}
match @nanoid.custom_random(@nanoid.url_alphabet, 21, rng) {
Ok(generator) => match generator() {
Ok(id) => println(id)
Err(@nanoid.RandomGenerationError(msg)) => println("RNG failure: \{msg}")
Err(e) => println("Other error: \{e.to_string()}")
}
Err(e) => println("Setup failed: \{e.to_string()}")
}| Alphabet | Characters | Description |
|---|---|---|
| numbers | 0123456789 | Numbers only |
| lowercase | abcdefghijklmnopqrstuvwxyz | Lowercase letters |
| uppercase | ABCDEFGHIJKLMNOPQRSTUVWXYZ | Uppercase letters |
| alphanumeric | A-Za-z0-9 (62 chars) | Letters and numbers |
| Alphabet | Characters | Description |
|---|---|---|
| hex | 0123456789abcdef | Lowercase hexadecimal |
| hex_upper | 0123456789ABCDEF | Uppercase hexadecimal |
| Alphabet | Characters | Description |
|---|---|---|
| url_alphabet | A-Za-z0-9_- (64 chars) | Default URL-safe alphabet |
| nolookalikes | 346789ABCD...xyz (49 chars) | No confusing characters (removes 1,l,I,0,O,o,u,v,5,S,s,2,Z) |
| nolookalikes_safe | 6789BCDF...twz (35 chars) | No lookalikes + no vowels (safer for public IDs) |
| base58 | 123456789ABCD...xyz (58 chars) | Bitcoin-style (excludes 0,O,I,l) |
| base62 | 0-9A-Za-z (62 chars) | Standard base62 encoding |
| url_safe | A-Za-z0-9_- (64 chars) | Same character set as url_alphabet |
| filename_safe | A-Za-z0-9_- (64 chars) | Same character set as url_alphabet, for filename contexts |
// Use different alphabets for different purposes
let uuid_like = @nanoid.custom_alphabet(@nanoid.hex, size=32)
let readable = @nanoid.custom_alphabet(@nanoid.nolookalikes, size=8)
let safe_public = @nanoid.custom_alphabet(@nanoid.nolookalikes_safe, size=6)
let crypto_style = @nanoid.custom_alphabet(@nanoid.base58, size=16)moon test --target alllet alphanumeric : Stringlet base58 : Stringlet base62 : Stringfn custom_alphabet(alphabet : String, size? : Int) -> Result[() -> Result[String, NanoidError], NanoidError]fn custom_alphabet_or_empty(alphabet : String, size? : Int) -> (() -> String)fn custom_random(alphabet : String, size : Int, random : (Int) -> Result[Array[Int], NanoidError]) -> Result[() -> Result[String, NanoidError], NanoidError]fn custom_random_or_empty(alphabet : String, size : Int, random : (Int) -> Array[Int]) -> (() -> String)let filename_safe : Stringfn nanoid_or_empty(size? : Int) -> Stringlet nolookalikes : Stringlet nolookalikes_safe : Stringlet url_alphabet : Stringlet url_safe : StringA MoonBit port of the NanoID library