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

nano64

v1.3.1

Published

64-bit Time-Sortable Identifiers for TypeScript

Readme

Nano64 — 64-bit Time-Sortable Identifiers for TypeScript

Nano64 is a lightweight library for generating time-sortable, globally unique IDs that provide similar practical guarantees to ULID or UUID while using half the storage.
This reduces index and I/O overhead while preserving cryptographic-grade randomness. Includes optional monotonic sequencing and AES-GCM encryption for timestamp privacy.

GitHub Repo stars NPM Version JSR Version npm package minimized gzipped size TypeScript License: MIT

Features

  • Time-sortable: IDs naturally order by creation time.
  • Compact: 8 bytes (16 hex characters).
  • Deterministic layout: [63‥20]=timestamp, [19‥0]=random.
  • Collision-resistant: ~1 % collision probability at 145 000 IDs/s.
  • Cross-database-safe: Big-endian bytes preserve order in SQLite, PostgreSQL, MySQL, and others.
  • AES-GCM encryption: Optionally hides the embedded timestamp.
  • Unsigned canonical form: Portable numeric representation 0‥2⁶⁴ − 1.
  • Typed and tested: 100 % TypeScript with full Vitest coverage.

Installation

npm install nano64

Usage

Basic ID generation

import { Nano64 } from "nano64";

const id = Nano64.generate();

console.log(id.toHex());        // 17-char uppercase hex TIMESTAMP-RANDOM
// 199C01B6659-5861C
console.log(id.toBytes());      // Uint8Array(8)
// [25,156,1,182,101,149,134,28]
console.log(id.getTimestamp()); // ms since epoch
// 1759864645209

Monotonic generation

Ensures strictly increasing values even when created within the same millisecond.

const a = Nano64.generateMonotonic();
const b = Nano64.generateMonotonic();

console.log(Nano64.compare(a, b)); // -1

AES-GCM encryption

Encrypt and decrypt IDs to hide the embedded timestamp from public view. Encrypted IDs can be safely shared to the internet without exposing any timestamp information of the source ID.

const key = await crypto.subtle.generateKey({ name: "AES-GCM", length: 256 }, true, ["encrypt", "decrypt"]);
const factory = Nano64.encryptedFactory(key);

// Generate and encrypt
const wrapped = await factory.generateEncrypted();
console.log(wrapped.id.toHex()) // Unencrypted ID
// 199C01B66F8-CB911
console.log(wrapped.toEncryptedHex()); // 72‑char hex payload
// 2D5CEBF218C569DDE077C4C1F247C708063BAA93B4285CD67D53327EA4C374A64395CFF0

// Decrypt later
const restored = await factory.fromEncryptedHex(wrapped.toEncryptedHex());
console.log(restored.id.value === wrapped.id.value); // true

Comparison with Other Identifiers

| Property | Nano64 | ULID | UUIDv4 | Snowflake ID | | ---------------------- | ----------------------------------------- | --------------------------- | ----------------------- | ---------------------------- | | Bits total | 64 | 128 | 128 | 64 | | Encoded timestamp bits | 44 | 48 | 0 | 41 | | Random / entropy bits | 20 | 80 | 122 | 22 (per-node sequence) | | Sortable by time | ✅ Yes (lexicographic & numeric) | ✅ Yes | ❌ No | ✅ Yes | | Collision risk (1%) | ~145 IDs/ms | ~26M/ms | Practically none | None (central sequence) | | Typical string length | 16 hex chars | 26 Crockford base32 | 36 hex+hyphens | 18–20 decimal digits | | Encodes creation time | ✅ | ✅ | ❌ | ✅ | | Can hide timestamp | ✅ via AES-GCM encryption | ⚠️ Not built-in | ✅ (no time field) | ❌ Not by design | | Database sort order | ✅ Stable with big-endian BLOB | ✅ (lexical) | ❌ Random | ✅ Numeric | | Cryptographic strength | 20-bit random, optional AES | 80-bit random | 122-bit random | None (deterministic) | | Dependencies | None (crypto optional) | None | None | Central service or worker ID | | Target use | Compact, sortable, optionally private IDs | Human-readable sortable IDs | Pure random identifiers | Distributed service IDs |

Nano64 keeps the chronological behavior of ULIDs but in 64 bits instead of 128, cutting key size by half without sacrificing sort order or safety.

Database Usage

Nano64 IDs are time-sortable, enabling index-only time-range queries without needing a separate timestamp column.

