- Multi-platform Support: Works in browsers, Node.js, Service Workers, and Edge Workers (Cloudflare Workers, etc.)
- Edge Mode (Entropy): Optional
mode: 'edge'generates IDs with cryptographic randomness β no worker-id assignment needed for serverless/edge runtimes - Zero Dependencies: Lightweight with no external dependencies
- Functional Style: Pure functions without classes
- Time-sortable: IDs are chronologically sortable
- High Performance: Fast ID generation with built-in sequence handling
- TypeScript: Full type support with comprehensive type definitions
- Service Worker Support: Generate IDs in background contexts without blocking the main thread
npm install @tknf/snowflakeimport { createSnowflake, generateSnowflakeId } from '@tknf/snowflake';
// Create a generator function
const generateId = createSnowflake();
const id1 = generateId(); // "1234567890123456789"
const id2 = generateId(); // "1234567890123456790"
// Or generate a single ID
const id = generateSnowflakeId(); // "1234567890123456791"import { createSnowflake } from '@tknf/snowflake';
const generateId = createSnowflake({
epoch: 1640995200000, // Custom epoch (2022-01-01)
datacenterId: 1, // Datacenter ID (0-31)
workerId: 5 // Worker ID (0-31)
});
const id = generateId();For serverless / edge runtimes (e.g. Cloudflare Workers) where you cannot assign a stable
workerId to each ephemeral isolate, use mode: 'edge'. Instead of an assigned
(datacenterId, workerId) + sequence, the 22 non-timestamp bits are filled with
cryptographic randomness (crypto.getRandomValues). This removes the need for any
worker-id assignment and ensures uniqueness probabilistically via "timestamp + 22-bit entropy".
import { createSnowflake } from '@tknf/snowflake';
// No datacenterId / workerId required
const generateId = createSnowflake({ mode: 'edge', epoch: 1640995200000 });
const id = generateId(); // "1843927461029384756" (numeric string, chronologically sortable)modedefaults to'default'(the existing assigned worker-id behavior).'edge'is opt-in.- In edge mode
datacenterId/workerIdare ignored (even if provided). - The output is unchanged: a numeric string that fits a 63-bit positive integer (storable in a
SQLite / Turso
INTEGERcolumn) and remains chronologically sortable at millisecond granularity.
Uniqueness is probabilistic. 22 bits = 4,194,304 possibilities per millisecond. When generating
kIDs within the same millisecond the collision probability (birthday bound) is approximatelykΒ² / (2Β·2Β²Β²)(e.g. ~0.12% at 100 IDs/ms). Because of this, consumers should add a DBUNIQUEconstraint and retry-on-collision as a backstop so that even a theoretical collision causes no actual harm.
defaultmode: if the system clock moves backwards,createSnowflake()throws an error ("Clock moved backwards. Refusing to generate id") to avoid emitting a duplicate or out-of-order ID.edgemode: clock-backwards is not checked, and generation continues regardless. This is intentional β edge mode's whole purpose is to keep working in ephemeral/serverless environments without throwing, and the 22 bits of entropy already make the resulting collision risk negligible.
Approximate collision probability for n IDs generated within the same millisecond in edge
mode (birthday approximation, p β nΒ² / 2Β²Β³):
| IDs per millisecond | Collision probability |
|---|---|
| 10 | ~0.001% |
| 100 | ~0.12% |
| 1,000 | ~11% |
If you need to generate a large volume of IDs from a single long-lived process, prefer
default mode (with an assigned datacenterId / workerId), which avoids this probabilistic
risk entirely. If you need strict monotonicity without a stable worker id, see
Monotonic edge mode below.
Pass monotonic: true alongside mode: 'edge' to guarantee strict uniqueness and
generation-order sortability within a single generator:
import { createSnowflake } from '@tknf/snowflake';
const generateId = createSnowflake({ mode: 'edge', monotonic: true });
const id1 = generateId();
const id2 = generateId();
// id1 < id2 is guaranteed, even when generated within the same millisecond- Within the same millisecond, instead of drawing a fresh random entropy value each time,
the generator increments the previous entropy value. This makes every ID produced by a
single
createSnowflakegenerator strictly unique and sorted in generation order. - If the 22-bit entropy space is exhausted within a millisecond, the generator waits for the next millisecond and draws a fresh random entropy value.
- If the system clock moves backwards, the generator does not throw β it keeps using the last observed timestamp so IDs keep increasing monotonically instead of duplicating or going out of order.
- Uniqueness and ordering guarantees apply per generator. Across multiple independent generators (e.g. multiple edge isolates), uniqueness remains probabilistic, the same as regular edge mode.
// src/node.ts - Node.js specific utilities
import { createSnowflakeFromEnv, generateFromEnv } from '@tknf/snowflake/node';
// Set environment variables
process.env.SNOWFLAKE_EPOCH = '1640995200000';
process.env.SNOWFLAKE_DATACENTER_ID = '1';
process.env.SNOWFLAKE_WORKER_ID = '5';
// Create generator from environment
const generateId = createSnowflakeFromEnv();
const id1 = generateId();
// Or generate single ID from environment
const id2 = generateFromEnv();
// Override environment with custom config
const id3 = generateFromEnv({ workerId: 10 });// src/browser.ts - Browser specific utilities
import {
createSnowflakeFromStorage,
generateFromStorage,
initializeBrowserConfig,
saveConfigToStorage
} from '@tknf/snowflake/browser';
// Initialize browser-specific config (auto-generated from browser characteristics)
const config = initializeBrowserConfig({
save: true, // Save to localStorage
config: { epoch: 1640995200000 } // Optional custom config
});
// Create generator using localStorage + browser fingerprint
const generateId = createSnowflakeFromStorage();
const id1 = generateId();
// Or generate single ID from storage
const id2 = generateFromStorage();
// Manual config management
saveConfigToStorage({ datacenterId: 5, workerId: 10 });
const id3 = generateFromStorage();Creates a Snowflake ID generator function.
Parameters:
config(optional): Configuration objectepoch(number): Custom epoch timestamp in milliseconds (default: 2020-01-01)datacenterId(number): Datacenter ID (0-31, default: 0). Ignored whenmodeis'edge'.workerId(number): Worker ID (0-31, default: 0). Ignored whenmodeis'edge'.mode('default' | 'edge'): Generation mode (default:'default'). See Edge Mode (Entropy).monotonic(boolean): Enable monotonic entropy in'edge'mode (default:false). Ignored in'default'mode. See Monotonic edge mode.
Returns: Function that generates Snowflake IDs as strings
const generator = createSnowflake({
epoch: 1577836800000,
datacenterId: 1,
workerId: 2
});
const id = generator(); // "1234567890123456789"Generates a single Snowflake ID.
Parameters:
config(optional): Same ascreateSnowflake
Returns: Snowflake ID as string
const id = generateSnowflakeId({
datacenterId: 1,
workerId: 2
});Parses a Snowflake ID into its components.
Parameters:
id(string): Snowflake ID to parseepoch(number, optional): Epoch used for generation (default: 2020-01-01)mode('default' | 'edge', optional): Mode the ID was generated with (default:'default'). Edge IDs are bit-indistinguishable from default IDs, so you must pass the mode you generated with.
Returns (default mode): Object containing:
timestamp(number): Generation timestampdatacenterId(number): Datacenter IDworkerId(number): Worker IDsequence(number): Sequence numberentropy(null): Alwaysnullin default modedate(Date): Generation date
Returns (edge mode): In edge mode the non-timestamp bits are random, so the worker fields are
meaningless. They are returned as null and the raw 22-bit value is returned as entropy:
timestamp(number): Generation timestampdatacenterId/workerId/sequence(null): Not meaningful in edge modeentropy(number): The 22-bit random value (0β4,194,303)date(Date): Generation date
// default mode
const parsed = parseSnowflakeId("1234567890123456789");
// { timestamp: 1640995200123, datacenterId: 1, workerId: 2, sequence: 0, entropy: null, date: ... }
// edge mode
const edgeParsed = parseSnowflakeId("1843927461029384756", 1640995200000, "edge");
// { timestamp: ..., datacenterId: null, workerId: null, sequence: null, entropy: 4194301, date: ... }
getSnowflakeTimestampandsnowflakeToDateread only the timestamp bits, so they work identically for both default and edge IDs (nomodeargument needed).
Extracts timestamp from a Snowflake ID.
Parameters:
id(string): Snowflake IDepoch(number, optional): Epoch used for generation
Returns: Timestamp in milliseconds
const timestamp = getSnowflakeTimestamp("1234567890123456789");
console.log(timestamp); // 1640995200123Converts a Snowflake ID to a Date object.
Parameters:
id(string): Snowflake IDepoch(number, optional): Epoch used for generation
Returns: Date object
const date = snowflakeToDate("1234567890123456789");
console.log(date); // 2022-01-01T00:00:00.123ZImport from @tknf/snowflake/node for Node.js-specific features:
SNOWFLAKE_EPOCH: Custom epoch timestampSNOWFLAKE_DATACENTER_ID: Datacenter ID (0-31)SNOWFLAKE_WORKER_ID: Worker ID (0-31)
Loads configuration from environment variables.
const config = loadConfigFromEnv();
// Returns SnowflakeConfig object based on environment variablesCreates a generator with environment configuration.
const generator = createSnowflakeFromEnv({ workerId: 5 });Generates a single ID with environment configuration.
const id = generateFromEnv({ datacenterId: 2 });Import from @tknf/snowflake/browser for browser-specific features:
Fingerprint-derived IDs (datacenter/worker) can collide across multiple tabs of the same browser or environments with identical characteristics, since sequence state is independent per tab. If you generate IDs from multiple concurrent contexts, prefer
mode: 'edge'instead. See Edge Mode (Entropy).
snowflake.epoch: Custom epoch timestampsnowflake.datacenterId: Datacenter ID (0-31)snowflake.workerId: Worker ID (0-31)
Generates browser-specific configuration based on browser characteristics (user agent, timezone, screen properties, etc.).
const config = generateBrowserConfig();
// Returns consistent config for the same browser environmentLoads configuration from localStorage.
const config = loadConfigFromStorage();
// Returns SnowflakeConfig object from localStorageSaves configuration to localStorage.
saveConfigToStorage({ datacenterId: 5, workerId: 10 });Creates a generator with localStorage configuration and browser fingerprinting fallback.
const generator = createSnowflakeFromStorage({ epoch: 1640995200000 });Generates a single ID with localStorage configuration.
const id = generateFromStorage({ workerId: 15 });Initializes browser-specific configuration with optional localStorage persistence.
Parameters:
options.save(boolean): Whether to save to localStorage (default: false)options.config(SnowflakeConfig): Custom configuration to merge
const config = initializeBrowserConfig({
save: true,
config: { epoch: 1640995200000 }
});Clears all stored configuration from localStorage.
clearStoredConfig();Gets detailed browser fingerprint for debugging purposes.
const fingerprint = getBrowserFingerprint();
// Returns object with datacenterId, workerId, timezone, userAgent, screen info, etc.Snowflake IDs are 64-bit integers composed of:
1 bit | 41 bits | 5 bits | 5 bits | 12 bits
unused | timestamp | datacenter| worker | sequence
- Timestamp (41 bits): Milliseconds since custom epoch
- Datacenter ID (5 bits): Identifies the datacenter (0-31)
- Worker ID (5 bits): Identifies the worker process (0-31)
- Sequence (12 bits): Counter for same-millisecond generation (0-4095)
In edge mode (mode: 'edge'), the datacenter | worker | sequence block is replaced by 22 bits
of cryptographic randomness:
1 bit | 41 bits | 22 bits
unused | timestamp | random entropy (crypto)
0 | ms-epoch | getRandomValues
- Timestamp (41 bits): Milliseconds since custom epoch (same position as default mode)
- Entropy (22 bits):
crypto.getRandomValues(0-4,194,303)
const generator = createSnowflake({ workerId: 1 });
// Generate 1000 IDs quickly
const ids = [];
for (let i = 0; i < 1000; i++) {
ids.push(generator());
}
// All IDs are unique and sortable
console.log(ids.length === new Set(ids).size); // trueconst worker1 = createSnowflake({ workerId: 1 });
const worker2 = createSnowflake({ workerId: 2 });
const id1 = worker1(); // From worker 1
const id2 = worker2(); // From worker 2
// IDs from different workers are still unique
console.log(id1 !== id2); // true// React/Vue/etc. application
import { initializeBrowserConfig, createSnowflakeFromStorage } from '@tknf/snowflake/browser';
// Initialize on app startup
const config = initializeBrowserConfig({
save: true, // Persist across browser sessions
config: { epoch: 1640995200000 }
});
const generator = createSnowflakeFromStorage();
// Use in your app
function createNewRecord() {
const id = generator();
console.log('New record ID:', id);
return id;
}// Service Worker example
// sw.js
importScripts('https://unpkg.com/@tknf/snowflake/dist/snowflake.min.js');
self.addEventListener('message', (event) => {
if (event.data.type === 'GENERATE_ID') {
const generator = Snowflake.createSnowflake({
datacenterId: 1,
workerId: 5
});
const id = generator();
// Send back to main thread
self.clients.matchAll().then(clients => {
for (const client of clients) {
client.postMessage({
type: 'ID_GENERATED',
data: { id }
});
}
});
}
});
// Main thread
navigator.serviceWorker.controller.postMessage({
type: 'GENERATE_ID'
});In Workers your code runs across many ephemeral isolates, so you cannot assign a stable workerId
(reading env.WORKER_ID gives every isolate the same value, which risks collisions). Use
mode: 'edge' so uniqueness comes from cryptographic entropy instead of an assigned worker number.
Create the generator once at module scope, not per-request β this lets an isolate that gets reused across requests keep its state (and, for monotonic edge mode below, its ordering guarantees) instead of paying setup cost on every invocation:
import { createSnowflake } from '@tknf/snowflake';
// Create once at module scope, not per-request
const generateId = createSnowflake({ mode: 'edge' });
export default {
async fetch(request): Promise<Response> {
return new Response(generateId());
},
} satisfies ExportedHandler;Edge mode is probabilistic β pair it with a DB
UNIQUEconstraint and retry-on-collision (e.g. when using a single-writer Turso/SQLite). See Edge Mode (Entropy).
If a single isolate needs to generate many IDs in quick succession (e.g. a batch job or a
hot request path), add monotonic: true:
const generateId = createSnowflake({ mode: 'edge', monotonic: true });Within the same isolate, IDs are guaranteed to be strictly unique and sorted in generation order, even when produced in the same millisecond. See Monotonic edge mode for the full guarantees and behavior when the 22-bit entropy space is exhausted.
A Durable Object instance processes requests serially (one at a time, single-threaded), so
the collision risk that motivates edge mode's entropy-based ID doesn't apply within a single
DO β default mode is safe to use there. Derive a stable workerId from the DO's own ID so
IDs generated by the same instance share a consistent worker identity:
import { DurableObject } from 'cloudflare:workers';
import { createSnowflake } from '@tknf/snowflake';
export class IdGenerator extends DurableObject {
// Derive a stable workerId from the DO's own ID
#generate = createSnowflake({
workerId: Number(BigInt('0x' + this.ctx.id.toString().slice(0, 8)) % 32n),
});
async newId(): Promise<string> {
return this.#generate();
}
}
workerIdonly has 32 (0-31) possible values, so with more than 32 concurrently active DO instances, different instances can derive the sameworkerIdand collide. If you expect more than 32 instances, usemode: 'edge'instead.
See examples/worker for a runnable Service Worker example.
import { getBrowserFingerprint, generateBrowserConfig } from '@tknf/snowflake/browser';
// Get detailed browser information
const fingerprint = getBrowserFingerprint();
console.log('Browser fingerprint:', fingerprint);
// Consistent config for same browser
const config1 = generateBrowserConfig();
const config2 = generateBrowserConfig();
console.log(config1.workerId === config2.workerId); // true - same browser = same workerIdThe library includes comprehensive examples for different environments:
examples/iife/: IIFE browser example with localStorage integrationexamples/node/: Node.js example with environment variablesexamples/worker/: Service Worker example with background generation
Each example includes:
- Complete working code
- Performance testing
- Error handling
- Detailed README with setup instructions
# IIFE Browser Example
cd examples/iife
pnpm install
pnpm run start
# Node.js Example
cd examples/node
pnpm install
pnpm run start
# Service Worker Example
cd examples/worker
pnpm install
pnpm run startThis project uses Vite+ as its unified toolchain β bundling with tsdown, linting with Oxlint, formatting with Oxfmt, and testing with Vitest, all driven by the vp CLI.
git clone git@github.com:tknf/snowflake.git
cd snowflake
vp install# Build the library
vp pack
# Run tests
vp test run
# Run tests with coverage
vp test run --coverage
# Format + lint + type-check in one pass
vp check
# Lint only / format only
vp lint
vp fmtsrc/
βββ index.ts # Core Snowflake logic (platform-agnostic)
βββ node.ts # Node.js-specific utilities
βββ browser.ts # Browser-specific utilities
βββ iife.ts # IIFE build for browser usage
βββ index.test.ts # Core functionality tests
βββ node.test.ts # Node.js utilities tests
βββ browser.test.ts # Browser utilities tests
examples/
βββ iife/ # IIFE browser example
βββ node/ # Node.js example
βββ worker/ # Service Worker example
- Runtime: No runtime dependencies
- Development: Node.js 18+ for building and testing
- TypeScript: Full type support included
MIT License - see LICENSE file for details.
- Fork the repository
- Create a feature branch
- Make your changes
- Ensure checks pass:
vp check - Ensure tests pass:
vp test - Submit a pull request
This implementation follows the distributed ID generation pattern, designed for high-performance unique ID generation in distributed systems.
