node-flakes 1.1.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -16
- package/dist/index.d.ts +6 -0
- package/dist/index.js +104 -0
- package/package.json +73 -23
- package/.editorconfig +0 -27
- package/.travis.yml +0 -10
- package/CHANGELOG.md +0 -14
- package/src/index.js +0 -86
package/README.md
CHANGED
|
@@ -25,37 +25,64 @@ The Node identity lib that provides:
|
|
|
25
25
|
|
|
26
26
|
The sequence number increments for each subsequent id requested within the same millisecond.
|
|
27
27
|
|
|
28
|
+
## Requirements
|
|
29
|
+
|
|
30
|
+
- Node.js 18 or newer
|
|
31
|
+
|
|
28
32
|
## API
|
|
29
33
|
|
|
30
|
-
### `
|
|
34
|
+
### `getBase62Provider()`
|
|
35
|
+
|
|
36
|
+
Returns a function that creates base62 ids that sort lexicographically by time.
|
|
37
|
+
|
|
38
|
+
```javascript
|
|
39
|
+
const { getBase62Provider } = require('node-flakes');
|
|
31
40
|
|
|
32
|
-
|
|
41
|
+
const nextId = getBase62Provider();
|
|
42
|
+
const id = nextId();
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
import { getBase62Provider } from 'node-flakes';
|
|
47
|
+
|
|
48
|
+
const nextId = getBase62Provider();
|
|
49
|
+
const id = nextId();
|
|
50
|
+
```
|
|
33
51
|
|
|
34
|
-
###
|
|
52
|
+
### `getBigIntIdProvider()`
|
|
35
53
|
|
|
36
|
-
|
|
54
|
+
Returns a function that creates bigint ids ordered by time.
|
|
37
55
|
|
|
38
56
|
```javascript
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
57
|
+
const { getBigIntIdProvider } = require('node-flakes');
|
|
58
|
+
|
|
59
|
+
const nextId = getBigIntIdProvider();
|
|
60
|
+
const id = nextId();
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### `getBigIntIdProviderFromMac(macNumber: bigint)`
|
|
64
|
+
|
|
65
|
+
Uses a provided 48-bit node id (MAC address) to seed the generator.
|
|
66
|
+
|
|
67
|
+
```javascript
|
|
68
|
+
const { getBigIntIdProviderFromMac } = require('node-flakes');
|
|
69
|
+
|
|
70
|
+
const nextId = getBigIntIdProviderFromMac(0x112233445566n);
|
|
71
|
+
const id = nextId();
|
|
46
72
|
```
|
|
47
73
|
|
|
48
|
-
###
|
|
74
|
+
### `bigIntTo62(num: bigint)`
|
|
49
75
|
|
|
50
|
-
|
|
76
|
+
Converts a bigint into a 22-character base62 id that preserves lexicographic order.
|
|
51
77
|
|
|
52
78
|
```javascript
|
|
53
|
-
const
|
|
54
|
-
|
|
79
|
+
const { bigIntTo62 } = require('node-flakes');
|
|
80
|
+
|
|
81
|
+
const id = bigIntTo62(123456789n);
|
|
55
82
|
```
|
|
56
83
|
|
|
57
84
|
### Speed
|
|
58
|
-
Looks like this tops out around
|
|
85
|
+
Looks like this tops out around 500 / ms on comodity processors. Do with that what you will :)
|
|
59
86
|
|
|
60
87
|
[travis-image]: https://travis-ci.org/arobson/node-flakes.svg?branch=master
|
|
61
88
|
[travis-url]: https://travis-ci.org/arobson/node-flakes
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare function getBigIntIdProvider(nodeIdentifier?: string): () => bigint;
|
|
2
|
+
export declare function getBase62Provider(nodeIdentifier?: string): () => string;
|
|
3
|
+
export declare function getBase36Provider(nodeIdentifier: string): () => string;
|
|
4
|
+
export declare function getBigIntIdProviderWithNodeId(nodeIdentifier: bigint): () => bigint;
|
|
5
|
+
export declare function bigIntTo62(num: bigint): string;
|
|
6
|
+
export declare function bigIntTo36(num: bigint): string;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// * Note: This implementation is based on the 128-bit flake id algorithm
|
|
3
|
+
// * developed by Boundary (github.com/boundary/flake) and implementations
|
|
4
|
+
// * I've written in other languages.
|
|
5
|
+
// * IMPORTANT: This implementation will require a custom epoch offset
|
|
6
|
+
// * in order to avoid the 2038 problem. The epoch offset *cannot* be
|
|
7
|
+
// * changed after the first id is generated in a given database.
|
|
8
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
9
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.getBigIntIdProvider = getBigIntIdProvider;
|
|
13
|
+
exports.getBase62Provider = getBase62Provider;
|
|
14
|
+
exports.getBase36Provider = getBase36Provider;
|
|
15
|
+
exports.getBigIntIdProviderWithNodeId = getBigIntIdProviderWithNodeId;
|
|
16
|
+
exports.bigIntTo62 = bigIntTo62;
|
|
17
|
+
exports.bigIntTo36 = bigIntTo36;
|
|
18
|
+
const getmac_1 = __importDefault(require("getmac"));
|
|
19
|
+
const farmhash_1 = __importDefault(require("farmhash"));
|
|
20
|
+
const EPOCH = 1577862000000n; // 2020-01-01T00:00:00.000Z
|
|
21
|
+
// Returns a function that is guaranteed to return a unique bigint
|
|
22
|
+
// that will be k-ordered (time ordered) when compared to other ids
|
|
23
|
+
function getBigIntIdProvider(nodeIdentifier) {
|
|
24
|
+
const nodeId = nodeIdentifier ? getNodeIdentifierAsBigInt(nodeIdentifier) : getMacAddressAsBigInt();
|
|
25
|
+
return getBigIntIdProviderWithNodeId(nodeId);
|
|
26
|
+
}
|
|
27
|
+
// Returns a function that is guaranteed to return a unique base62 string
|
|
28
|
+
// that will lexicographically sort in k-order (time ordered) when compared to other ids
|
|
29
|
+
// nodeIdentifier is an optional arbitrary string that is hashed to create the node id portion
|
|
30
|
+
// if not provided, the host mac address will be used
|
|
31
|
+
function getBase62Provider(nodeIdentifier) {
|
|
32
|
+
const idProvider = getBigIntIdProvider(nodeIdentifier);
|
|
33
|
+
return () => bigIntTo62(idProvider());
|
|
34
|
+
}
|
|
35
|
+
// Returns a function that is guaranteed to return a unique base36 string
|
|
36
|
+
// that will lexicographically sort in k-order (time ordered) when compared to other ids
|
|
37
|
+
function getBase36Provider(nodeIdentifier) {
|
|
38
|
+
const idProvider = getBigIntIdProvider(nodeIdentifier);
|
|
39
|
+
return () => bigIntTo36(idProvider());
|
|
40
|
+
}
|
|
41
|
+
// Creates a unique bigint id that is k-ordered (time ordered) when compared to other ids
|
|
42
|
+
function getBigIntIdProviderWithNodeId(nodeIdentifier) {
|
|
43
|
+
let lastTime = 0n;
|
|
44
|
+
let iteration = 0n;
|
|
45
|
+
return () => {
|
|
46
|
+
const now = BigInt(Date.now()) - EPOCH;
|
|
47
|
+
if (now === lastTime) {
|
|
48
|
+
iteration++;
|
|
49
|
+
if (iteration > 0xffffn) {
|
|
50
|
+
throw new Error('Too many iterations in the same millisecond');
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
else if (now < lastTime) {
|
|
54
|
+
throw new Error('NTP clock skew detected');
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
lastTime = now;
|
|
58
|
+
iteration = 0n;
|
|
59
|
+
}
|
|
60
|
+
return (lastTime << 64n) + (nodeIdentifier << 16n) + iteration;
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
// Converts a bigint to a base62 string that will sort lexicographically
|
|
64
|
+
const BASE62_ALPHABET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
|
|
65
|
+
function bigIntTo62(num) {
|
|
66
|
+
let result = '';
|
|
67
|
+
while (num > 0n) {
|
|
68
|
+
result = `${BASE62_ALPHABET[Number(num % 62n)]}${result}`;
|
|
69
|
+
num = num / 62n;
|
|
70
|
+
}
|
|
71
|
+
const len = 22 - result.length;
|
|
72
|
+
for (let i = 0; i < len; i++) {
|
|
73
|
+
result = `0${result}`;
|
|
74
|
+
}
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
77
|
+
// converts a bigint to a base36 string that will sort lexicographically
|
|
78
|
+
const BASE36_ALPHABET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
|
79
|
+
function bigIntTo36(num) {
|
|
80
|
+
let result = '';
|
|
81
|
+
while (num > 0n) {
|
|
82
|
+
result = `${BASE36_ALPHABET[Number(num % 36n)]}${result}`;
|
|
83
|
+
num = num / 36n;
|
|
84
|
+
}
|
|
85
|
+
const len = 25 - result.length;
|
|
86
|
+
for (let i = 0; i < len; i++) {
|
|
87
|
+
result = `0${result}`;
|
|
88
|
+
}
|
|
89
|
+
return result;
|
|
90
|
+
}
|
|
91
|
+
// Converts the host's mac address to a big int for use in the 48 bit
|
|
92
|
+
// node id portion of the flake id
|
|
93
|
+
function getMacAddressAsBigInt() {
|
|
94
|
+
return (0, getmac_1.default)()
|
|
95
|
+
.split(':')
|
|
96
|
+
.reduce((acc, octet, i) => {
|
|
97
|
+
return acc + (BigInt(`0x${octet}`) << BigInt(i * 8));
|
|
98
|
+
}, 0n);
|
|
99
|
+
}
|
|
100
|
+
// Take an arbitrary node identifier as a string, use farmhash to hash it to
|
|
101
|
+
// a 48 bit number for use in the node id portion of the flake id
|
|
102
|
+
function getNodeIdentifierAsBigInt(identifier) {
|
|
103
|
+
return BigInt(farmhash_1.default.hash32(identifier) & 0x0000FFFFFFFFFFFF);
|
|
104
|
+
}
|
package/package.json
CHANGED
|
@@ -1,15 +1,31 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-flakes",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "128-bit, k-ordered, lexicographically sortable, base 62, coordination
|
|
5
|
-
"main": "
|
|
3
|
+
"version": "2.1.0",
|
|
4
|
+
"description": "128-bit, k-ordered, lexicographically sortable, base 62, coordination-free id generation for Node that stays crunchy in milk",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"require": "./dist/index.js",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"README.md",
|
|
17
|
+
"LICENSE"
|
|
18
|
+
],
|
|
6
19
|
"scripts": {
|
|
7
|
-
"
|
|
8
|
-
"lint
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"
|
|
20
|
+
"build": "tsc -p tsconfig.json",
|
|
21
|
+
"lint": "semistandard src/* --ext .js,.ts",
|
|
22
|
+
"lint-fix": "semistandard src/* --fix --ext .js,.ts",
|
|
23
|
+
"pretest": "npm run lint",
|
|
24
|
+
"test": "vitest run",
|
|
25
|
+
"test:watch": "vitest",
|
|
26
|
+
"coverage": "vitest run --coverage",
|
|
27
|
+
"release": "standard-version",
|
|
28
|
+
"prepublishOnly": "npm run build"
|
|
13
29
|
},
|
|
14
30
|
"author": "Alex Robson",
|
|
15
31
|
"publishConfig": {
|
|
@@ -26,29 +42,63 @@
|
|
|
26
42
|
"128 bit",
|
|
27
43
|
"flake"
|
|
28
44
|
],
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=18"
|
|
47
|
+
},
|
|
29
48
|
"devDependencies": {
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
49
|
+
"@types/node": "^18.19.0",
|
|
50
|
+
"@typescript-eslint/eslint-plugin": "^8.51.0",
|
|
51
|
+
"@typescript-eslint/parser": "^8.51.0",
|
|
52
|
+
"@vitest/coverage-v8": "^1.6.0",
|
|
53
|
+
"semistandard": "^17.0.0",
|
|
54
|
+
"standard-version": "^9.1.0",
|
|
55
|
+
"typescript": "^5.4.5",
|
|
56
|
+
"vitest": "^1.6.0"
|
|
38
57
|
},
|
|
39
58
|
"dependencies": {
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"getmac": "^1.4.6"
|
|
59
|
+
"farmhash": "^5.0.1",
|
|
60
|
+
"getmac": "^6.6.0"
|
|
43
61
|
},
|
|
44
62
|
"semistandard": {
|
|
63
|
+
"parser": "@typescript-eslint/parser",
|
|
64
|
+
"parserOptions": {
|
|
65
|
+
"ecmaVersion": 2020,
|
|
66
|
+
"sourceType": "module",
|
|
67
|
+
"project": "./tsconfig.json"
|
|
68
|
+
},
|
|
69
|
+
"plugins": [
|
|
70
|
+
"@typescript-eslint"
|
|
71
|
+
],
|
|
45
72
|
"env": [
|
|
46
|
-
"
|
|
73
|
+
"node",
|
|
74
|
+
"es2020"
|
|
47
75
|
],
|
|
48
76
|
"globals": [
|
|
49
|
-
"
|
|
77
|
+
"_",
|
|
78
|
+
"afterEach",
|
|
79
|
+
"beforeEach",
|
|
80
|
+
"describe",
|
|
50
81
|
"expect",
|
|
51
|
-
"
|
|
82
|
+
"it",
|
|
83
|
+
"vi"
|
|
84
|
+
],
|
|
85
|
+
"ignore": [
|
|
86
|
+
"dist/**",
|
|
87
|
+
"node_modules/**"
|
|
52
88
|
]
|
|
89
|
+
},
|
|
90
|
+
"vitest": {
|
|
91
|
+
"coverage": {
|
|
92
|
+
"provider": "v8",
|
|
93
|
+
"reporter": [
|
|
94
|
+
"text",
|
|
95
|
+
"lcov"
|
|
96
|
+
],
|
|
97
|
+
"reportsDirectory": "coverage",
|
|
98
|
+
"exclude": [
|
|
99
|
+
"dist/**",
|
|
100
|
+
"node_modules/**"
|
|
101
|
+
]
|
|
102
|
+
}
|
|
53
103
|
}
|
|
54
104
|
}
|
package/.editorconfig
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
# This file is for unifying the coding style for different editors and IDEs
|
|
2
|
-
# editorconfig.org
|
|
3
|
-
|
|
4
|
-
root = true
|
|
5
|
-
|
|
6
|
-
[*]
|
|
7
|
-
end_of_line = lf
|
|
8
|
-
charset = utf-8
|
|
9
|
-
trim_trailing_whitespace = true
|
|
10
|
-
insert_final_newline = true
|
|
11
|
-
|
|
12
|
-
# Tabs in JS unless otherwise specified
|
|
13
|
-
[**.js]
|
|
14
|
-
indent_style = space
|
|
15
|
-
indent_size = 2
|
|
16
|
-
|
|
17
|
-
[**.jsx]
|
|
18
|
-
indent_style = space
|
|
19
|
-
indent_size = 2
|
|
20
|
-
|
|
21
|
-
[**.html]
|
|
22
|
-
indent_style = space
|
|
23
|
-
indent_size = 2
|
|
24
|
-
|
|
25
|
-
[**.css]
|
|
26
|
-
indent_style = space
|
|
27
|
-
indent_size = 2
|
package/.travis.yml
DELETED
package/CHANGELOG.md
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
|
-
|
|
5
|
-
## 1.1.0 (2021-01-29)
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
### Features
|
|
9
|
-
|
|
10
|
-
* upgrade dependencies for Node 12+ ([8abb932](https://github.com/arobson/node-flakes/commit/8abb9320805d6b1e559b97c95f15b4f7a3131d5f))
|
|
11
|
-
|
|
12
|
-
## 1.0.0
|
|
13
|
-
|
|
14
|
-
initial commit
|
package/src/index.js
DELETED
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
const os = require('os');
|
|
2
|
-
const getMac = require('getmac').getMac;
|
|
3
|
-
const jump = require('basejump');
|
|
4
|
-
const farmhash = require('farmhash');
|
|
5
|
-
let instance;
|
|
6
|
-
let instanceCount = 0;
|
|
7
|
-
|
|
8
|
-
// 128 bit ids can generate up to
|
|
9
|
-
// 1 1 2 2 3 3 3
|
|
10
|
-
// 5 0 5 0 5 0 5 9
|
|
11
|
-
// 340282366920938463463374607431768211456
|
|
12
|
-
// unique ids.
|
|
13
|
-
// By base-62 encoding them, we can shorten the length of the id to 22 places
|
|
14
|
-
|
|
15
|
-
// via http://stackoverflow.com/questions/8482309/converting-javascript-integer-to-byte-array-and-back
|
|
16
|
-
function longToBytes (long) {
|
|
17
|
-
// we want to represent the input as a 8-bytes array
|
|
18
|
-
const byteArray = [0, 0, 0, 0, 0, 0, 0, 0];
|
|
19
|
-
for (let index = 0; index < byteArray.length; index++) {
|
|
20
|
-
const byte = long & 0xff;
|
|
21
|
-
byteArray[index] = byte;
|
|
22
|
-
long = ((long - byte) / 256);
|
|
23
|
-
}
|
|
24
|
-
return byteArray.reverse();
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
async function getWorkerIdFromEnvironment () {
|
|
28
|
-
return new Promise(resolve => {
|
|
29
|
-
getMac((err, address) => {
|
|
30
|
-
if (err) {
|
|
31
|
-
address = os.hostname();
|
|
32
|
-
}
|
|
33
|
-
const bytes = getNodeBytesFromSeed(address);
|
|
34
|
-
resolve(bytes);
|
|
35
|
-
});
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function getNodeBytesFromSeed (seed) {
|
|
40
|
-
const hash = farmhash.hash32(`${seed.toString()}|${process.pid}`);
|
|
41
|
-
return longToBytes(hash).splice(2);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const Flake = function (seed) {
|
|
45
|
-
this.lastMs = Date.now();
|
|
46
|
-
this.msCounter = 0;
|
|
47
|
-
this.msBytes = [0, 0];
|
|
48
|
-
this.index = ++instanceCount;
|
|
49
|
-
this.seed = seed;
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
Flake.prototype.create = function () {
|
|
53
|
-
this.updateTime();
|
|
54
|
-
const bytes = this.timestamp.concat(this.seed, this.msBytes).reverse();
|
|
55
|
-
return jump.toBase62(bytes, 22);
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
Flake.prototype.seedFromEnvironment = async function () {
|
|
59
|
-
this.seed = await getWorkerIdFromEnvironment();
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
Flake.prototype.updateTime = function () {
|
|
63
|
-
const now = Date.now();
|
|
64
|
-
let change = false;
|
|
65
|
-
if (now < this.lastMs) {
|
|
66
|
-
throw new Error('NTP is slewing system time backwards - node-flakes cannot issue ids while this is occurring.');
|
|
67
|
-
} else if (now > this.lastMs) {
|
|
68
|
-
change = true;
|
|
69
|
-
this.msCounter = 0;
|
|
70
|
-
this.msBytes = [0, 0];
|
|
71
|
-
this.lastMs = now;
|
|
72
|
-
this.timestamp = longToBytes(now);
|
|
73
|
-
} else {
|
|
74
|
-
this.msCounter++;
|
|
75
|
-
this.msBytes = [0, 0].concat(longToBytes(this.msCounter)).splice(-2, 2);
|
|
76
|
-
}
|
|
77
|
-
return change;
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
module.exports = function (seed) {
|
|
81
|
-
if (instance) {
|
|
82
|
-
return instance;
|
|
83
|
-
}
|
|
84
|
-
instance = new Flake(seed);
|
|
85
|
-
return instance;
|
|
86
|
-
};
|