Skip to content

Instantly share code, notes, and snippets.

@annibal
Last active November 22, 2025 21:12
Show Gist options
  • Select an option

  • Save annibal/c4eed25bd3d572976ea3b1411b93e41c to your computer and use it in GitHub Desktop.

Select an option

Save annibal/c4eed25bd3d572976ea3b1411b93e41c to your computer and use it in GitHub Desktop.
i think PRIMARY KEY INTEGER AUTOINCREMENT ids are ugly.

Custom UID generator

const { uid: customUID } = CreateNibolsUID();
const thingID = customUID(4);
const otherID = customUID(8);

// decode uid
log(thingID === otherID); // true
log( customUID(thingID, true), customUID(otherID, true) ); // 4, 8

// increment previous id
const newThingID = customID(customID(thingID, true) + 1);
const newNewThingID = customID(customID(thingID, true) + 2);
log( customUID(thingID, true) + 4 === customUID(otherID, true) ); // true

00 = AAAAAAAA 01 = QXR2KZJ9 02 = BNDUXRV7 03 = SEWNCJ96 04 = C5HHNDJ5 05 = US2BZ5V3 06 = EJK5EV92 07 = WA6WQNJZ 08 = FYPP5GVY 09 = XPBJG99X 10 = HFUDSZJW 11 = Y6E67RVV 12 = IUXYJJ9U 13 = 2KIRWDJS 14 = KB3LA5VR 15 = 3ZMFLV9Q

00 = AAAA 01 = KZJ9 02 = XRV7 03 = CJ96 04 = NDJ5 05 = Z5V3 06 = EV92 07 = QNJZ 08 = 5GVY 09 = G99X 10 = SZJW 11 = 7RVV 12 = JJ9U 13 = WDJS 14 = A5VR 15 = LV9Q

