npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

genauid

v2.0.0

Published

A lean, fast, and efficient package for generating time-based, sortable, and unique UUIDs for use as database primary keys.

Readme

genauid

GenauID is a high-performance, environment-agnostic utility for generating time-based, lexicographically sortable, and cryptographically random unique identifiers. It works in Node.js, browsers, Cloudflare Workers, Deno, and Bun.

Inspired by the stern and precise First-Class Mage Genau from Frieren: Beyond Journey's End, this package is designed for systems where order, efficiency, and unwavering reliability are non-negotiable.

Key Features

  • Sortable by creation time — lexicographic order equals chronological order, enabling efficient B-tree index range scans.
  • Cryptographically random — uses globalThis.crypto.getRandomValues() with bias elimination, never Math.random().
  • Environment-agnostic — no Node.js built-ins required; works in browsers, Cloudflare Workers, Deno, and Bun out of the box.
  • Dual CJS + ESM output — ships dist/index.cjs and dist/index.mjs with full tree-shaking support ("sideEffects": false).
  • Highly customisable — length, character set, timestamp width, and separator are all configurable.
  • Human-readable slugsslugify() converts any string to a URL-safe slug, with optional random or timestamp suffix for collision avoidance.
  • Validation — built-in validator checks format, character set, and optionally enforces a maximum age.
  • Full TypeScript types.d.ts / .d.mts declarations generated from TypeScript source.
  • 100% test coverage — 92 tests covering edge cases, security, and performance.

Requirements

  • Node.js ≥ 15, or any runtime that exposes the Web Crypto API (globalThis.crypto): modern browsers, Cloudflare Workers, Deno, Bun.

Installation

npm install genauid

Quick Start

import { generate, slugify, validate, CHARSETS } from 'genauid';

// Generate a time-based sortable ID (26 chars, BASE32 charset by default)
const id = generate();
console.log(id); // e.g. '01J3RVMQ8Z4KXNTBPD6S7WHMF'

// Slugify a string (plain)
const slug = slugify('Hello World');
console.log(slug); // 'hello-world'

// Slugify with a timestamp suffix for collision-safe unique slugs
const uniqueSlug = slugify('Hello World', { suffix: 'timestamp' });
console.log(uniqueSlug); // e.g. 'hello-world-01j3rvmq8z-k4xntbpd'

// Validate a previously generated ID
const result = validate(id);
console.log(result.valid);     // true
console.log(result.timestamp); // Date object: the generation time

API

generate(options?): string

Generates a time-based, cryptographically random, lexicographically sortable ID.

The ID starts with a fixed-width timestamp prefix (chronologically sortable) followed by a cryptographically random suffix.

Options

| Option | Type | Default | Description | |---|---|---|---| | length | number | 26 | Total length of the generated ID (10–128). | | tsLength | number | 10 | Number of leading characters encoding the timestamp. | | charset | string | CHARSETS.BASE32 | Character set to draw characters from. | | separator | string | '' | Optional separator inserted between timestamp and random parts. |

Examples

// Default — 26-char BASE32 ID
generate();
// '01J3RVMQ8Z4KXNTBPD6S7WHMF'

// Custom length (32 chars)
generate({ length: 32 });

// Custom charset
generate({ charset: CHARSETS.ALPHANUMERIC, length: 32 });

// With separator (makes the timestamp boundary visible)
generate({ tsLength: 10, length: 27, separator: '-' });
// '01J3RVMQ8Z-4KXNTBPD6S7WH'

// HEX IDs for legacy systems
generate({ charset: CHARSETS.HEX, length: 32 });

slugify(str, options?): string

Converts a string into a URL-friendly slug. Optionally appends a random or timestamp-based suffix to guarantee uniqueness.

Options

