dashboard/src/utilities/snowflake-uid.ts

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;
}