Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
node_modules/
*.log
*.local.md
.env*
.DS_Store
dist/
Expand Down
147 changes: 114 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,36 +9,56 @@
A lightweight TypeScript library that converts any number into consistent, memorable codename from a curated word list. It's designed for use cases where human-readability is more important than collision resistance, such as generating preview URLs or readable test IDs.

```typescript
// Codename generator using the most memorable 20 cities
import codename from "codenames/cities-20";
// Default: uses the cities-20 word list
import codename from "codenames";

codename(1234); // "london", uses "cities"
codename(1234); // "london"
codename(1234); // "london" (always the same)
codename(5678); // "paris"
codename(6789); // "berlin"

// With custom words
codename(1234, ["one", "two", "three"]); // "two"
```

## Features

- **🎯 Deterministic** - Same input always produces the same codename
- **💬 Human-Readable** - Memorable names instead of random strings
- **🚀 Zero Dependencies** - Lightweight and fast with no external packages
- **⚡ Fast** - 50,000+ generations per second
- **📦 <3KB Core** - Ultra-minimal footprint, themes add ~2KB each
- **⚡ Fast & Tiny** - 50,000+ generations per second, <3KB core + ~2KB per theme
- **🌐 Universal Runtime** - Works in Node.js, Bun, Deno, browsers, and edge runtimes
- **🎨 Multiple Themes** - Cities, animals, colors, space, and more built-in themes
- **📦 Modern JavaScript** - TypeScript with full type safety, ESM package
- **📝 Customizable** - Create your own themes and word lists
- **🤖 CLI Included** - Generate codenames directly from your terminal

## Use Cases

- **Preview Deployments** - Generate unique URLs for pull requests
- **Container Names** - Memorable Docker container identifiers
- **Session IDs** - User-friendly session identifiers for support
- **Feature Flags** - Human-friendly names for A/B tests
- **Test Environments** - Readable identifiers for testing pipelines
- **Test Data Generation** - Consistent test data generation
### Preview Deployments

Managing preview environments can get messy. You end up tracking which PR is deployed where, maintaining state, dealing with conflicts. Here's a simpler approach: use deterministic hashing to map PR numbers to memorable names.

```typescript
import codename from "codenames/cities-20";

// PR #1234 always maps to the same URL
const previewUrl = `https://${codename(1234)}.example.com`;
// => https://london.example.com

// PR #5678 gets a different one
const anotherUrl = `https://${codename(5678)}.example.com`;
// => https://paris.example.com
```

With 20 city names, you get 20 deployment slots. No database needed. The same PR number always produces the same city name, so URLs stay consistent throughout the PR lifecycle.

Plus, it's easier to share "london.example.com" in Slack than "preview-env-1234.k8s.us-east-1.example.com".

### Other Uses

- **Docker Containers**: `docker run --name "app-${codename(buildId)}" myapp` → `app-tokyo`
- **Session IDs**: `vienna-support` is friendlier than `sess_kJ8Hg2Bx9`
- **Feature Flags**: `berlin-experiment` instead of `experiment_42`
- **Test Data**: Generate predictable usernames (`cat`, `dog`, `bird`)

## Getting Started

Expand All @@ -47,23 +67,82 @@ npm install codenames
```

```typescript
// Import the codename generator using the top 20
// words from the curated list of world cities
import codename from "codenames/cities-20";
// Default: uses cities-20 word list
import codename from "codenames";
// OR
import { codename } from "codenames";

// Get a codename for the number 1234
const name = codename(1234); // "london"
```

You can use different word lists and list sizes by importing the appropriate module:
### Using Different Themes

You can import specific themed word lists:

```typescript
// Import from a specific theme
import codename from "codenames/animals-20";
// OR
import { codename } from "codenames/animals-20";

const name = codename(1234); // "cat"
```

### Using Custom Word Lists

For maximum flexibility, use the core function with your own words:

```typescript
import codename from "codenames/colors-50";
// Use your own custom word list
import { codename } from "codenames/core";

const name = codename(1234); // "blue"
const name = codename(1234, ["alpha", "beta", "gamma"]); // "beta"
```

Supported list sizes are 10, 20, 30, 50, and 100. The default is 20.
## API Reference

### Default API (with cities-20)

```typescript
import codename from "codenames"; // default export
import { codename } from "codenames"; // named export

codename(input: number, words?: readonly string[]): string
```

- `input` - The number to convert
- `words` - Optional array of words to use (defaults to cities-20)

### Core API (with custom words)

```typescript
import codename from "codenames/core"; // default export
import { codename } from "codenames/core"; // named export

codename(input: number, words: readonly string[]): string
```

- `input` - The number to convert
- `words` - Required array of words to use

### Themed APIs

```typescript
import codename from "codenames/cities-20"; // default export
import { codename } from "codenames/animals-50"; // named export
// ... and many more

codename(input: number): string
```

All APIs support the same input types:

- Positive integers: `123`, `1234`
- Negative integers: `-42`
- Decimals: `3.14` (converted to integers internally)
- Large numbers: up to `Number.MAX_SAFE_INTEGER`

Supported list sizes are 10, 20, 30, 50, and 100. The default theme uses 20 words.

## Command Line Interface

Expand Down Expand Up @@ -132,18 +211,20 @@ done

## Word Lists