/**
* Usage:
* const { nblUID } = CreateNibolsUID({ uniqueChars: "ABCDEFGHIJKLMNPQRSUVWXYZ235679", size: 8, prime: 999867530999 });
* const uid = nblUID(1);
*/
const CreateNibolsUID = (() => {
const DEFAULT_CONFIG = Object.freeze({
/** Define each unique symbol that the end UID may have: */
alphabet: 'ABCDEFGHIJKLMNPQRSUVWXYZ235679',
// this is 30 chars and omits similar characters like 0 and O.
/** Ok we have baseNum different symbols. */
// baseNum: BigInt(alphabet.length),
/** The UID will always have size many chars: */
size: 8n,
/** modulus is (how many unique symbols are available) to the power of (the desired length of the UID) */
// modulus: BigInt(baseNum ** size),
/** Now after calculating modulus, we must choose a prime number, who is close to modulus, that is COPRIME to modulus. */
// prime: 999867530999n, // To which i chose 999 "8675-389" 999 lol
/** Finally we need the modular inverse of prime with respect to modulus: */
// invPrime: modInverse(cfg.prime, cfg.modulus)
});
function NibolsUIDFactory(config) {
const preConfig = { ...DEFAULT_CONFIG, ...config };
const baseNum = BigInt(preConfig.alphabet.length);
const modulus = BigInt(baseNum ** preConfig.size);
const invPrime = modInverse(preConfig.prime, modulus);
const cfg = { ...preConfig, baseNum, modulus, invPrime };
/**
* Get a constant length unique identifier for a positive integer number.
* @param {Number} n positive integer number
* @returns {String} unique identifier
*/
function nblEncodeUID(n) {
try {
if (n == null) { throw new Error(`NibolsUID.encode: please do not try to encode '${typeof n}'.`); }
if (!Number.isFinite(n)) { throw new Error(`NibolsUID.encode: This only works with numbers, you cannot encode '${typeof n}' stuff like ${n} yet.`); }
if (!Number.isInteger(n)) { throw new Error(`NibolsUID.encode: Unable to encode decimal ${n} because of the ambiguity of not being able to choose between encoding either ${Math.ceil(n)} or ${Math.floor(n)} or any number in between, please provide an integer.`); }
if (n < 0) { throw new Error(`NibolsUID.encode: Failed to encode negative number ${n} because the concept of a Unique Negative Identifier has not yet been implemented.`); }
} catch (e) {
throw new Error(`NibolsUID.encode: Failed to make the message explaining why the input is invalid. Please only try to encode positive integers.`);
}
// console.log('cfg :>> ', cfg);
let x = (BigInt(n) * cfg.prime) % cfg.modulus;
let chars = [];
for (let i = 0; i < cfg.size; i++) {
chars.push(cfg.alphabet[Number(x % cfg.baseNum)]);
x = x / cfg.baseNum;
}
return chars.reverse().join('');
}
/**
* Get the number used to generate this UID.
*
* ⚠️ IMPORTANT: Decode is config-dependant !
* >
* > To ensure it will decode correctly the UIDs,
* > ensure the decoder has the same configuration
* > as the encoder.
* >
* > Make sure to always decode the string UIDs by
* > an NibolsUID instance created with the same
* > configuration: alphabet, size, and prime must
* > be the same.
*[
"AFRDCV7U",
42,
276662824446,
202949699793,
"AAAAABX2",
159700981134,
42,
192137064411,
"3B2LKDR6",
1245225811379,
1079924085291,
42
]
* For instance:
* ```javascript
*
* // Using default (length=9, prime=999867530999)
* const NbDefault = CreateNibolsUID();
* const uidDefo = NbDefault.nblUID(4815162342); // "AFRDCV7U"
*
* // Different prime (37 instead of 999867530999):
* const NbPrime37 = CreateNibolsUID({ prime: 37 });
* const uidPrime37 = NbPrime37.nblUID(4815162342); // "AAAAABX2"
*
* // Different length (4 instead of 9):
* const NbLen4 = CreateNibolsUID({ alphabet: "SK8TE4LIFZABCDGHJMNPQRUVWXY235679" });
* const uidLen4 = NbLen4.nblUID(4815162342); // "3B2LKDR6"
*
* // Now decoding back: ╭─────────────────────╥─────────────────────╥─────────────────────╮
* // ┯ │ uidDefo ║ uidPrime37 ║ uidLen4 │
* ╭─────────────────────╆━━━━━━━━━━━━━━━━━━━━━╫━━━━━━━━━━━━━━━━━━━━━╫━━━━━━━━━━━━━━━━━━━━━┥
* │ NibolsUID (String) ┃ "AFRDCV7U" ║ "AAAAABX2" ║ "3B2LKDR6 │
* ├─────────────────────╂───────────────┬─────╫───────────────┬─────╫───────────────┬─────┤
* │ NbDefault ┃ 42 │ Yes ║ 276662824446 │ ║ 202949699793 │ │
* ├─────────────────────╂───────────────┼─────╫───────────────┼─────╫───────────────┼─────┤
* │ NbPrime37 ┃ 159700981134 │ ║ 42 │ Yes ║ 192137064411 │ │
* ├─────────────────────╂───────────────┼─────╫───────────────┼─────╫───────────────┼─────┤
* │ NbLen4 ┃ 1245225811379 │ ║ 1079924085291 │ ║ 42 │ Yes │
* ╰─────────────────────┸───────────────┴──╥──╨───────────────┴──╥──╨───────────────┴──╥──╯
* ╚══▷ Column of ◁══╝ ◁══════╝
* "Is Correct?"
*
* ```
*
* @param {String} str Previously generated UID
* @returns {Number} the original number id
*/
function nblDecodeUID(str) {
// console.log({ DEFAULT_CONFIG, cfg, str})
let x = 0n;
for (const ch of str) {
x = x * cfg.baseNum + BigInt(cfg.alphabet.indexOf(ch));
}
return Number((x * cfg.invPrime) % cfg.modulus);
}
/**
* NibolsUID has two behaviors: Encode (default) or Decode.
*
* (encode, default):
* From an positive integer identifier number,
* get an fixed length unique string.
*
* (decode=true):
* From a previously generated unique string,
* get back the original positive integer identifier number.
*
* const id = NibolsUID("MYNV6QGB", true); // 7
*
* @param {Number|String} n The ID to get the UID, or the UID to get the ID from (decode = true)
* @param {Boolean} decode default FALSE, set to true to inverse the function and decode instead of encode.
* @returns {String|Number}
*/
function nblUID(n, decode = false) {
return decode ? nblDecodeUID(n) : nblEncodeUID(n);
}
return {
// function with original names
nblUID,
nblEncodeUID,
nblDecodeUID,
// aliases
uid: nblUID,
to: nblEncodeUID,
from: nblDecodeUID,
encode: nblEncodeUID,
decode: nblDecodeUID,
};
}
// Extended Euclidean Algorithm
function modInverse(a, m) {
// console.log(`a, m: ${a}, ${m}`);
let m0 = m, x0 = 0n, x1 = 1n;
let b = a % m;
while (b > 1n) {
const q = b / m;
[b, m] = [m, b % m];
[x0, x1] = [x1 - q * x0, x0];
}
const r = x1 < 0n ? x1 + m0 : x1;
return BigInt(r);
}
function CreateNibolsUID(config = {}) {
let options = config;
const cfg = {};
try {
const test = String(options.alphabet) + String(options.size) + String(options.prime);
} catch (e) {
options = {};
}
if (options.alphabet) {
let valid = typeof options.alphabet === 'string' && options.alphabet.length > 10
const uniqueChars = new Set(options.alphabet.split('')).size;
const inputLen = options.alphabet.length;
if (uniqueChars < inputLen) { valid = false; }
if (valid) {
cfg.alphabet = options.alphabet;
} else {
throw new Error(`NibolsUID: The provided set of symbols (alphabet options) used to make the UID must be at least larger that the numbers available in base 10 maths.`);
}
}
if (options.size) {
if (Number.isInteger(options.size) && options.size > 3) {
if (options.size > 13) {
throw new Error(`NibolsUID: The desired size of ${options.size} is beyond the maximum supported size of 13.`)
}
cfg.size = BigInt(options.size);
} else {
throw new Error(`NibolsUID: The desired length (size option) provided is either something not numerical, or less than 3.`);
}
} else {
cfg.size = 8n;
}
if (options.prime) {
if (Number.isInteger(options.prime) && options.prime > 1) {
cfg.prime = BigInt(options.prime);
} else {
throw new Error(`NibolsUID: Invalid prime option, ensure it is at least a positive integer number.`);
}
} else {
// console.log(cfg.size, goodPrimes30[cfg.size])
// cfg.prime = BigInt(goodPrimes30[cfg.size]);
}
return NibolsUIDFactory(cfg);
}
return CreateNibolsUID;
})()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment