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 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
- ### `require('node-flakes')`
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
- Requiring `node-flakes` returns a function. Even when required and executed multiple times the same instance gets returned. This is worth noting so that you don't have to go through extra effort to pass around the seed value or continue to re-initialize it every time.
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
- ### Using MACAddress or HostName as seed
52
+ ### `getBigIntIdProvider()`
35
53
 
36
- If no seed is provided, you can use `seedFromEnvironment` to use either the MAC address or host name plus the process id as the seed. This may not prevent duplicate seeds or id collisions, because of scenario where duplicate MACs or host names in your environment can occur.
54
+ Returns a function that creates bigint ids ordered by time.
37
55
 
38
56
  ```javascript
39
- const flakes = require('node-flakes')();
40
- flakes.seedFromEnvironment() // this call returns a promise and can be awaited if in an async call
41
- .then(
42
- () => {
43
- const id = flakes.create(); // ta-da
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
- ### Unique seeds
74
+ ### `bigIntTo62(num: bigint)`
49
75
 
50
- Anything can seed the node id. node-flakes uses farmhash to create a unique 32 bit integer from whatever you have lying around. This needs to be unique for every instance creating ids.
76
+ Converts a bigint into a 22-character base62 id that preserves lexicographic order.
51
77
 
52
78
  ```javascript
53
- const flakes = require('node-flakes')('Hey, look, a (terrible) string based seed.');
54
- const id = flakes.create(); // ta-da
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 20 / ms on modern processors. Do with that what you will :)
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
@@ -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": "1.1.0",
4
- "description": "128-bit, k-ordered, lexicographically sortable, base 62, coordination free id generation for Node that stay crunchy in milk",
5
- "main": "src/index.js",
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
- "lint": "semistandard",
8
- "lint-fix": "semistandard --fix",
9
- "pretest": "semistandard",
10
- "test": "./node_modules/mocha/bin/mocha spec/**/*.spec.js",
11
- "coverage": "nyc npm test",
12
- "release": "standard-version"
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
- "chai": "^4.2.0",
31
- "coveralls": "^3.1.0",
32
- "fauxdash": "^1.7.0",
33
- "mocha": "^8.2.1",
34
- "mocha-lcov-reporter": "^1.3.0",
35
- "nyc": "^15.1.0",
36
- "semistandard": "^16.0.0",
37
- "standard-version": "^9.1.0"
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
- "basejump": "^1.1.0",
41
- "farmhash": "^3.2.0",
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
- "mocha"
73
+ "node",
74
+ "es2020"
47
75
  ],
48
76
  "globals": [
49
- "should",
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
@@ -1,10 +0,0 @@
1
- sudo: required
2
- os:
3
- - linux
4
-
5
- language: node_js
6
- node_js:
7
- - "8"
8
-
9
- script:
10
- - nyc npm test && nyc report --reporter=text-lcov | coveralls
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
- };