unique-custom-id
Version:
⚡️ Generate custom IDs like never before — fast, secure, and fully yours! 🔥
183 lines (165 loc) • 6.15 kB
JavaScript
const { resolveFormat, secureRandChar, timeStamp } = require('./utils.js');
/**
* Generates a customizable and optionally verbose Unique Custom ID (UCID).
*
* @function
* @param {Object} [options={}] - Configuration options for generating the ID(s).
* @param {number} [options.octets=4] - Number of octets in the ID (must be > 0).
* @param {number} [options.octetLength=8] - Default length of each octet.
* @param {string|Array<number>} [options.octetFormat=''] - Custom format for octet lengths, e.g., [4,6,8] or "4-6-8".
* @param {boolean} [options.uppercase=false] - Whether to include uppercase A–Z characters.
* @param {boolean} [options.lowercase=true] - Whether to include lowercase a–z characters.
* @param {boolean} [options.numbers=true] - Whether to include digits 0–9.
* @param {boolean} [options.symbols=false] - Whether to include symbols (e.g., !@#$%).
* @param {string} [options.octetSeparator='-'] - Separator string between octets.
* @param {string|null} [options.includeOnly=null] - If defined, overrides all character sets with a custom one.
* @param {string|null} [options.timestamp=null] - If set to 'prefix' or 'suffix', includes a timestamp in the ID.
* @param {string|null} [options.timestampFormat=null] - Custom format for the timestamp (e.g., 'yyyy-mm-dd').
* @param {string|null} [options.template=null] - If set, will use template instead of octet structure. Use `%id` and `%ts`.
* @param {string} [options.prefix=''] - Optional string to prepend before the generated ID.
* @param {string} [options.suffix=''] - Optional string to append after the generated ID.
* @param {boolean} [options.verbose=false] - If true, returns metadata including the ID and resolved options.
* @param {number} [options.instances=1] - Number of IDs to generate (≥1).
* @param {(resolve: Function, reject: Function) => void} [options.condition=null] - Optional function that must call resolve() to allow ID generation, or reject(message) to block it.
* @param {(octet: string, index: number) => string} [options.customize=null] - Optional function to modify each octet before joining.
* @returns {string|Object|Array<string|Object>} - Returns the generated ID string(s), or verbose object(s) if `verbose` is true.
*
* @example
* ucidGenerateId(); // -> "ac1d2f3e-d4e5f6g7-h8i9j0k1-l2m3n4o5"
*/
function ucidGenerateId(options = {}) {
const defaults = Object.freeze({
octets: 4,
uppercase: false,
lowercase: true,
octetLength: 8,
octetFormat: '',
instances: 1,
numbers: true,
octetSeparator: '-',
symbols: false,
includeOnly: null,
timestamp: null,
timestampFormat: null,
template: null,
prefix: '',
suffix: '',
verbose: false,
customize: null,
condition: null,
});
let {
octets,
uppercase,
lowercase,
octetLength,
octetFormat,
instances,
numbers,
octetSeparator,
symbols,
includeOnly,
timestamp,
timestampFormat,
template,
prefix,
suffix,
verbose,
customize,
condition,
} = { ...defaults, ...options };
if (typeof condition === 'function') {
let allowed = false;
let error = null;
condition(
() => (allowed = true),
(msg) => {
allowed = false;
error = msg || 'UCID condition rejected.';
}
);
if (!allowed) {
if (error instanceof Error) {
throw error;
}
console.error(error);
return;
}
}
if (octets <= 0) throw new Error('Octets must be greater than 0');
if (octetLength <= 0) throw new Error('OctetLength must be greater than 0');
const charset =
includeOnly ||
[
uppercase && 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
lowercase && 'abcdefghijklmnopqrstuvwxyz',
numbers && '0123456789',
symbols && '!$%&',
]
.filter(Boolean)
.join('');
if (!charset) {
throw new Error(
'Character set is empty. Make sure at least one of `uppercase`, `lowercase`, `numbers`, `symbols`, or `includeOnly` is enabled.'
);
}
/**
* Generates a single ID based on octets, timestamp, and customizations.
* @private
* @returns {string}
*/
const generateId = () => {
const ids = Array.from({ length: octets }, (_, i) => {
const len = resolveFormat(octetFormat, i, octetLength, octetSeparator);
const octet = Array.from({ length: len }, () =>
secureRandChar(charset)
).join('');
return typeof customize === 'function' ? customize(octet, i) : octet;
});
return `${prefix}${
['prefix', 'p', 'pre', 'pref'].includes(timestamp)
? timeStamp(timestampFormat) + octetSeparator
: ''
}${ids.join(octetSeparator)}${
['suffix', 's', 'suf', 'suff'].includes(timestamp)
? octetSeparator + timeStamp(timestampFormat)
: ''
}${suffix}`;
};
// Use template mode (overrides normal generation)
if (typeof template === 'string') {
/**
* Generates an ID using the template pattern (overrides normal generation)
* @private
* @returns {string}
*/
const generateTemplated = () =>
template
.replace(/%id/g, () => generateId())
.replace(/%ts/g, () => timeStamp(timestampFormat));
return instances > 1
? Array.from({ length: instances }, () => generateTemplated())
: generateTemplated();
}
// Verbose return with metadata
if (verbose) {
/**
* Generates a verbose result object with ID and options.
* @private
* @returns {Object}
*/
const generateVerbose = () => ({
ucid: generateId(),
...defaults,
...options,
});
return instances > 1
? Array.from({ length: instances }, () => generateVerbose())
: generateVerbose();
}
// Default mode: return plain ID(s)
return instances > 1
? Array.from({ length: instances }, () => generateId())
: generateId();
}
module.exports = ucidGenerateId;