Skip to content

Commit

Permalink
Introduce sunflake generation (#1)
Browse files Browse the repository at this point in the history
* Introduce sunflake generation

* Update README to reflect Licensing
  • Loading branch information
robiot authored Jan 1, 2022
1 parent 0da84e3 commit a14e899
Show file tree
Hide file tree
Showing 9 changed files with 277 additions and 12 deletions.
6 changes: 6 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"tabWidth": 4,
"useTabs": false,
"singleQuote": true
}

5 changes: 0 additions & 5 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
{
"files.exclude": {
"lib": true,
"yarn.lock": true,
"yarn-error.log": true,
".prettierrc": true,
".gitignore": true,
"node_modules": true,
".vscode": true,
".github": false,
"coverage": true
}
}
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,4 @@ yarn add sunflake
## LICENSE

This package is licensed under the [GNU Lesser General Public License](https://www.gnu.org/licenses/lgpl-3.0).
Hexadecimal to decimal convertion within this package is sourced from [hex2dec](http://www.danvk.org/hex2dec.html), which is licensed under the [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0.html) license.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"devDependencies": {
"@types/jest": "^27.0.3",
"@types/node": "^17.0.2",
"@types/yup": "^0.29.13",
"@typescript-eslint/parser": "^5.2.0",
"chalk": "4.0.0",
"eslint": "^8.4.0",
Expand All @@ -35,7 +36,8 @@
"jest": "^27.4.4",
"ts-jest": "^27.1.1",
"ts-node": "^10.4.0",
"typescript": "^4.5.2"
"typescript": "^4.5.2",
"yup": "^0.32.11"
},
"dependencies": {},
"version": "0.0.1"
Expand Down
91 changes: 91 additions & 0 deletions src/hex2dec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// Adds two arrays for the given base (10 or 16), returning the result.
// This turns out to be the only "primitive" operation we need.
const add = (x: number[], y: number[], base: number) => {
let z = [];
let n = Math.max(x.length, y.length);
let carry = 0;
let i = 0;
while (i < n || carry) {
let xi = i < x.length ? x[i] : 0;
let yi = i < y.length ? y[i] : 0;
let zi = carry + xi + yi;
z.push(zi % base);
carry = Math.floor(zi / base);
i++;
}

return z;
};

// Returns a*x, where x is an array of decimal digits and a is an ordinary
// JavaScript number. base is the number base of the array x.
const multiplyByNumber = (num: number, x: number[], base: number): number[] => {
if (num == 0 || num < 0) return [];

let result: number[] = [];
let power = x;

while (num !== 0) {
if (num & 1) {
result = add(result, power, base);
}

num >>= 1;

if (num === 0) break;

power = add(power, power, base);
}

return result;
};

const parseToDigitsArray = (str: string, base: number) => {
let digits = str.split('');
let ary = [];
for (let i = digits.length - 1; i >= 0; i--) {
let n = parseInt(digits[i], base);

if (isNaN(n)) return null;

ary.push(n);
}

return ary;
};

const convertBase = (str: string, fromBase: number, toBase: number) => {
let digits = parseToDigitsArray(str, fromBase);

if (digits === null) return null;

let outArray: number[] = [];
let power = [1];
for (let i = 0; i < digits.length; i++) {
// inletiant: at this point, fromBase^i = power
if (digits[i]) {
outArray = add(
outArray,
multiplyByNumber(digits[i] as number, power, toBase),
toBase
);
}

power = multiplyByNumber(fromBase, power, toBase);
}

let out = '';
for (let i = outArray.length - 1; i >= 0; i--) {
out += outArray[i].toString(toBase);
}

return out;
};

export const hexToDec = (hexStr: string) => {
if (hexStr.startsWith('0x')) hexStr = hexStr.substring(2);

hexStr = hexStr.toLowerCase();

return convertBase(hexStr, 16, 10);
};
64 changes: 63 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,63 @@
export const Sunflake = {};
import { hexToDec } from './hex2dec';

let lastTime: number = 0;
let seq: number = 0;

export type SunflakeConfig = {
machineID: number;
epoch: number;
time?: number;
};

export const generateSunflake = async (config: SunflakeConfig) => {
let { machineID, epoch, time } = Object.assign<
SunflakeConfig,
Partial<SunflakeConfig>
>(
{
epoch: 0,
machineID: 0,
time: 1,
},
config
) as Required<SunflakeConfig>;

lastTime = time;
machineID = machineID % 1023;

const bTime = (time - epoch).toString(2);

// Get the sequence number
if (lastTime == time) {
seq++;

if (seq > 4095) {
seq = 0;

// Make system wait till time is been shifted by one millisecond
while (Date.now() <= time) {
await new Promise<void>((acc) => setImmediate(acc));
}
}
} else {
seq = 0;
}

lastTime = time;

let bSeq = seq.toString(2);
let bMid = machineID.toString(2);

// Create sequence binary bit
while (bSeq.length < 12) bSeq = '0' + bSeq;
while (bMid.length < 10) bMid = '0' + bMid;

const bid = bTime + bMid + bSeq;

let id = '';
for (let i = bid.length; i > 0; i -= 4) {
id = parseInt(bid.substring(i - 4, i), 2).toString(16) + id;
}

return hexToDec(id);
};
5 changes: 0 additions & 5 deletions tests/snowflake.test.ts

This file was deleted.

58 changes: 58 additions & 0 deletions tests/sunflake.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { generateSunflake } from '../src';

const EPOCH: number = 1640988001000; // First second of 2022


it('Exports Sunflake', () => {
expect(generateSunflake);
});

it('Generates two snowflake value', async () => {
const flake1 = await generateSunflake({ machineID: 1, epoch: EPOCH });
const flake2 = await generateSunflake({ machineID: 1, epoch: EPOCH });

expect(flake1 != flake2);
});

it('Generates two snowflake value in sync', async () => {
const [flake1, flake2] = await Promise.all([
generateSunflake({ machineID: 1, epoch: EPOCH }),
generateSunflake({ machineID: 1, epoch: EPOCH }),
]);

expect(flake1 != flake2);
});

it('Generates two snowflake value in sync with same time', async () => {
const time = Date.now();
const [flake1, flake2] = await Promise.all([
generateSunflake({ machineID: 1, epoch: EPOCH, time }),
generateSunflake({ machineID: 1, epoch: EPOCH, time }),
]);

expect(flake1 !== flake2);
});

it('Generates 500 snowflake value in sync with same time', async () => {
const time = Date.now();
const hugeList = [];
for (let i = 0; i <= 500; i++) {
hugeList.push(generateSunflake({ machineID: 1, epoch: EPOCH, time }));
}

const list = await Promise.all(hugeList);

expect(new Set(list).size === list.length);
});

it('Generates 5200 snowflake value in sync with same time', async () => {
const time = Date.now();
const hugeList = [];
for (let i = 0; i <= 5200; i++) {
hugeList.push(generateSunflake({ machineID: 1, epoch: EPOCH, time }));
}

const list = await Promise.all(hugeList);

expect(new Set(list).size === list.length);
});
55 changes: 55 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.16.7"

"@babel/runtime@^7.15.4":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.7.tgz#03ff99f64106588c9c403c6ecb8c3bafbbdff1fa"
integrity sha512-9E9FJowqAsytyOY6LG+1KuueckRL+aQW+mKvXRXnuFGyRAyepJPmEo9vgMfXUA6O9u3IeEdv9MAkppFcaQwogQ==
dependencies:
regenerator-runtime "^0.13.4"

"@babel/template@^7.16.7", "@babel/template@^7.3.3":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155"
Expand Down Expand Up @@ -653,6 +660,11 @@
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d"
integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==

"@types/lodash@^4.14.175":
version "4.14.178"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.178.tgz#341f6d2247db528d4a13ddbb374bcdc80406f4f8"
integrity sha512-0d5Wd09ItQWH1qFbEyQ7oTQ3GZrMfth5JkbN3EvTKLXcHLRDSXeLnlvlOn0wvxVIwK5o2M8JzP/OWz7T3NRsbw==

"@types/node@*", "@types/node@^17.0.2":
version "17.0.6"
resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.6.tgz#cc1589c9ee853b389e67e8fb4384e0f250a139b9"
Expand Down Expand Up @@ -685,6 +697,11 @@
dependencies:
"@types/yargs-parser" "*"

"@types/yup@^0.29.13":
version "0.29.13"
resolved "https://registry.yarnpkg.com/@types/yup/-/yup-0.29.13.tgz#21b137ba60841307a3c8a1050d3bf4e63ad561e9"
integrity sha512-qRyuv+P/1t1JK1rA+elmK1MmCL1BapEzKKfbEhDBV/LMMse4lmhZ/XbgETI39JveDJRpLjmToOI6uFtMW/WR2g==

"@typescript-eslint/experimental-utils@^5.0.0":
version "5.8.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-5.8.1.tgz#01861eb2f0749f07d02db342b794145a66ed346f"
Expand Down Expand Up @@ -2425,6 +2442,11 @@ locate-path@^5.0.0:
dependencies:
p-locate "^4.1.0"

lodash-es@^4.17.21:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee"
integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==

lodash.memoize@4.x:
version "4.1.2"
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
Expand Down Expand Up @@ -2528,6 +2550,11 @@ multimap@^1.1.0:
resolved "https://registry.yarnpkg.com/multimap/-/multimap-1.1.0.tgz#5263febc085a1791c33b59bb3afc6a76a2a10ca8"
integrity sha512-0ZIR9PasPxGXmRsEF8jsDzndzHDj7tIav+JUmvIFB/WHswliFnquxECT/De7GR4yg99ky/NlRKJT82G1y271bw==

nanoclone@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/nanoclone/-/nanoclone-0.2.1.tgz#dd4090f8f1a110d26bb32c49ed2f5b9235209ed4"
integrity sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA==

natural-compare@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
Expand Down Expand Up @@ -2734,6 +2761,11 @@ prompts@^2.0.1:
kleur "^3.0.3"
sisteransi "^1.0.5"

property-expr@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-2.0.4.tgz#37b925478e58965031bb612ec5b3260f8241e910"
integrity sha512-sFPkHQjVKheDNnPvotjQmm3KD3uk1fWKUN7CrpdbwmUx3CrG3QiM8QpTSimvig5vTXmTvjz7+TDvXOI9+4rkcg==

psl@^1.1.33:
version "1.8.0"
resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24"
Expand Down Expand Up @@ -2773,6 +2805,11 @@ read-pkg@^5.2.0:
parse-json "^5.0.0"
type-fest "^0.6.0"

regenerator-runtime@^0.13.4:
version "0.13.9"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52"
integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==

regexp-tree@^0.1.23, regexp-tree@~0.1.1:
version "0.1.24"
resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.24.tgz#3d6fa238450a4d66e5bc9c4c14bb720e2196829d"
Expand Down Expand Up @@ -3090,6 +3127,11 @@ to-regex-range@^5.0.1:
dependencies:
is-number "^7.0.0"

toposort@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/toposort/-/toposort-2.0.2.tgz#ae21768175d1559d48bef35420b2f4962f09c330"
integrity sha1-riF2gXXRVZ1IvvNUILL0li8JwzA=

tough-cookie@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.0.0.tgz#d822234eeca882f991f0f908824ad2622ddbece4"
Expand Down Expand Up @@ -3370,3 +3412,16 @@ yn@3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==

yup@^0.32.11:
version "0.32.11"
resolved "https://registry.yarnpkg.com/yup/-/yup-0.32.11.tgz#d67fb83eefa4698607982e63f7ca4c5ed3cf18c5"
integrity sha512-Z2Fe1bn+eLstG8DRR6FTavGD+MeAwyfmouhHsIUgaADz8jvFKbO/fXc2trJKZg+5EBjh4gGm3iU/t3onKlXHIg==
dependencies:
"@babel/runtime" "^7.15.4"
"@types/lodash" "^4.14.175"
lodash "^4.17.21"
lodash-es "^4.17.21"
nanoclone "^0.2.1"
property-expr "^2.0.4"
toposort "^2.0.2"

0 comments on commit a14e899

Please # to comment.