UNPKG

alinea

Version:
142 lines (140 loc) 3.83 kB
import "../chunks/chunk-NZLE2WMY.js"; // src/core/Id.ts import { crypto } from "@alinea/iso"; var BASE62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; function base62(view) { if (view.byteLength !== 20) { throw new Error("incorrect buffer size"); } const str = new Array(27).fill("0"); let n = 27; let bp = new Array(5); bp[0] = view.getUint32(0, false); bp[1] = view.getUint32(4, false); bp[2] = view.getUint32(8, false); bp[3] = view.getUint32(12, false); bp[4] = view.getUint32(16, false); const srcBase = 4294967296n; const dstBase = 62n; while (bp.length !== 0) { const quotient = []; let remainder = 0; for (const c of bp) { const value = BigInt(c) + BigInt(remainder) * srcBase; const digit = value / dstBase; remainder = Number(value % dstBase); if (quotient.length !== 0 || digit !== 0n) { quotient.push(Number(digit)); } } n--; str[n] = BASE62.charAt(remainder); bp = quotient; } return str.join(""); } function debase62(str) { if (str.length !== 27) throw new Error("Expected 27 characters long base62 string"); const srcBase = 62n; const dstBase = 4294967296n; let bp = new Array(27); const dst = new Uint8Array(20); for (let i = 0; i < str.length; i++) { bp[i] = str.charCodeAt(i); if (bp[i] >= 48 && bp[i] <= 57) { bp[i] -= 48; continue; } if (bp[i] >= 65 && bp[i] <= 90) { bp[i] = 10 + (bp[i] - 65); continue; } if (bp[i] >= 97 && bp[i] <= 122) { bp[i] = 36 + (bp[i] - 97); continue; } throw new Error(`Unexpected symbol "${str.charAt(i)}"`); } let n = 20; while (bp.length !== 0) { const quotient = []; let remainder = 0n; for (const c of bp) { const value = BigInt(c) + BigInt(remainder) * srcBase; const digit = value / dstBase; remainder = value % dstBase; if (quotient.length !== 0 || digit !== 0n) { quotient.push(Number(digit)); } } if (n < 4) { throw new Error("short buffer"); } dst[n - 4] = Number(remainder) >> 24; dst[n - 3] = Number(remainder) >> 16; dst[n - 2] = Number(remainder) >> 8; dst[n - 1] = Number(remainder); n -= 4; bp = quotient; } return dst; } function toEpoch(timestamp, desc) { if (!desc) { return Math.round(timestamp / 1e3) - 14e8; } return 4294967295 - (Math.round(timestamp / 1e3) - 14e8); } function fromEpoch(timestamp, desc) { if (!desc) { return (14e8 + timestamp) * 1e3; } return (4294967295 - timestamp + 14e8) * 1e3; } function randomBytes() { return crypto.getRandomValues(new Uint8Array(16)); } function generate(desc = false, timestamp = Date.now()) { const buf = new ArrayBuffer(20); const view = new DataView(buf); const ts = toEpoch(timestamp, desc); let offset = 0; view.setUint32(offset, ts, false); offset += 4; const rnd = randomBytes(); for (const b of rnd) { view.setUint8(offset++, b); } if (desc) return `z${base62(view)}`; return base62(view); } function parse(ksuid) { if (ksuid.length > 28 || ksuid.length < 27) { throw new Error(`Incorrect length: ${ksuid.length}, expected 27 or 28`); } const desc = ksuid.length === 28 && ksuid[0] === "z"; if (ksuid.length === 28 && ksuid[0] !== "z") { throw new Error(`KSUID is 28 symbol, but first char is not "z"`); } const buf = debase62(desc ? ksuid.slice(1, 28) : ksuid); const view = new DataView(buf.buffer); const tsValue = view.getUint32(0, false); const ts = new Date(fromEpoch(tsValue, desc)); return { ts, rnd: buf.buffer.slice(4) }; } function validateId(id) { try { parse(id); return true; } catch { return false; } } function createId() { return generate(); } export { createId, validateId };