57 lines
1.5 KiB
TypeScript
57 lines
1.5 KiB
TypeScript
|
|
import { randomInt } from 'crypto';
|
|
|
|
export type Snowflake = `${bigint}`;
|
|
|
|
export interface SnowflakeConfig {
|
|
/** Epoch time, e.g. `1577836800000` for 1 Jan 2020 00:00:00 */
|
|
epoch: number;
|
|
|
|
/** 6-bit instance ID (0-63) */
|
|
instance: number;
|
|
}
|
|
|
|
export function validate_snowflake_conf(conf: unknown) : asserts conf is SnowflakeConfig {
|
|
// todo: validate config
|
|
}
|
|
|
|
/**
|
|
* Generates a unique 64-bit integer ID.
|
|
*
|
|
* Format based on Snowflake IDs (https://en.wikipedia.org/wiki/Snowflake_ID),
|
|
* with the following modifications / details:
|
|
*
|
|
* - Uses a configurable epoch time
|
|
* - Uses a 45-bit timestamp rather than 41-bit, shortening the "instance" (since this project
|
|
* is never expected to operate at large scale) to 6-bits (which for now is always 0).
|
|
*/
|
|
export function create_snowflake_provider(conf: SnowflakeConfig) {
|
|
const instance = BigInt(conf.instance) << 12n;
|
|
|
|
return {
|
|
uid() {
|
|
const sequence = next_sequence();
|
|
const timestamp = BigInt(Date.now() - conf.epoch) & timestamp_mask;
|
|
return BigInt.asUintN(64, (timestamp << 18n) | instance | sequence);
|
|
},
|
|
uid_str() {
|
|
return this.uid().toString(10) as Snowflake;
|
|
}
|
|
};
|
|
}
|
|
|
|
export function is_snowflake(value: string) : value is Snowflake {
|
|
return /^\d+$/.test(value) && value.length <= 20;
|
|
}
|
|
|
|
const iterator_mask = 0xfffn;
|
|
const timestamp_mask = 0x1fffffffffffn;
|
|
|
|
let iterator = BigInt(randomInt(0xfff));
|
|
|
|
function next_sequence() {
|
|
const value = iterator++;
|
|
iterator &= iterator_mask;
|
|
return value;
|
|
}
|