snowflakify
Version:
The most complete Snowflake ID generator in TypeScript
114 lines (113 loc) • 3.5 kB
JavaScript
import { hrtime } from 'process';
import FragmentBase from '../FragmentBase.js';
const DEFAULT_EPOCH = 1420070400000;
/**
* TimestampFragment class for timestamp IDs.
* @public
*/
export default class TimestampFragment extends FragmentBase {
/**
* @param bits - The number of bits for the fragment.
* @param epoch - A custom epoch timestamp.
*
* Defaults to `1420070400000` (2015-01-01 00:00:00) if omitted.
*
* @throws `[TIMESTAMP_BITS_INVALID_RANGE]` If bits is less than 38
* @throws `[EPOCH_INVALID_TYPE]` If epoch is not a number
* @throws `[EPOCH_INVALID_RANGE]` If epoch is not within 0 and Date.now()
*/
constructor(bits, epoch = DEFAULT_EPOCH) {
super(bits);
if (bits < 38)
throw new RangeError(
'[TIMESTAMP_BITS_INVALID_RANGE]: TimestampFragment bits must be greater than or equal to 38',
);
if (typeof epoch !== 'number')
throw new TypeError(
'[EPOCH_INVALID_TYPE]: TimestampFragment epoch must be a number.',
);
if (epoch < 0 || epoch > Date.now())
throw new RangeError(
'[EPOCH_INVALID_RANGE]: TimestampFragment epoch must be within 0 and Date.now() at instantiation time.',
);
if (bits >= 58) {
// nanosecond time unit
this.timeUnit = BigInt(1);
this.epoch = BigInt(epoch * 10 ** 6);
} else if (bits >= 48) {
// microsecond time unit
this.timeUnit = BigInt(10 ** 3);
this.epoch = BigInt(epoch * 10 ** 3);
} else {
// millisecond time unit
this.timeUnit = BigInt(10 ** 6);
this.epoch = BigInt(epoch);
}
const firstHrTime = hrtime.bigint();
const unixMilliseconds = Date.now();
const secondHrTime = hrtime.bigint();
this.nanoTimeAnchor =
BigInt(unixMilliseconds) * BigInt(10 ** 6) -
(firstHrTime + secondHrTime) / BigInt(2);
this.lastTimestamp = BigInt(0);
}
/**
* @internal
*/
set sequenceFragmentReference(sequenceFragment) {
this.sequenceFragmentRef = sequenceFragment;
}
getValue() {
this.value = this.unixNow();
// hrtime is not subject to clock drift
// this.checkForClockDrift();
if (this.sequenceFragmentRef) this.checkForSequenceCollision();
this.lastTimestamp = this.value;
return this.value - this.epoch;
}
destructure(snowflake) {
const bits = BigInt(snowflake) & this.bitMask;
return {
identifier: this.identifier,
value: (bits >> this.bitShift) + this.epoch,
};
}
/**
* Returns a Unix timestamp.
*
* @remarks
* The number of the fragment's bits defines the time unit.
* This is done to avoid overflow when left shifting.
*
* @returns A Unix timestamp in the fragment's time unit.
* @internal
*/
unixNow() {
return (this.nanoTimeAnchor + hrtime.bigint()) / this.timeUnit;
}
/**
* Wait for the next timestamp.
* @internal
*/
waitForNextTimestamp() {
// :)
while (this.value === this.lastTimestamp) {
this.value = this.unixNow();
}
}
/**
* Check for sequence collision.
*
* @remarks
* If a sequence completes its cycle, and the timestamp
* is still the same, an already generated snowflake will
* be re-generated.
* @internal
*/
checkForSequenceCollision() {
if (this.value !== this.lastTimestamp)
return this.sequenceFragmentRef.resetSequence();
if (this.sequenceFragmentRef.willReset()) return this.waitForNextTimestamp();
}
}
//# sourceMappingURL=TimestampFragment.js.map