@wxn0brp/db
Version:
A simple file-based database management system with support for CRUD operations, custom queries, and graph structures.
135 lines (134 loc) • 4.92 kB
JavaScript
const usedIdsMap = new Map();
let lastId;
const recentIdsTimestamps = [];
let startIndex = 0;
let lastGeneratedMs = 0;
let lastRandomValue = 0;
/**
* Generates a unique random identifier based on time and parts.
*
* @param {number[]} [parts] - an array of lengths of parts of the identifier
* @returns {Id} - a new unique identifier
*/
export default function genId(parts = null) {
if (parts === null)
parts = [1, 1];
const time = getTime();
const id = getUniqueRandom({
time,
partsSchema: parts,
s: 0,
});
return id;
}
/**
* Generates a unique random identifier based on time and parts.
*/
function getUniqueRandom(opts) {
while (true) {
let minValue = 0;
if (lastId) {
const parts = lastId.split("-");
const lastTime = parts.shift();
if (lastTime === opts.time) {
const int36 = parts.join("");
minValue = parseInt(int36, 36) + 1;
}
}
const partsLengthSum = opts.partsSchema.reduce((acc, num) => acc + num, 0);
const partsData = generateBase36InRange(minValue, partsLengthSum);
const parts = splitStringByArray(opts.partsSchema, partsData);
const id = [opts.time, ...parts].join("-");
if (!usedIdsMap.has(id)) {
usedIdsMap.set(id, true);
setTimeout(() => usedIdsMap.delete(id), 1000);
lastId = id;
return id;
}
opts.s++;
if (opts.s >= partsLengthSum) {
opts.time = getTime();
opts.s = 0;
}
}
}
/**
* Tracks the current load of the application by counting the number of ids that have been generated in the last second.
* @returns The number of ids that have been generated in the last second.
*/
function trackLoad() {
const now = Date.now();
recentIdsTimestamps.push(now);
while (startIndex < recentIdsTimestamps.length && recentIdsTimestamps[startIndex] < now - 1) {
startIndex++;
}
return recentIdsTimestamps.length - startIndex;
}
/**
* Generates a base-36 encoded string with a specified length based on a random value within a range.
*
* The function calculates a random value within the base-36 range determined by the provided
* `minValue` and `length`. It uses a bias mechanism to adjust the randomness based on the
* current load, ensuring unique generation over short periods. If the system load is low or
* the current millisecond has changed, a new random value is generated. Otherwise, the random
* value is incremented by a small amount with certain probabilities.
*
* @param minValue - The minimum value for the base-36 encoded number.
* @param length - The length of the resulting base-36 string.
* @returns A base-36 encoded string representation of the random value, padded to the specified length.
*/
function generateBase36InRange(minValue, length) {
const maxValue = Math.pow(36, length) - 1;
const load = trackLoad();
const currentMs = Date.now();
if (load < 3 || currentMs !== lastGeneratedMs) {
lastRandomValue = biasedRandomInRange(minValue, maxValue);
lastGeneratedMs = currentMs;
}
else {
const rand = Math.random();
if (rand < 0.6) {
lastRandomValue += 1;
}
else if (rand < 0.9) {
lastRandomValue += 2;
}
else {
lastRandomValue = biasedRandomInRange(lastRandomValue, maxValue);
}
}
return lastRandomValue.toString(36).padStart(length, "0");
}
/**
* Generates a random number between the specified range, but with a bias towards the lower
* end of the range. The bias is calculated as the cube root of the random value, which
* will make the random value more likely to be closer to the minimum value.
* @param min - The minimum value of the range.
* @param max - The maximum value of the range.
* @returns A random number between the specified range, with a bias towards the lower end.
*/
function biasedRandomInRange(min, max) {
let randomValue = min;
const bias = Math.pow(Math.random(), 3);
randomValue = Math.floor(min + (max - min) * bias);
return Math.min(randomValue, max);
}
/**
* Splits a string into an array of substrings, where each substring is determined by
* the values in the provided array.
*
* @param arr - An array of numbers, where each number represents the length of a substring.
* @param str - The string to split.
* @returns An array of substrings, where each element is a substring of the original string,
* determined by the corresponding element in the provided array.
*/
function splitStringByArray(arr, str) {
let start = 0;
return arr.map(length => str.slice(start, start += length));
}
/**
* Gets the current time in a base36 string format.
*/
function getTime() {
return new Date().getTime().toString(36);
}