const _crypto: Crypto = globalThis.crypto ?? (globalThis as any).crypto ?? (await import("node:crypto")).webcrypto; const ID_BYTE_SIZE = 16; const ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; const BASE = BigInt(ALPHABET.length); class InvalidEncodingError extends Error { constructor(message = "snowflake: invalid encoding string") { super(message); this.name = "InvalidEncodingError"; } } class InvalidIDLengthError extends Error { constructor(message = "snowflake: invalid ID byte length") { super(message); this.name = "InvalidIDLengthError"; } } const DECODE_LUT = new Map(); for (let i = 0; i < ALPHABET.length; i++) { DECODE_LUT.set(ALPHABET[i], i); } function bytesToBigInt(bytes: Uint8Array): bigint { let result = 0n; for (const b of bytes) { result = (result << 8n) | BigInt(b); } return result; } function bigIntToBytes(value: bigint, length: number): Uint8Array { const bytes = new Uint8Array(length); for (let i = length - 1; i >= 0; i--) { bytes[i] = Number(value & 0xffn); value >>= 8n; } return bytes; } class Snowflake { readonly bytes: Uint8Array; constructor(bytes: Uint8Array) { this.bytes = bytes; } static generate(): Snowflake { const bytes = new Uint8Array(ID_BYTE_SIZE); _crypto.getRandomValues(bytes); return new Snowflake(bytes); } static zero(): Snowflake { return new Snowflake(new Uint8Array(ID_BYTE_SIZE)); } isZero(): boolean { for (const b of this.bytes) { if (b !== 0) return false; } return true; } equals(other: Snowflake): boolean { for (let i = 0; i < ID_BYTE_SIZE; i++) { if (this.bytes[i] !== other.bytes[i]) return false; } return true; } toString(): string { if (this.isZero()) return "0"; let value = bytesToBigInt(this.bytes); let result = ""; while (value > 0n) { const remainder = Number(value % BASE); result = ALPHABET[remainder] + result; value /= BASE; } return result; } static fromString(s: string): Snowflake { if (s.length === 0) return Snowflake.zero(); let value = 0n; for (const ch of s) { const idx = DECODE_LUT.get(ch); if (idx === undefined) { throw new InvalidEncodingError(); } value = value * BASE + BigInt(idx); } if (value >= (1n << 128n)) { throw new InvalidEncodingError(); } return new Snowflake(bigIntToBytes(value, ID_BYTE_SIZE)); } toHex(): string { return Array.from(this.bytes) .map((b) => b.toString(16).padStart(2, "0")) .join(""); } static fromHex(h: string): Snowflake { if (!/^[0-9a-f]+$/i.test(h)) { throw new InvalidEncodingError(); } if (h.length !== ID_BYTE_SIZE * 2) { throw new InvalidIDLengthError(); } const bytes = new Uint8Array(ID_BYTE_SIZE); for (let i = 0; i < ID_BYTE_SIZE; i++) { bytes[i] = parseInt(h.substring(i * 2, i * 2 + 2), 16); } return new Snowflake(bytes); } toJSON(): string { return this.toString(); } static fromJSON(json: string): Snowflake { if (!json.startsWith('"') || !json.endsWith('"')) { throw new InvalidEncodingError(); } return Snowflake.fromString(json.slice(1, -1)); } toBytes(): Uint8Array { return new Uint8Array(this.bytes); } static fromBytes(source: Uint8Array | string): Snowflake { let bytes: Uint8Array; if (source instanceof Uint8Array) { bytes = source; } else { bytes = new Uint8Array(source.length); for (let i = 0; i < source.length; i++) { bytes[i] = source.charCodeAt(i) & 0xff; } } if (bytes.length !== ID_BYTE_SIZE) { throw new InvalidIDLengthError(); } return new Snowflake(new Uint8Array(bytes)); } } export { Snowflake, InvalidEncodingError, InvalidIDLengthError, ID_BYTE_SIZE, ALPHABET, };