snowflakify
Version:
The most complete Snowflake ID generator in TypeScript
178 lines (176 loc) • 5.55 kB
JavaScript
import { isMainThread } from 'worker_threads';
import Options from './Utils/Options.js';
import CircularBuffer from './CircularBuffer/CircularBuffer.js';
/**
* Snowflakify class for generating snowflake IDs.
* @public
*/
export default 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 (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 (isMainThread && options?.useBuffer)
this.buffer = new CircularBuffer(
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.defaultOptions(options);
}
}
//# sourceMappingURL=Snowflakify.js.map