UNPKG

snowflakify

Version:

The most complete Snowflake ID generator in TypeScript

144 lines (143 loc) 5.94 kB
"use strict"; 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