Time-Based Range Queries

Use Nano64.timeRangeToBytes(startMs, endMs) to obtain the lowest and highest possible Nano64 values for a given time window. These can be used directly in a SQL BETWEEN clause to select all rows created within that range.

Using Nano64 as the primary key is recommended, since the database’s native index makes these range queries extremely fast.

Storing Nano64 IDs in SQL

Store IDs as unsigned big-endian bytes using id.toBytes() and a byte-ordered column type.

| DBMS | Column Type | Notes | | ----------- | ----------- | ----------------------------------------------------- | | SQLite | BLOB(8) | Lexicographic byte order matches unsigned big-endian. | | PostgreSQL | BYTEA(8) | Works with primary key indexes. | | MySQL 8+ | BINARY(8) | Use binary collation. | | MariaDB | BINARY(8) | Same as MySQL. | | SQL Server | BINARY(8) | Clustered index sorts by bytes. | | Oracle | RAW(8) | Bytewise comparison. | | CockroachDB | BYTES(8) | Bytewise ordering. | | DuckDB | BLOB(8) | Bytewise ordering. |

SQLite Example

import Database from "better-sqlite3";
import { Nano64 } from "nano64";

const db = new Database(":memory:");
db.exec("CREATE TABLE events (id BLOB PRIMARY KEY, message TEXT)");

// generate IDs
const id1 = Nano64.generate(Date.now() - 2000);
const id2 = Nano64.generate(Date.now() - 1000);
const id3 = Nano64.generate(Date.now());  

// insert records
const insert = db.prepare("INSERT INTO events (id, message) VALUES (?, ?)");
insert.run(id1.toBytes(), "Event from 2s ago");
insert.run(id2.toBytes(), "Event from 1s ago");
insert.run(id3.toBytes(), "Event from now");

// search for rows between now and 1.5 seconds ago
const tsEnd = Date.now();
const tsStart = tsEnd - 1500;

const [ start, end ] = Nano64.timeRangeToBytes(tsStart, tsEnd);

const query = db.prepare("SELECT * FROM events WHERE id BETWEEN ? AND ?");
const results = query.all(start, end);

// Will only get 2 rows
console.log(`Found ${results.length} events between ${new Date(tsStart).toISOString()} and ${new Date(tsEnd).toISOString()}`);

for (const row of results) {
  const found = Nano64.fromBytes(row.id);
  console.log(`- ${found.toHex()} @ ${found.toDate().toISOString()} → ${row.message}`);
}

You can also store IDs as integers in your database, which can increase performance but comes with some caveats. Database integer storage instructions.

API Summary

Nano64.generate(timestamp?, rng?)

Creates a new ID with optional timestamp and RNG.

Nano64.generateMonotonic(timestamp?, rng?)

Same as generate, but strictly increasing within the same millisecond.

Nano64.fromHex(hex) / fromBytes(bytes) / fromBigIntUnsigned(v)

Parse back into a Nano64.

id.toHex() / id.toBytes() / id.toDate() / id.getTimestamp()

Export utilities.

Nano64.compare(a,b) / id.equals(b)

Comparison helpers.

Nano64.encryptedFactory(key, clock?)

Returns an object with encrypt, generateEncrypted, fromEncryptedBytes, and fromEncryptedHex.

Design

| Bits | Field | Purpose | Range | | ---- | -------------- | ------------------- | -------------- | | 44 | Timestamp (ms) | Chronological order | 1970–2527 | | 20 | Random | Collision avoidance | 1 048 576 / ms |

Collision characteristics:

  • Theoretical: ~1% collision probability at 145 IDs/millisecond
  • Real-world sustained rate (145k IDs/sec): <0.05% collision rate
  • High-speed burst (3.4M IDs/sec): ~0.18% collision rate
  • Concurrent generation (10.6M IDs/sec): ~0.58% collision rate

Reference: go-nano64 data

Tests

npm test

All tests are written in Vitest and cover:

  • Hex ↔ byte conversions
  • BigInt encoding
  • Timestamp extraction and monotonic logic
  • AES-GCM encryption / decryption integrity
  • Overflow and edge-case handling
  • BLOB Primary key and range queries with SQLite

Unofficial Ports

License

MIT License

Keywords

nano64, ulid, time-sortable, 64-bit id, bigint, aes-gcm, uid, uuid alternative,
distributed id, database key, monotonic id, sortable id, crypto id,
typescript, nodejs, browser, timestamp id