snowflakify
Version:
The most complete Snowflake ID generator in TypeScript
144 lines (143 loc) • 5.94 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const worker_threads_1 = require("worker_threads");
const Options_js_1 = __importDefault(require("./Utils/Options.js"));
const CircularBuffer_js_1 = __importDefault(require("./CircularBuffer/CircularBuffer.js"));
/**
* Snowflakify class for generating snowflake IDs.
* @public
*/
class Snowflakify {
/**
*
* @remarks
* A Snowflakify instance must follow the following conditions:
* - At least one TimestampFragment or RandomFragment.
* - A RandomFragment must be coupled with at least one TimestampFragment or SequenceFragment.
*
* @param options - Optional parameter for specifying Snowflakify options.
*
* Can be an array of fragments or an object with the following properties:
* - `epoch`: A custom epoch timestamp.
* - `preset`: `"worker_process"`, `"ipv4"`, or `"mac"`
*/
constructor(options) {
this.fragments = this.resolveOptions(options);
this.totalBits = this.fragments.reduce((acc, fragment) => acc + fragment.bits, 0);
this.updateBitShiftsAndMasks();
this.coupleTimestampAndSequence();
}
/**
* Returns a snowflake ID.
*
* @returns The sum of all fragments values shifted to the left by their bitShift.
*
* @public
*/
nextId() {
if (worker_threads_1.isMainThread && this.buffer && !this.buffer.isEmpty()) {
return this.buffer.pop();
}
return this.fragments.reduce((acc, fragment) => acc + (fragment.getValue() << fragment.bitShift), BigInt(0));
}
/**
* Returns a destructured snowflake.
*
* @param snowflake - The snowflake to be destructured.
*
* @returns An array of destructured fragments.
*
* @throws `[SNOWFLAKE_INVALID]` If snowflake is not a number, bigint, or string of a positive number.
*
* @public
*/
destructure(snowflake) {
// We do not need bigint precision when checking if a snowflake is valid
if (Number.isNaN(Number(snowflake)) || Number(snowflake) < 0)
throw new Error(`[SNOWFLAKE_INVALID]: snowflake "${snowflake}" is invalid; Expected a number, bigint, or string of a positive number.`);
return this.fragments.map((fragment) => fragment.destructure(snowflake));
}
/**
* Returns a snowflake ID as Hexadecimal.
*
* @returns a snowflake ID converted to Hexadecimal
*
* @public
*/
nextHexId() {
return this.nextId().toString(16);
}
/**
* Returns a destructured snowflake.
*
* @param snowflake - The hexadecimal snowflake to be destructured.
*
* @returns An array of destructured fragments.
*
* @throws `[SNOWFLAKE_INVALID]` If snowflake is not a valid hexademical string.
*
* @public
*/
destructureHex(snowflake) {
if (typeof snowflake !== 'string' || snowflake.match(/^[0-9A-Fa-f]+$/g))
throw new Error(`[SNOWFLAKE_HEX_INVALID]: hex snowflake "${snowflake}" is invalid; Expected a valid hex string.`);
const id = BigInt(`0x${snowflake}`);
return this.destructure(id);
}
/**
* Returns the buffers content.
*
* @returns An array of snowflake IDs or undefined if the buffer is not instatiated.
*
* @public
*/
getBuffer() {
return this.buffer?.buffer ?? undefined;
}
/**
* @internal
*/
updateBitShiftsAndMasks() {
let remainingBits = this.totalBits;
for (const fragment of this.fragments) {
remainingBits -= fragment.bits;
fragment.setBitShiftAndBitMask(remainingBits);
}
}
/**
* @internal
*/
coupleTimestampAndSequence() {
const timestampFragment = this.fragments.find((fragment) => fragment.constructor.name === 'TimestampFragment');
const sequenceFragment = this.fragments.find((fragment) => fragment.constructor.name === 'SequenceFragment');
if (timestampFragment && sequenceFragment)
timestampFragment.sequenceFragmentReference = sequenceFragment;
}
/**
* @internal
*/
resolveOptions(options) {
if (typeof options !== 'undefined' && typeof options !== 'object')
throw new Error('[OPTIONS_INVALID]: Snowflakify options supplied are invalid.');
if (worker_threads_1.isMainThread && options?.useBuffer)
this.buffer = new CircularBuffer_js_1.default(options?.bufferSize, options, options?.bufferRefillThershold, options?.workerCount);
if (options?.fragmentArray && Array.isArray(options?.fragmentArray)) {
const fragmentTypes = options.fragmentArray.map((fragment) => fragment.constructor.name);
if (!['TimestampFragment', 'RandomFragment'].some((type) => fragmentTypes.includes(type)))
throw new Error('[BAD_FRAGMENT_CONDITIONS]: Snowflakify instance must have at least one TimestampFragment or RandomFragment.');
if (fragmentTypes.includes('RandomFragment') &&
!['TimestampFragment', 'SequenceFragment'].some((type) => fragmentTypes.includes(type)))
throw new Error('[BAD_FRAGMENT_CONDITIONS]: A RandomFragment must be coupled with at least one TimestampFragment or SequenceFragment.');
return options.fragmentArray;
}
if (options?.preset &&
!['worker_process', 'ipv4', 'mac'].includes(options.preset))
throw new Error(`[PRESET_INVALID]: Snowflakify preset "${options?.preset}" is invalid; Expected "worker_process", "ipv4", or "mac".`);
return Options_js_1.default.defaultOptions(options);
}
}
exports.default = Snowflakify;
//# sourceMappingURL=Snowflakify.js.map