| Option | Type | Default | Description | |---|---|---|---| | charset | string | CHARSETS.SLUG | Character set to allow (lowercase alphanumeric by default). | | separator | string | '-' | Separator used between words and suffix parts. | | suffix | 'none'\|'random'\|'timestamp' | 'none' | Suffix mode: 'none' = plain slug; 'random' = append random string; 'timestamp' = append encoded timestamp + random string. | | randomLength | number | 8 | Characters for the random part (used when suffix is 'random' or 'timestamp'). | | tsLength | number | 10 | Characters for the timestamp part (used when suffix is 'timestamp'). |

Examples

// Plain slug
slugify('Hello World!');
// 'hello-world'

// Diacritics / accents are removed
slugify('Café au lait');
// 'cafe-au-lait'

// Custom separator
slugify('Café au lait', { separator: '_' });
// 'cafe_au_lait'

// Append a random suffix (collision-resistant)
slugify('Hello World', { suffix: 'random' });
// 'hello-world-a3b8x2k4'

// Append a timestamp + random suffix (sortable and collision-safe)
slugify('Hello World', { suffix: 'timestamp' });
// 'hello-world-01j3rvmq8z-k4xntbpd'

// Custom suffix length
slugify('Hello World', { suffix: 'random', randomLength: 12 });
// 'hello-world-a3b8x2k4j9p2'

validate(id, options?): ValidationResult

Validates a previously generated ID.

Checks performed:

  1. Input is a non-empty string.
  2. Length matches expectation (when length is provided).
  3. All characters belong to the expected charset.
  4. The embedded timestamp is not in the future (within tolerated clock skew).
  5. The embedded timestamp is not before year 2000.
  6. The ID has not exceeded maxAgeMs (when provided).

Options

| Option | Type | Default | Description | |---|---|---|---| | charset | string | CHARSETS.BASE32 | Expected character set. | | length | number | — | Expected total length (optional). | | tsLength | number | 10 | Expected timestamp prefix length. | | separator | string | '' | Expected separator used during generation. | | maxAgeMs | number | — | Reject IDs older than this many milliseconds (optional). | | clockSkewMs | number | 5000 | Tolerated future clock skew in milliseconds. |

Return value: ValidationResult

{
  valid: boolean;      // true if all checks pass
  errors: string[];    // list of failure reasons (empty when valid)
  timestamp: Date | null; // extracted Date from the embedded timestamp
}

Examples

const id = generate();
const result = validate(id);
// { valid: true, errors: [], timestamp: Date }

// Validate with full options
validate(id, {
  charset: CHARSETS.BASE32,
  length: 26,
  maxAgeMs: 30_000, // reject IDs older than 30 seconds
});

// Validate a slug
validate(slug, {
  charset: CHARSETS.SLUG,
  separator: '-',
  tsLength: 10,
});

decodeTimestamp(encoded, charset): bigint

Decodes the timestamp prefix of an ID back into a BigInt millisecond value.

import { decodeTimestamp, CHARSETS } from 'genauid';

const tsPart = id.slice(0, 10);
const ms = decodeTimestamp(tsPart, CHARSETS.BASE32);
console.log(new Date(Number(ms))); // generation Date

CHARSETS

Built-in character sets:

| Name | Characters | Notes | |---|---|---| | CHARSETS.BASE32 | 0–9 A–Z (Crockford variant) | Default for generate(). Excludes I, L, O, U to avoid visual confusion. | | CHARSETS.SLUG | 0–9 a–z | Default for slugify(). URL-safe, lowercase. | | CHARSETS.ALPHANUMERIC | 0–9 A–Z a–z | Maximum density (62 symbols). | | CHARSETS.HEX | 0–9 a–f | Hexadecimal — lowest density, widest compatibility. |

CHARSET alias

CHARSET is a convenience alias for CHARSETS, enabling the single-word import pattern:

import { generate, slugify, CHARSET } from 'genauid';
generate({ charset: CHARSET.BASE32 });

You can also pass any custom string as a charset. Requirements:

  • Minimum 2 characters.
  • No duplicate characters.
  • Characters should be in ASCII-sorted order for guaranteed lexicographic sortability (the timestamp prefix relies on this).

Sortability guarantee

