UNPKG

btuid

Version:

Btuid library provides an interface to generate UIDs (Unique Identifiers) that are safe, cryptographically secure, and unpredictable, ensuring that every UUID is unique. The library also supports B+tree indexing, which makes it ideal for use in database s

239 lines (238 loc) 10.1 kB
"use strict"; // Copyright 2025 Omar Alhaj Ali // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.BtuidGenerator = void 0; const crypto_1 = require("crypto"); const fs_1 = __importDefault(require("fs")); class BtuidGenerator { constructor({ degreeConfig = { pageSize: 8192, keySize: 16, TIDSize: 6, indexTupleDataSize: 4, linePointerSize: 4, addingPaddingSize: 2, degree: 0, }, startValue = 0n, displacementRate = 6, restConfigData = null, DegreeComplications = 1, path = "", saveTime = 86400, hexLength = 16, randomLength = 8, securityKey = "my key", nodeNumber = 0 }) { this.HEX_LENGTH = 16; this.EXTRALENGTH = 8; this.degree = 0; this.usedIdCount = 0n; this.depth = 1; // for split first array this.length = 16n ** BigInt(this.HEX_LENGTH); this.indexInCurrentDepth = 0n; this.chunkCount = 0n; this.shiftAmount = []; this.nodeNumber = 0; this.restConfigData = { depth: 0, degree: 0, chunkLength: 0n, indexInCurrentDepth: 0n, startValue: 0n, usedIdCount: 0n, }; this.data = { depth: 0, degree: 0, chunkLength: BigInt(0), indexInCurrentDepth: BigInt(0), startValue: BigInt(0), usedIdCount: BigInt(0), }; this.HEX_LENGTH = hexLength; this.length = 16n ** BigInt(this.HEX_LENGTH); this.EXTRALENGTH = randomLength; this.saveTime = saveTime; this.shiftAmount = this.stringToArray16Random(securityKey); console.log(this.shiftAmount); this.startValue = startValue; this.nodeNumber = nodeNumber; this.length = this.length - (this.length * BigInt(displacementRate)) / 1000n; this.startValue = startValue + (this.length * BigInt(displacementRate)) / 1000n; this.degreeConfig = degreeConfig; this.overhead = this.sumOverhead(); this.entryOverhead = this.degreeConfig.pageSize / this.overhead; if (degreeConfig.degree == 0) this.degree = Math.floor((this.degreeConfig.pageSize - this.overhead) / (this.degreeConfig.keySize + this.degreeConfig.TIDSize + this.entryOverhead)); else this.degree = degreeConfig.degree; this.degree = DegreeComplications * this.degree; this.chunkCount = BigInt(this.degree * 2) ** BigInt(this.depth); this.chunkLength = this.length / this.chunkCount; this.data.degree = this.degree; this.restConfigData.chunkLength = this.chunkLength; this.restConfigData.degree = this.degree; this.restConfigData.startValue = this.startValue; this.restConfigData.degree = this.degree; if (!fs_1.default.existsSync(path)) { this.syncSaveObject(`${path}`, this.restConfigData); } if (restConfigData) { this.depth = restConfigData.depth; this.degree = restConfigData.degree; this.chunkLength = BigInt(restConfigData.chunkLength); this.indexInCurrentDepth = BigInt(restConfigData.indexInCurrentDepth); this.startValue = BigInt(restConfigData.startValue); this.usedIdCount = BigInt(restConfigData.usedIdCount); } else { this.restConfigData = this.readObject(path); if (this.restConfigData) { this.depth = this.restConfigData.depth; this.degree = this.restConfigData.degree; this.chunkLength = BigInt(this.restConfigData.chunkLength); this.indexInCurrentDepth = BigInt(this.restConfigData.indexInCurrentDepth); this.startValue = BigInt(this.restConfigData.startValue); this.usedIdCount = BigInt(this.restConfigData.usedIdCount); } } setInterval(() => { this.saveObject(`${path}`, this.restConfigData); }, this.saveTime); } sumOverhead() { const d = this.degreeConfig; return (d.keySize + d.TIDSize + d.indexTupleDataSize + d.linePointerSize + d.addingPaddingSize); } getExtraBtuid() { let extra = ''; extra += (0, crypto_1.randomBytes)(Math.ceil(this.EXTRALENGTH / 2)) .toString("hex") .slice(0, this.EXTRALENGTH); let first = this.bigIntToHex(this.getId()); return this.shiftHexMonotone(first, this.shiftAmount) + this.shiftHexMonotone(extra, this.shiftAmount); } shiftHexMonotone(text, shiftAmount) { const ALNUM = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; return Array.from(text).map(ch => { let code = ch.charCodeAt(0); if (code >= 97) { code = code - 87; } else if (code >= 48) { code = code - 48; } return ALNUM[shiftAmount[code][this.randomInRange(0, shiftAmount[code].length)]]; }).join(""); } stringToArray16Random(input) { const totalUniqueNumbers = 62; const buckets = 16; const allNumbers = Array.from({ length: totalUniqueNumbers }, (_, i) => i); let hash = 0; for (let i = 0; i < input.length; i++) { hash = (hash * 31 + input.charCodeAt(i)) >>> 0; } const next = () => (hash = (hash * 1103515245 + 12345) >>> 0); for (let i = allNumbers.length - 1; i > 0; i--) { const j = next() % (i + 1); [allNumbers[i], allNumbers[j]] = [allNumbers[j], allNumbers[i]]; } const base = Math.floor(totalUniqueNumbers / buckets); // 3 const extra = totalUniqueNumbers % buckets; // 14 const idx = Array.from({ length: buckets }, (_, i) => i); for (let i = idx.length - 1; i > 0; i--) { const j = next() % (i + 1); [idx[i], idx[j]] = [idx[j], idx[i]]; } const bigger = new Set(idx.slice(0, extra)); const sizes = Array.from({ length: buckets }, (_, i) => bigger.has(i) ? base + 1 : base); const result = []; let cursor = 0; for (const size of sizes) { result.push(allNumbers.slice(cursor, cursor + size)); cursor += size; } return result; } randomInRange(min, max) { return Math.floor(Math.random() * (max - min)) + min; } getBtuid() { return this.bigIntToHex(this.getId()); } getId() { let start = 0n; start = this.getNextInDepthAndIndex(this.depth, this.degree, this.chunkLength, this.indexInCurrentDepth + 1n, this.startValue, this.usedIdCount); this.indexInCurrentDepth++; this.restConfigData.indexInCurrentDepth = this.indexInCurrentDepth; if (this.indexInCurrentDepth >= this.chunkCount) { this.indexInCurrentDepth = 0n; this.restConfigData.indexInCurrentDepth = this.indexInCurrentDepth; this.usedIdCount++; this.restConfigData.usedIdCount = this.usedIdCount; if (this.usedIdCount >= 1) { this.usedIdCount = 0n; this.indexInCurrentDepth++; this.restConfigData.indexInCurrentDepth = this.indexInCurrentDepth; this.restConfigData.usedIdCount = this.usedIdCount; this.depth++; this.restConfigData.depth = this.depth; this.chunkCount = BigInt(this.degree * 2 - 1) ** BigInt(this.depth); let pwrPart = BigInt(this.degree * 2) ** BigInt(this.depth); this.chunkLength = this.length / pwrPart; this.restConfigData.chunkLength = this.chunkLength; } } return start + BigInt(this.nodeNumber); } padHex(hex) { let hexText = hex.padStart(this.HEX_LENGTH, "0"); return hexText; } bigIntToHex(value) { return this.padHex(value.toString(16)); } getNextInDepthAndIndex(depth, degree, chunkLength, index, startValue, usedIdCount) { const usedItemInlastLevel = BigInt((2 * degree - 1) * (depth - 1)); const multiPart = chunkLength * index; const start = startValue + usedItemInlastLevel + multiPart; return start; } syncSaveObject(path, obj) { const jsonString = JSON.stringify(obj, (key, value) => (typeof value === "bigint" ? value.toString() : value), 2); fs_1.default.writeFileSync(`${path}`, jsonString, "utf8"); } saveObject(path, obj) { const jsonString = JSON.stringify(obj, (key, value) => (typeof value === "bigint" ? value.toString() : value), 2); fs_1.default.writeFile(`${path}`, jsonString, "utf8", () => { }); } readObject(path) { if (!fs_1.default.existsSync(`${path}`)) { return null; } const fileData = fs_1.default.readFileSync(`${path}`, "utf8"); const parsedObject = JSON.parse(fileData, (key, value) => { if (typeof value === "string" && /^\d+n$/.test(value)) { return BigInt(value.slice(0, -1)); } return value; }); return parsedObject; } } exports.BtuidGenerator = BtuidGenerator;