- **Adjectives**: good, bad, big, small, new, old, hot, cold, fast, slow, ...
- **Animals**: cat, dog, fish, bird, cow, pig, bee, ant, bat, fly, ...
- **Cities**: paris, london, rome, tokyo, berlin, madrid, sydney, moscow, cairo, dubai, ...
- **Clothing**: shirt, jeans, shoe, hat, sock, dress, coat, belt, tie, pants, ...
- **Colors**: red, blue, green, yellow, black, white, gray, pink, orange, purple, ...
- **Countries**: china, japan, india, france, italy, spain, canada, mexico, brazil, germany, ...
- **Elements**: gold, iron, lead, zinc, tin, copper, silver, carbon, oxygen, helium, ...
- **Emotions**: love, hate, joy, sad, fear, mad, happy, angry, glad, calm, ...
- **Food**: bread, milk, egg, rice, meat, fish, cake, apple, cheese, pasta, ...
- **Gems**: ruby, pearl, jade, opal, amber, diamond, emerald, gold, silver, topaz, ...
- **Nature**: tree, sun, sky, rain, moon, star, wind, sea, water, rock, ...
- **Snacks**: chips, nuts, cookie, pretzel, popcorn, candy, fruit, cheese, cracker, yogurt, ...
Each theme is available in multiple sizes: 10, 20, 30, 50, and 100 words. Choose based on your collision tolerance needs.

- **[Adjectives](words/adjectives.txt)**: good, bad, big, small, new, old, hot, cold, fast, slow, ...
- **[Animals](words/animals.txt)**: cat, dog, fish, bird, cow, pig, bee, ant, bat, fly, ...
- **[Cities](words/cities.txt)**: paris, london, rome, tokyo, berlin, madrid, sydney, moscow, cairo, dubai, ...
- **[Clothing](words/clothing.txt)**: shirt, jeans, shoe, hat, sock, dress, coat, belt, tie, pants, ...
- **[Colors](words/colors.txt)**: red, blue, green, yellow, black, white, gray, pink, orange, purple, ...
- **[Countries](words/countries.txt)**: china, japan, india, france, italy, spain, canada, mexico, brazil, germany, ...
- **[Elements](words/elements.txt)**: gold, iron, lead, zinc, tin, copper, silver, carbon, oxygen, helium, ...
- **[Emotions](words/emotions.txt)**: love, hate, joy, sad, fear, mad, happy, angry, glad, calm, ...
- **[Food](words/food.txt)**: bread, milk, egg, rice, meat, fish, cake, apple, cheese, pasta, ...
- **[Gems](words/gems.txt)**: ruby, pearl, jade, opal, amber, diamond, emerald, gold, silver, topaz, ...
- **[Nature](words/nature.txt)**: tree, sun, sky, rain, moon, star, wind, sea, water, rock, ...
- **[Snacks](words/snacks.txt)**: chips, nuts, cookie, pretzel, popcorn, candy, fruit, cheese, cracker, yogurt, ...

## Contributing

Expand Down
24 changes: 22 additions & 2 deletions cities.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
/* SPDX-FileCopyrightText: 2025-present Kriasoft */
/* SPDX-License-Identifier: MIT */

import { describe, expect, test } from "bun:test";
import cityCodename, { cities, type City } from "./words/cities-20";
import cityCodename, { cities, codename, type City } from "./words/cities-20";

describe("cities-20 theme", () => {
describe("structure validation", () => {
Expand All @@ -26,37 +29,54 @@ describe("cities-20 theme", () => {
expect(cities).toContain(result);
});

test("exports named codename function", () => {
const result = codename(1234);
expect(cities).toContain(result);
expect(result).toBe(cityCodename(1234));
});

test("returns consistent results", () => {
expect(cityCodename(1)).toBe(cityCodename(1));
expect(codename(1)).toBe(codename(1));
expect(cityCodename(1234)).toBe(cityCodename(1234));
expect(codename(1234)).toBe(codename(1234));
expect(cityCodename(-42)).toBe(cityCodename(-42));
expect(codename(-42)).toBe(codename(-42));
});

test("TypeScript types work correctly", () => {
const city: City = cityCodename(123);
expect(cities).toContain(city);

const typedResult: City = cityCodename(1234);
const typedResult: City = codename(1234);
expect(typedResult).toBe(cityCodename(1234));
});

test("works as factory-created codename function", () => {
expect(typeof cityCodename).toBe("function");
expect(typeof codename).toBe("function");
const result = cityCodename(1234);
expect(cities).toContain(result);
});

test("handles various inputs", () => {
expect(cities).toContain(cityCodename(0));
expect(cities).toContain(codename(0));
expect(cities).toContain(cityCodename(-1));
expect(cities).toContain(codename(-1));
expect(cities).toContain(cityCodename(999999));
expect(cities).toContain(codename(999999));
expect(cities).toContain(cityCodename(Number.MAX_SAFE_INTEGER));
expect(cities).toContain(codename(Number.MAX_SAFE_INTEGER));
});

test("throws on invalid inputs", () => {
expect(() => cityCodename(NaN)).toThrow();
expect(() => codename(NaN)).toThrow();
expect(() => cityCodename(Infinity)).toThrow();
expect(() => codename(Infinity)).toThrow();
expect(() => cityCodename(-Infinity)).toThrow();
expect(() => codename(-Infinity)).toThrow();
});
});
});
Loading