UNPKG

@grkndev/snowflakeid

Version:

A simple Snowflake ID generator for JavaScript and TypeScript

139 lines (138 loc) 5.21 kB
/** * 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; 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 = {}) { 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) { 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, epoch = DEFAULT_EPOCH) { 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 {number} [length=6] - The length of the random ID (must be >= 1) * @param {RandomIdOptions} [options] - Custom options for ID generation * @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") * @throws {Error} If length is less than 1 or if neither useChars nor useNumbers is true */ function randomId(length = 6, options = {}) { if (length < 1) { throw new Error("Length must be at least 1"); } // If no options provided, default to numbers only // If options provided, use explicit values (undefined = false) const useChars = options.useChars ?? false; const useNumbers = options.useNumbers ?? (options.useChars === undefined ? true : false); const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; const numbers = "0123456789"; let result = ""; let characters = ""; if (useChars) characters += chars; if (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 pseudo-UUID string using random alphanumeric characters. * Note: This is NOT a standard UUID (RFC 4122). It generates a random * alphanumeric string split into segments for readability. * For cryptographically secure UUIDs, use crypto.randomUUID() instead. * @param {number} [split=4] - The number of 8-character segments to generate (must be >= 1) * @returns {string} The generated pseudo-UUID (example: "aBcD1234-EfGh5678-iJkL9012-MnOp3456") * @throws {Error} If split is less than 1 */ function uuid(split = 4) { if (split < 1) { throw new Error("Split must be at least 1"); } const id = randomId(8 * split, { useChars: true, useNumbers: true }); const result = []; for (let i = 0; i < split; i++) { result.push(id.substring(i * 8, (i + 1) * 8)); } return result.join("-"); } export { generateSnowflakeId, parseSnowflakeId, randomId, uuid }; // CommonJS default export compatibility export default { generateSnowflakeId, parseSnowflakeId, randomId, uuid };