universal-common
Version:
Library that provides useful missing base class library functionality.
175 lines (151 loc) • 5.51 kB
JavaScript
import ArgumentError from "./ArgumentError.js";
/**
* A utility class for generating and manipulating Globally Unique Identifiers (GUIDs).
* This implementation creates RFC4122 version 4 compliant UUIDs.
* The class stores GUIDs as byte arrays internally for efficient operations.
*/
export default class Guid {
/**
* The internal byte array representation of the GUID.
* @type {Uint8Array}
* @private
*/
#bytes;
/**
* Creates a new Guid instance.
* @param {Uint8Array|string|null} value - Optional byte array or string to initialize the GUID.
*/
constructor(value = null) {
if (value) {
if (value instanceof Uint8Array) {
if (value.length !== 16) {
throw new Error("GUID must be initialized with a 16-byte Uint8Array");
}
this.#bytes = new Uint8Array(value);
}
else if (typeof value === "string") {
const hexString = value.replace(/[^0-9a-f]/gi, "");
if (hexString.length !== 32) {
throw new ArgumentError("Invalid GUID format.");
}
this.#bytes = new Uint8Array(16);
for (let i = 0; i < 16; i++) {
this.#bytes[i] = parseInt(hexString.substring(i * 2, i * 2 + 2), 16);
}
}
} else {
this.#bytes = new Uint8Array(16);
}
}
/**
* Creates a Guid from a byte array.
* @param {Uint8Array} bytes - A 16-byte array.
* @returns {Guid} A new Guid instance.
*/
static fromBytes(bytes) {
return new Guid(bytes);
}
/**
* Creates a new random GUID instance.
* @returns {Guid} A new Guid instance with random values.
*/
static newGuid() {
const bytes = new Uint8Array(16);
// Fill with random values using Math.random() for consistent behavior
// Wait until crypto is standard in both Node and browsers without a environment check.
for (let i = 0; i < 16; i++) {
bytes[i] = Math.floor(Math.random() * 256);
}
// Set version bits (Version 4 - random UUID)
bytes[6] = (bytes[6] & 0x0f) | 0x40;
// Set variant bits (Variant 1 - RFC4122)
bytes[8] = (bytes[8] & 0x3f) | 0x80;
return new Guid(bytes);
}
/**
* Creates a Guid from a string representation.
* @param {string} guidString - The string representation of a GUID.
* @returns {Guid} A new Guid instance.
* @throws {ArgumentError} If the string is not a valid GUID format.
*/
static parse(guidString) {
const hexString = guidString.replace(/[^0-9a-f]/gi, "");
if (hexString.length !== 32) {
throw new ArgumentError("Invalid GUID format.");
}
const bytes = new Uint8Array(16);
for (let i = 0; i < 16; i++) {
bytes[i] = parseInt(hexString.substring(i * 2, i * 2 + 2), 16);
}
return new Guid(bytes);
}
/**
* Tries to parse a GUID string without throwing an exception.
* @param {string} guidString - The string to parse.
* @param {Object} result - An object with a "value" property that will be set to the parsed GUID if successful.
* @returns {Guid?} True if parsing was successful, false otherwise.
*/
static tryParse(guidString) {
try {
return Guid.parse(guidString);
} catch {
return null;
}
}
/**
* Checks if this GUID is equal to another GUID.
* @param {Guid} other - The GUID to compare with.
* @returns {boolean} True if the GUIDs are equal, false otherwise.
*/
equals(other) {
if (!(other instanceof Guid)) {
return false;
}
const a = this.#bytes;
const b = other.#bytes;
for (let i = 0; i < 16; i++) {
if (a[i] !== b[i]) {
return false;
}
}
return true;
}
/**
* Checks if this GUID is the empty GUID.
* @returns {boolean} True if this is the empty GUID, false otherwise.
*/
isEmpty() {
for (let i = 0; i < 16; i++) {
if (this.#bytes[i] !== 0) {
return false;
}
}
return true;
}
/**
* Returns the byte array representation of the GUID.
* @returns {Uint8Array} The byte array.
*/
toBytes() {
return new Uint8Array(this.#bytes);
}
/**
* Returns the GUID as a hyphenated string.
* @returns {string} The string representation.
*/
toString() {
const byteToHex = byte => byte.toString(16).padStart(2, "0");
const formatSection = (start, length) => {
let result = "";
for (let i = 0; i < length; i++) {
result += byteToHex(this.#bytes[start + i]);
}
return result;
};
return `${formatSection(0, 4)}-${
formatSection(4, 2)}-${
formatSection(6, 2)}-${
formatSection(8, 2)}-${
formatSection(10, 6)}`;
}
}