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; /** * 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()); }