UNPKG

@eristack/entity-id

Version:

UUID v7 entity identifiers — sortable, parseable, Drizzle column helper

156 lines (152 loc) • 4.69 kB
// src/core/errors.ts var ENTITY_ID_PARSE_CODE = "ENTITY_ID_PARSE"; var EntityIdParseError = class extends Error { code = ENTITY_ID_PARSE_CODE; constructor(message) { super(message); this.name = "EntityIdParseError"; } }; // src/core/types.ts var ENTITY_ID_VERSION = 7; var MAX_ENTITY_ID_UNIX_MS = 281474976710655; // src/core/internal.ts var HYPHENATED = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/; function bytesToCanonicalUuid(bytes) { const hex = [...bytes].map((b) => b.toString(16).padStart(2, "0")).join(""); const canonical = `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`; return canonical; } function canonicalUuidToBytes(id) { const compact = id.replace(/-/g, "").toLowerCase(); if (compact.length !== 32 || !/^[0-9a-f]+$/.test(compact)) { throw new EntityIdParseError("Entity id must be 32 hex digits (optional hyphens)"); } const bytes = new Uint8Array(16); for (let i = 0; i < 16; i++) { bytes[i] = Number.parseInt(compact.slice(i * 2, i * 2 + 2), 16); } return bytes; } function assertEntityIdV7(bytes) { const version = bytes[6] >> 4 & 15; if (version !== ENTITY_ID_VERSION) { throw new EntityIdParseError( `Entity id must be UUID version ${ENTITY_ID_VERSION}, got version ${version}` ); } const variant = bytes[8] >> 6 & 3; if (variant !== 2) { throw new EntityIdParseError("Entity id must use RFC 4122 variant (10xxxxxx)"); } } function unixMsFromV7Bytes(bytes) { const ms = BigInt(bytes[0]) << 40n | BigInt(bytes[1]) << 32n | BigInt(bytes[2]) << 24n | BigInt(bytes[3]) << 16n | BigInt(bytes[4]) << 8n | BigInt(bytes[5]); return Number(ms); } function generateEntityIdBytes(unixMs, random) { if (!Number.isFinite(unixMs) || unixMs < 0 || unixMs > MAX_ENTITY_ID_UNIX_MS) { throw new EntityIdParseError( `Unix timestamp ms out of range for UUID v7 (0..${MAX_ENTITY_ID_UNIX_MS})` ); } if (random.length < 10) { throw new EntityIdParseError("Random bytes must be at least 10 octets"); } const ts = BigInt(Math.trunc(unixMs)); const bytes = new Uint8Array(16); bytes[0] = Number(ts >> 40n & 0xffn); bytes[1] = Number(ts >> 32n & 0xffn); bytes[2] = Number(ts >> 24n & 0xffn); bytes[3] = Number(ts >> 16n & 0xffn); bytes[4] = Number(ts >> 8n & 0xffn); bytes[5] = Number(ts & 0xffn); bytes[6] = random[0] & 15 | 112; bytes[7] = random[1]; bytes[8] = random[2] & 63 | 128; bytes[9] = random[3]; bytes[10] = random[4]; bytes[11] = random[5]; bytes[12] = random[6]; bytes[13] = random[7]; bytes[14] = random[8]; bytes[15] = random[9]; return bytes; } function isHyphenatedUuidLower(value) { return HYPHENATED.test(value); } // src/core/entity-id.ts function randomOctets(length) { const bytes = new Uint8Array(length); crypto.getRandomValues(bytes); return bytes; } function entityIdFactory() { return generateEntityId(); } function generateEntityId(unixMs = Date.now()) { const rnd = randomOctets(10); const bytes = generateEntityIdBytes(unixMs, rnd); return bytesToCanonicalUuid(bytes); } function generateEntityIdAt(unixMs, random) { const bytes = generateEntityIdBytes(unixMs, random); return bytesToCanonicalUuid(bytes); } function normalizeEntityId(value) { const trimmed = value.trim().toLowerCase(); const canonical = trimmed.includes("-") ? trimmed : `${trimmed.slice(0, 8)}-${trimmed.slice(8, 12)}-${trimmed.slice(12, 16)}-${trimmed.slice(16, 20)}-${trimmed.slice(20)}`; if (!isHyphenatedUuidLower(canonical)) { throw new EntityIdParseError("Entity id must be a lowercase UUID string"); } const bytes = canonicalUuidToBytes(canonical); assertEntityIdV7(bytes); return canonical; } function parseEntityId(value) { return normalizeEntityId(value); } function isValidEntityId(value) { try { parseEntityId(value); return true; } catch { return false; } } function entityIdToUnixMs(id) { const bytes = canonicalUuidToBytes(id); assertEntityIdV7(bytes); return unixMsFromV7Bytes(bytes); } function entityIdToDate(id) { return new Date(entityIdToUnixMs(id)); } function compareEntityIds(a, b) { return a.localeCompare(b); } function entityIdEquals(a, b) { try { return parseEntityId(a) === parseEntityId(b); } catch { return false; } } export { ENTITY_ID_PARSE_CODE, EntityIdParseError, ENTITY_ID_VERSION, MAX_ENTITY_ID_UNIX_MS, entityIdFactory, generateEntityId, generateEntityIdAt, normalizeEntityId, parseEntityId, isValidEntityId, entityIdToUnixMs, entityIdToDate, compareEntityIds, entityIdEquals }; //# sourceMappingURL=chunk-7V2KY5JP.js.map