alinea
Version:
Headless git-based CMS
142 lines (140 loc) • 3.83 kB
JavaScript
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
};