snowyflake
Version:
A modern implementation Snowflake on TypeScript
128 lines (123 loc) • 3.61 kB
JavaScript
;
const Epoch = {
Unix: 0n,
Twitter: 1288834974657n,
Discord: 1420070400000n,
};
const DEFAULT_VALUE = 0n;
const DEFAULT_SEQUENCE = DEFAULT_VALUE;
const USIGNED_INCREASE = 1n;
const SIGNED_INCREASE = -1n;
const WORKER_ID_BITS = 5n;
const PROCESS_ID_BITS = 5n;
const SEQUENCE_BITS = 12n;
const WORKER_ID_DECONSTRUCT_MASK = 0x3e0000n;
const PROCESS_ID_DECONSTRUCT_MASK = 0x1f000n;
const WORKER_ID_MASK = SIGNED_INCREASE ^ (SIGNED_INCREASE << WORKER_ID_BITS);
const PROCESS_ID_MASK = SIGNED_INCREASE ^ (SIGNED_INCREASE << PROCESS_ID_BITS);
const SEQUNCE_MASK = SIGNED_INCREASE ^ (SIGNED_INCREASE << SEQUENCE_BITS);
const TIMESTAMP_LEFT_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS + PROCESS_ID_BITS;
const WORKER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS;
const PROCESS_ID_SHIFT = SEQUENCE_BITS;
const getNowBigInt = () => BigInt(Date.now());
class Snowyflake {
/**
* Snowflake start epoch
*/
epoch;
/**
* Internal worker ID
*/
workerId;
/**
* Internal process ID
*/
processId;
/**
* Sequence increment for process
*/
sequence = DEFAULT_SEQUENCE;
/**
* Latest timestamp
*/
latestTimestamp = getNowBigInt();
/**
* Constructor
*/
constructor({ epoch = Epoch.Unix, workerId = DEFAULT_VALUE, processId = DEFAULT_VALUE, } = {}) {
this.epoch = epoch;
this.workerId = workerId & WORKER_ID_MASK;
this.processId = processId & PROCESS_ID_MASK;
}
/**
* Generate a Snowflake
*/
nextId() {
const timestamp = getNowBigInt();
if (this.latestTimestamp === timestamp) {
this.sequence = (this.sequence + USIGNED_INCREASE) & SEQUNCE_MASK;
}
else {
this.sequence = DEFAULT_SEQUENCE;
this.latestTimestamp = timestamp;
}
return this.generateCustomId({
timestamp,
sequence: this.sequence,
});
}
/**
* Deconstruct the Snowflake with local epoch
*/
deconstruct(snowflake) {
return Snowyflake.deconstruct(snowflake, {
epoch: this.epoch,
});
}
/**
* Generate a custom Snowflake
*/
generateCustomId({ timestamp, sequence }) {
return (((timestamp - this.epoch) << TIMESTAMP_LEFT_SHIFT) |
(this.workerId << WORKER_ID_SHIFT) |
(this.processId << PROCESS_ID_SHIFT) |
sequence);
}
/**
* Deconstruct the Snowflake timestamp
*/
static deconstructTimestamp(snowflake, epoch) {
return (snowflake >> TIMESTAMP_LEFT_SHIFT) + epoch;
}
/**
* Deconstruct the Snowflake workerId
*/
static deconstructWorkerId(snowflake) {
return (snowflake & WORKER_ID_DECONSTRUCT_MASK) >> WORKER_ID_SHIFT;
}
/**
* Deconstruct the Snowflake processId
*/
static deconstructProcessId(snowflake) {
return (snowflake & PROCESS_ID_DECONSTRUCT_MASK) >> PROCESS_ID_SHIFT;
}
/**
* Deconstruct the Snowflake sequence
*/
static deconstructSequence(snowflake) {
return snowflake & SEQUNCE_MASK;
}
/**
* Deconstruct the Snowflake
*/
static deconstruct(snowflake, { epoch }) {
return {
timestamp: Snowyflake.deconstructTimestamp(snowflake, epoch),
workerId: Snowyflake.deconstructWorkerId(snowflake),
processId: Snowyflake.deconstructProcessId(snowflake),
sequence: Snowyflake.deconstructSequence(snowflake),
};
}
}
exports.Epoch = Epoch;
exports.Snowyflake = Snowyflake;