memorable-ids
Version:
A flexible library for generating human-readable, memorable identifiers
142 lines (139 loc) • 4.25 kB
JavaScript
import { dictionaryStats, adjectives, nouns, verbs, adverbs, prepositions } from './dictionary.mjs';
export { dictionary } from './dictionary.mjs';
/**
* Memorable ID Generator
*
* A flexible library for generating human-readable, memorable identifiers.
* Uses combinations of adjectives, nouns, verbs, adverbs, and prepositions
* with optional numeric/custom suffixes.
*
* @author Aris Ripandi
* @license MIT
*/
function generate(options = {}) {
const { components = 2, suffix = null, separator = "-" } = options;
if (components < 1 || components > 5) {
throw new Error("Components must be between 1 and 5");
}
function random(max) {
return Math.floor(Math.random() * max);
}
function randomItem(array) {
return array[random(array.length)];
}
const parts = [];
const componentGenerators = [
() => randomItem(adjectives),
// 0: adjective
() => randomItem(nouns),
// 1: noun
() => randomItem(verbs),
// 2: verb
() => randomItem(adverbs),
// 3: adverb
() => randomItem(prepositions)
// 4: preposition
];
for (let i = 0; i < components; i++) {
parts.push(componentGenerators[i]());
}
if (suffix && typeof suffix === "function") {
const suffixValue = suffix();
if (suffixValue !== null && suffixValue !== void 0) {
parts.push(suffixValue);
}
}
return parts.join(separator);
}
function defaultSuffix() {
return Math.floor(Math.random() * 1e3).toString().padStart(3, "0");
}
function parse(id, separator = "-") {
const parts = id.split(separator);
const result = {
components: [],
suffix: null
};
const lastPart = parts[parts.length - 1];
if (/^\d+$/.test(lastPart)) {
result.suffix = lastPart;
result.components = parts.slice(0, -1);
} else {
result.components = parts;
}
return result;
}
function calculateCombinations(components = 2, suffixRange = 1) {
const componentSizes = [
dictionaryStats.adjectives,
// 78 adjectives
dictionaryStats.nouns,
// 68 nouns
dictionaryStats.verbs,
// 40 verbs
dictionaryStats.adverbs,
// 27 adverbs
dictionaryStats.prepositions
// 26 prepositions
];
let total = 1;
for (let i = 0; i < components; i++) {
total *= componentSizes[i];
}
return total * suffixRange;
}
function calculateCollisionProbability(totalCombinations, generatedIds) {
if (generatedIds >= totalCombinations) return 1;
if (generatedIds <= 1) return 0;
const exponent = -(generatedIds * generatedIds) / (2 * totalCombinations);
return 1 - Math.exp(exponent);
}
function getCollisionAnalysis(components = 2, suffixRange = 1) {
const total = calculateCombinations(components, suffixRange);
const testSizes = [50, 100, 200, 500, 1e3, 2e3, 5e3, 1e4, 2e4, 5e4];
return {
totalCombinations: total,
scenarios: testSizes.filter((size) => size < total * 0.8).map((size) => ({
ids: size,
probability: calculateCollisionProbability(total, size),
percentage: `${(calculateCollisionProbability(total, size) * 100).toFixed(2)}%`
}))
};
}
const suffixGenerators = {
/**
* Random 3-digit number (000-999)
* Adds 1,000x multiplier to total combinations
*/
number: defaultSuffix,
/**
* Random 4-digit number (0000-9999)
* Adds 10,000x multiplier to total combinations
*/
number4: () => Math.floor(Math.random() * 1e4).toString().padStart(4, "0"),
/**
* Random 2-digit hex (00-ff)
* Adds 256x multiplier to total combinations
*/
hex: () => Math.floor(Math.random() * 256).toString(16).padStart(2, "0"),
/**
* Last 4 digits of current timestamp
* Adds ~10,000x multiplier (time-based, not truly random)
*/
timestamp: () => Date.now().toString().slice(-4),
/**
* Random lowercase letter (a-z)
* Adds 26x multiplier to total combinations
*/
letter: () => String.fromCharCode(97 + Math.floor(Math.random() * 26))
};
const memorableId = {
generate,
parse,
calculateCombinations,
calculateCollisionProbability,
getCollisionAnalysis,
suffixGenerators,
defaultSuffix
};
export { calculateCollisionProbability, calculateCombinations, memorableId as default, defaultSuffix, dictionaryStats, generate, getCollisionAnalysis, parse, suffixGenerators };