@grkndev/snowflakeid
Version:
A simple Snowflake ID generator for JavaScript and TypeScript
174 lines (150 loc) • 5 kB
text/typescript
/**
* The default epoch timestamp used for Snowflake ID generation.
* Set to 2021-01-01T00:00:00Z (1609459200000 in milliseconds since Unix epoch).
*/
const DEFAULT_EPOCH = 1609459200000;
/**
* Options for customizing Snowflake ID generation.
*/
interface SnowflakeOptions {
/** Custom epoch timestamp (in milliseconds since Unix epoch) */
epoch?: number;
/** Node ID (0-1023) for distributed systems */
nodeId?: number;
/** Initial sequence number (0-4095) */
sequence?: number;
}
interface RandomIdOptions {
useChars: boolean;
useNumbers: boolean;
}
let globalSequence = 0;
let lastTimestamp = -1;
/**
* Generates a Snowflake ID.
*
* @param {SnowflakeOptions} options - Custom options for ID generation
* @param {number} [options.epoch=DEFAULT_EPOCH] - Custom epoch timestamp
* @param {number} [options.nodeId=1] - Node ID for distributed systems
* @param {number} [options.sequence] - Initial sequence number
* @returns {string} The generated Snowflake ID as a string
* @throws {Error} If nodeId is invalid or if the clock moves backwards
*/
function generateSnowflakeId(options: SnowflakeOptions = {}): string {
const {
epoch = DEFAULT_EPOCH,
nodeId = 1,
sequence: initialSequence,
} = options;
if (nodeId < 0 || nodeId > 1023) {
throw new Error("Node ID must be between 0 and 1023");
}
if (epoch < 0 || epoch > Date.now()) {
throw new Error("Epoch must be a valid timestamp not in the future");
}
let timestamp = Date.now();
let sequence = initialSequence !== undefined ? initialSequence : globalSequence;
if (timestamp < lastTimestamp) {
throw new Error("Clock moved backwards. Refusing to generate id");
}
if (timestamp === lastTimestamp) {
sequence = (sequence + 1) & 4095;
if (sequence === 0) {
timestamp = waitNextMillis(lastTimestamp);
}
} else {
sequence = initialSequence !== undefined ? initialSequence : 0;
}
lastTimestamp = timestamp;
globalSequence = sequence;
const id =
(BigInt(timestamp - epoch) << 22n) |
(BigInt(nodeId) << 12n) |
BigInt(sequence);
return id.toString();
}
/**
* Waits until the next millisecond.
*
* @param {number} lastTimestamp - The last timestamp generated
* @returns {number} The next available timestamp
*/
function waitNextMillis(lastTimestamp: number): number {
let timestamp = Date.now();
while (timestamp <= lastTimestamp) {
timestamp = Date.now();
}
return timestamp;
}
/**
* Parses a Snowflake ID into its component parts.
*
* @param {string} id - The Snowflake ID to parse
* @param {number} [epoch=DEFAULT_EPOCH] - The epoch used in ID generation
* @returns {{timestamp: Date, nodeId: number, sequence: number}} Parsed components of the Snowflake ID
*/
function parseSnowflakeId(
id: string,
epoch: number = DEFAULT_EPOCH
): {
timestamp: Date;
nodeId: number;
sequence: number;
} {
const snowflake = BigInt(id);
const timestamp = Number(snowflake >> 22n) + epoch;
const nodeId = Number((snowflake >> 12n) & 1023n);
const sequence = Number(snowflake & 4095n);
return {
timestamp: new Date(timestamp),
nodeId,
sequence,
};
}
/**
* Generates a random ID with a specified length.
* @param {RandomIdOptions} options - Custom options for ID generation
* @param {number} [lenght=6] - The length of the random ID
* @param {boolean} [options.useChars=false] - Use characters in the ID
* @param {boolean} [options.useNumbers=true] - Use numbers in the ID
* @returns {string} The generated random ID (example: "aBc123")
*/
function randomId(
length: number = 6,
options: RandomIdOptions = { useChars: false, useNumbers: true }
): string {
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
const numbers = "0123456789";
let result = "";
let characters = "";
if (options.useChars) characters += chars;
if (options.useNumbers) characters += numbers;
if (characters.length === 0) {
throw new Error("At least one of useChars or useNumbers must be true");
}
for (let i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * characters.length));
}
return result;
}
/**
* Generates a UUID (Universally Unique Identifier).
* @param {number} [split=4] - The number of parts to split the UUID into
* @returns {string} The generated UUID (example: "aBcD-1234-EfGh-5678")
*/
function uuid(split: number = 4) {
let uuid = randomId(8 * split, { useChars: true, useNumbers: true });
let result = [];
for (let i = 0; i < split; i++) {
result.push(uuid.substring(i * 8, (i + 1) * 8));
}
return result.join("-");
}
export { generateSnowflakeId, parseSnowflakeId, randomId, uuid };
// CommonJS default export compatibility
export default {
generateSnowflakeId,
parseSnowflakeId,
randomId,
uuid
};