The timestamp prefix is encoded as a fixed-width base-N number where the digit symbols are in ascending ASCII order. This means:

encode(t1) < encode(t2)  ⟺  t1 < t2

All built-in charsets (BASE32, SLUG, ALPHANUMERIC, HEX) are in ascending ASCII order, so lexicographic sorting of generated IDs equals chronological sorting.

Custom charsets: ensure your charset characters are in ascending ASCII order if sortability is required.


Uniqueness

Each ID combines:

  • A millisecond-precision timestamp (common prefix within the same millisecond).
  • A cryptographically random suffix produced via crypto.randomBytes with modulo-bias elimination.

With a 16-character BASE32 random suffix the probability of a collision within the same millisecond is approximately $1 / 32^{16} \approx 10^{-24}$, making collisions practically impossible.


Security considerations

  • The random component is generated with globalThis.crypto.getRandomValues(), which is cryptographically secure across all supported runtimes.
  • Modulo bias is eliminated by rejection sampling.
  • The validation function uses a pure constant-time XOR comparison to prevent timing attacks (no longer depends on Node's timingSafeEqual).
  • IDs embed a timestamp and are therefore not suitable as opaque secrets (an attacker can determine when an ID was created). Use them as primary keys, not as authentication tokens.

Performance

Benchmarked on Node.js 24 (Apple M-class / modern x86):

| Operation | Throughput | |---|---| | generate() | > 100,000 IDs/s | | slugify() (plain) | > 100,000 slugs/s | | validate() | > 500,000 validations/s |

Run the built-in performance tests:

npm test

Running tests

npm test               # run tests with coverage report
npm run test:watch     # watch mode

Coverage thresholds (enforced):

  • Statements: 90%
  • Branches: 90%
  • Functions: 90%
  • Lines: 90%

Actual coverage of this release: 100% statements, 97.84% branches, 100% functions, 100% lines.

92 tests pass, ESLint clean, 100% statement/function/line coverage.

Here's the full security assessment:


Automated checks

| Check | Result | |---|---| | npm audit | 0 vulnerabilities | | eslint-plugin-security | 0 warnings/errors | | tsc --noEmit | 0 type errors |


Manual review — all clear

| Area | What was checked | Status | |---|---|---| | Random generation | globalThis.crypto.getRandomValues() only — no Math.random() | ✅ | | Modulo bias | Rejection sampling eliminates skew | ✅ | | Timing attacks | Pure constant-time XOR comparison — no Node.js dependency | ✅ | | RegExp injection | escapeRegex() sanitises separator before new RegExp() | ✅ | | eval / new Function | None present | ✅ | | Prototype pollution | Options read with !== undefined guards, not in or bare access | ✅ | | Input bounds | All lengths, charsets, and separators validated before use | ✅ | | NaN/Infinity inputs | Fixed — maxAgeMs and clockSkewMs now reject non-finite/invalid values with a clear error | ✅ |

One inherent design note (not a vulnerability): IDs embed a millisecond timestamp, so anyone who holds an ID can determine when it was created — don't use them as secrets or authentication tokens, only as primary keys.


TypeScript

The package ships with .d.ts and .d.mts declaration files generated from TypeScript source (no hand-written stubs). An exports map ensures it works out of the box with all TypeScript moduleResolution modes — including node, node16, and bundler.

import { generate, slugify, validate, CHARSETS, CHARSET } from 'genauid';
import type { GenerateOptions, SlugOptions, ValidateOptions, ValidationResult } from 'genauid';

const id: string = generate({ length: 32, charset: CHARSETS.ALPHANUMERIC });
const slug: string = slugify('Hello World', { suffix: 'timestamp' });
const result: ValidationResult = validate(id);

Limitations

  • IDs are not RFC 4122 UUIDs. If strict UUID format compliance is required, use the uuid package instead.
  • The timestamp has millisecond precision. Multiple IDs generated within the same millisecond share the same timestamp prefix and are ordered randomly among themselves.
  • Maximum ID length is 128 characters.

License

MIT