migrate snowflake generator to new library
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

This commit is contained in:
James Brumond 2023-08-21 19:45:34 -07:00
parent e8688c0b8f
commit abfbabf71f
Signed by: james
GPG Key ID: E8F2FC44BAA3357A
6 changed files with 166 additions and 27 deletions

View File

@ -23,8 +23,8 @@ jobs:
- name: Login to package registry - name: Login to package registry
run: | run: |
npm config set @<scope name>:registry https://gitea.jbrumond.me/api/packages/<scope name>/npm/ npm config set @js:registry https://gitea.jbrumond.me/api/packages/js/npm/
npm config set -- '//gitea.jbrumond.me/api/packages/<scope name>/npm/:_authToken' "$NPM_PUBLISH_TOKEN" npm config set -- '//gitea.jbrumond.me/api/packages/js/npm/:_authToken' "$NPM_PUBLISH_TOKEN"
- name: Install dependencies - name: Install dependencies
run: npm ci run: npm ci

View File

@ -26,8 +26,8 @@ jobs:
- name: Login to package registry - name: Login to package registry
run: | run: |
npm config set @<scope name>:registry https://gitea.jbrumond.me/api/packages/<scope name>/npm/ npm config set @js:registry https://gitea.jbrumond.me/api/packages/js/npm/
npm config set -- '//gitea.jbrumond.me/api/packages/<scope name>/npm/:_authToken' "$NPM_PUBLISH_TOKEN" npm config set -- '//gitea.jbrumond.me/api/packages/js/npm/:_authToken' "$NPM_PUBLISH_TOKEN"
- name: Install dependencies - name: Install dependencies
run: npm ci run: npm ci

7
package-lock.json generated
View File

@ -9,9 +9,16 @@
"version": "1.0.0", "version": "1.0.0",
"license": "ISC", "license": "ISC",
"devDependencies": { "devDependencies": {
"@types/node": "^20.5.1",
"typescript": "^5.1.3" "typescript": "^5.1.3"
} }
}, },
"node_modules/@types/node": {
"version": "20.5.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.1.tgz",
"integrity": "sha512-4tT2UrL5LBqDwoed9wZ6N3umC4Yhz3W3FloMmiiG4JwmUJWpie0c7lcnUNd4gtMKuDEO4wRVS8B6Xa0uMRsMKg==",
"dev": true
},
"node_modules/typescript": { "node_modules/typescript": {
"version": "5.1.6", "version": "5.1.6",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz",

View File

@ -1,7 +1,7 @@
{ {
"name": "@templates/typescript-library", "name": "@js/snowflake-uid",
"version": "1.0.0", "version": "0.1.0",
"description": "Template project for creating new TypeScript library packages", "description": "Utility for generating Snowflake UIDs",
"main": "build/index.js", "main": "build/index.js",
"types": "build/index.d.ts", "types": "build/index.d.ts",
"scripts": { "scripts": {
@ -9,15 +9,16 @@
"clean": "rm -rf ./build" "clean": "rm -rf ./build"
}, },
"publishConfig": { "publishConfig": {
"registry": "https://gitea.jbrumond.me/api/packages/templates/npm/" "registry": "https://gitea.jbrumond.me/api/packages/js/npm/"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://gitea.jbrumond.me/templates/typescript-library.git" "url": "https://gitea.jbrumond.me/js/snowflake-uid.git"
}, },
"author": "James Brumond <https://jbrumond.me>", "author": "James Brumond <https://jbrumond.me>",
"license": "ISC", "license": "ISC",
"devDependencies": { "devDependencies": {
"@types/node": "^20.5.1",
"typescript": "^5.1.3" "typescript": "^5.1.3"
} }
} }

View File

@ -1,30 +1,62 @@
Template project for creating new TypeScript library packages Utility for generating Snowflake UIDs (see: <https://en.wikipedia.org/wiki/Snowflake_ID>)
--- ---
## Get Started ## Install
### Pull down the code <!-- -->
```bash
git init
git pull https://gitea.jbrumond.me/templates/typescript-library.git master
```
### Update configuration
- In `package.json`, update any fields like `name`, `description`, `repository`, etc.
- In `.gitea/workflows/publish.yaml`, update `<scope name>` placeholders
## Building ## Usage
```bash ```ts
npm run tsc import { Snowflake, create_snowflake_uid_generator } from '@js/snowflake-uid';
const snowflake = create_snowflake_uid_generator({
// See "More about instance_size" below
instance_size: 10n,
// Epoch time for the timestamp field. Static field that cannot be
// changed once you start generating IDs. Must be a time in the past.
epoch: BigInt(Date.parse('1 Jan 2020 00:00:00')),
// Instance ID for this generator. Must be unique across all generators
// in the same ID space. IDs can be reused once a new generator can no
// longer create IDs. These should likely be treated as a shared resource
// allocated from a pool. Must fit inside of the space allocated by the
// instance_size field.
instance: process.env.SNOWFLAKE_INSTANCE_ID,
});
// IDs can be generated as bigint or string values
const id1: bigint = snowflake.uid();
const id2: Snowflake = snowflake.uid_str();
``` ```
### More about `instance_size`
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 space limitations 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 |

View File

@ -1,4 +1,103 @@
export function hello() : string { import { randomInt } from 'crypto';
return 'hello';
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());
} }