snowflake-uid/src/index.ts
James Brumond abfbabf71f
All checks were successful
Build and test / build-and-test (18.x) (push) Successful in 13s
Build and test / build-and-test (20.x) (push) Successful in 13s
migrate snowflake generator to new library
2023-08-21 19:45:34 -07:00

104 lines
3.8 KiB
TypeScript

import { randomInt } from 'crypto';
export type Snowflake = `${bigint}`;
export interface SnowflakeConfig {
/**
* Number of bits allocated to the instance ID.
*
* A larger value enables running more concurrent instances generating IDs in the
* same space (as few as 16 for 4-bits, or as many as 1024 for 10-bits).
*
* This number cannot be changed once you start generating IDs, so you should choose
* a value that you don't think you will exceed the supported instance count for.
*
* Using fewer bits for the instance creates more space for the timestamp and sequence
* number fields. The supported configurations are listed below, with the `instance_size`
* parameter representing the bit-size of the "instances" column:
*
* | instances | timestamp space | sequence space |
* |--------------------------|-----------------------|-----------------------------|
* | `4-bit`: 16 instances | `44-bit`: ~560 years | `15-bit`: 32768 IDs/ms/inst |
* | `5-bit`: 32 instances | `44-bit`: ~560 years | `14-bit`: 16384 IDs/ms/inst |
* | `6-bit`: 64 instances | `43-bit`: ~280 years | `14-bit`: 16384 IDs/ms/inst |
* | `7-bit`: 128 instances | `43-bit`: ~280 years | `13-bit`: 8192 IDs/ms/inst |
* | `8-bit`: 256 instances | `42-bit`: ~140 years | `13-bit`: 8192 IDs/ms/inst |
* | `9-bit`: 512 instances | `42-bit`: ~140 years | `12-bit`: 4096 IDs/ms/inst |
* | `10-bit`: 1024 instances | `41-bit`: ~70 years | `12-bit`: 4096 IDs/ms/inst |
*/
instance_size: 4n | 5n | 6n | 7n | 8n | 9n | 10n;
/**
* Epoch time, e.g. `1577836800000n` for 1 Jan 2020 00:00:00
*/
epoch: bigint;
/**
* Instance ID to use in generated IDs. This value MUST be unique across all generators
* within the ID space (otherwise, ID collisions will occur). Must fit within the space
* allocated by the `instance_size` parameter.
*/
instance: bigint;
}
export function validate_snowflake_conf(conf: unknown) : asserts conf is SnowflakeConfig {
// todo: validate config
}
export type SnowflakeUIDGenerator = ReturnType<typeof create_snowflake_uid_generator>;
/**
* 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 configurable instance size (rather than the hard-coded 10-bits), and increases
* the size of the other fields if set to values smaller than 10.
*/
export function create_snowflake_uid_generator(conf: SnowflakeConfig) {
switch (conf.instance_size) {
case 4n: return generator(15n, 0x7fffn, 0xfffffffffffn);
case 5n: return generator(14n, 0x3fffn, 0xfffffffffffn);
case 6n: return generator(14n, 0x3fffn, 0x7ffffffffffn);
case 7n: return generator(13n, 0x1fffn, 0x7ffffffffffn);
case 8n: return generator(13n, 0x1fffn, 0x3ffffffffffn);
case 9n: return generator(12n, 0x0fffn, 0x3ffffffffffn);
case 10n: return generator(12n, 0x0fffn, 0x1ffffffffffn);
}
function generator(sequence_size: bigint, sequence_mask: bigint, timestamp_mask: bigint) {
const instance = BigInt(conf.instance) << sequence_size;
const timestamp_offset = conf.instance_size + sequence_size;
let sequence = BigInt(randomInt(Number(sequence_mask)));
function next_sequence() {
const value = sequence++;
sequence &= sequence_mask;
return value;
}
return {
uid() {
const sequence = next_sequence();
const timestamp = (now_big() - conf.epoch) & timestamp_mask;
return BigInt.asUintN(64, (timestamp << timestamp_offset) | instance | sequence);
},
uid_str() {
return this.uid().toString(10) as Snowflake;
}
};
}
}
export function is_snowflake_uid(value: string) : value is Snowflake {
return /^\d+$/.test(value) && value.length <= 20;
}
function now_big() {
return BigInt(Date.now());
}