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
227 lines (226 loc) • 9.52 kB
JavaScript
"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 = 16, securityKey = "my key" }) {
this.HEX_LENGTH = 16;
this.EXTRALENGTH = 16;
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.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.stringToArray16("hello world");
console.log(this.shiftAmount);
this.startValue = startValue;
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;
}
let shiftMin = 0;
if (code != 0)
shiftMin = shiftAmount[code - 1];
let shiftMax = shiftAmount[code];
return ALNUM[this.randomInRange(shiftMin, shiftMax)];
}).join("");
}
stringToArray16(input) {
const result = [];
let hash = 0;
// حساب قيمة hash من النص
for (let i = 0; i < input.length; i++) {
hash = (hash * 31 + input.charCodeAt(i)) >>> 0; // >>> 0 لتحويله لـ unsigned int
}
// توليد 16 رقم بناءً على الـ hash
for (let i = 0; i < 16; i++) {
hash = (hash * 1103515245 + 12345) >>> 0; // Linear Congruential Generator
result.push(hash % 63); // لأنك تريد القيم ≤ 62
}
// ترتيب تصاعدي
result.sort((a, b) => a - b);
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;
}
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;