mirror of
https://github.com/Wessel/Snowflakey.git
synced 2026-06-08 14:19:02 +02:00
Rewrite into TypeScript
This commit is contained in:
8
.editorconfig
Normal file
8
.editorconfig
Normal file
@@ -0,0 +1,8 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = false
|
||||
2
.eslintignore
Normal file
2
.eslintignore
Normal file
@@ -0,0 +1,2 @@
|
||||
node_modules/
|
||||
dist/
|
||||
65
.eslintrc
Normal file
65
.eslintrc
Normal file
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"env": {
|
||||
"es6": true,
|
||||
"amd": true,
|
||||
"node": true,
|
||||
"mongo": true,
|
||||
"jquery": true,
|
||||
"browser": true,
|
||||
"commonjs": true
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 9,
|
||||
"sourceType": "module",
|
||||
"ecmaFeatures": {
|
||||
"jsx": true,
|
||||
"forOf": true,
|
||||
"spread": true,
|
||||
"modules": true,
|
||||
"classes": true,
|
||||
"generators": true,
|
||||
"restParams": true,
|
||||
"regexUFlag": true,
|
||||
"regexYFlag": true,
|
||||
"globalReturn": true,
|
||||
"destructuring": true,
|
||||
"impliedStrict": true,
|
||||
"blockBindings": true,
|
||||
"defaultParams": true,
|
||||
"octalLiterals": true,
|
||||
"arrowFunctions": true,
|
||||
"binaryLiterals": true,
|
||||
"templateStrings": true,
|
||||
"superInFunctions": true,
|
||||
"unicodeCodePointEscapes": true,
|
||||
"objectLiteralShorthandMethods": true,
|
||||
"objectLiteralComputedProperties": true,
|
||||
"objectLiteralDuplicateProperties": true,
|
||||
"objectLiteralShorthandProperties": true
|
||||
}
|
||||
},
|
||||
"plugins": [],
|
||||
"rules": {
|
||||
"semi": "warn",
|
||||
"indent": [ 0, 2 ],
|
||||
"strict": "off",
|
||||
"eqeqeq": "error",
|
||||
"no-var": "warn",
|
||||
"no-undef": "warn",
|
||||
"valid-jsdoc": "warn",
|
||||
"comma-dangle": "warn",
|
||||
"no-dupe-args": "warn",
|
||||
"no-dupe-keys": "warn",
|
||||
"require-await": "warn",
|
||||
"spaced-comment": "error",
|
||||
"space-in-parens": "error",
|
||||
"no-global-assign": "warn",
|
||||
"no-duplicate-imports": "error",
|
||||
"no-dupe-class-members": "error"
|
||||
},
|
||||
"globals": {
|
||||
"_config": false,
|
||||
"console": false
|
||||
}
|
||||
}
|
||||
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
||||
14
.github/CONTRIBUTING.md
vendored
Normal file
14
.github/CONTRIBUTING.md
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
# Contributing
|
||||
|
||||
**The issue tracker is only for bug reports and enhancement suggestions. If you have a question, please ask it in the [Discord](https://discord.gg/SV7DAE9) instead of opening an issue – you will get redirected there anyway.**
|
||||
|
||||
If you wish to contribute to Aimaina, feel free to fork the repository and submit a pull request.
|
||||
[ESLint](https://eslint.org/) is used to correct most typo's you make, so it would be helpful if you added [ESLint](https://eslint.org/) to your editor of choice)
|
||||
|
||||
## Setup
|
||||
To get ready to work on the codebase, please do the following:
|
||||
|
||||
1. Fork & clone the repository, and make sure you're on the **master** branch
|
||||
2. Run `yarn --dev` or `npm install --dev`
|
||||
4. Code your heart out and test using `yarn test` or `npm run test`!
|
||||
6. [Submit a pull request](https://github.com/PassTheWessel/aimaina/compare)
|
||||
2
.github/FUNDING.yml
vendored
Normal file
2
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
patreon: wessel
|
||||
ko_fi: wessel
|
||||
26
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
26
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: Report an issue with Aimaina
|
||||
title: "[BUG] Somenthing broke"
|
||||
labels: bug
|
||||
assignees: PassTheWessel
|
||||
|
||||
---
|
||||
|
||||
**Please describe the problem you are having in as much detail as possible:**
|
||||
|
||||
|
||||
**Include a reproducible code sample here, if possible:**
|
||||
```js
|
||||
// Place your code here
|
||||
```
|
||||
|
||||
**Further details:**
|
||||
- Node.js version:
|
||||
- Operating system:
|
||||
|
||||
<!--
|
||||
If this applies to you, please check the respective checkbox: [ ] becomes [x].
|
||||
-->
|
||||
|
||||
- [ ] I have also tested the issue on latest master, commit hash:
|
||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Request a feature for Aimaina
|
||||
title: "[ENHANCEMENT] This is an amazing new idea!"
|
||||
labels: enhancement
|
||||
assignees: PassTheWessel
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the ideal solution**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
10
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
10
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
**Please describe the changes this PR makes and why it should be merged:**
|
||||
|
||||
|
||||
**Status**
|
||||
- [ ] Code changes have been tested and there aren't any typos in it
|
||||
|
||||
**Semantic versioning classification:**
|
||||
- [ ] This PR changes wumpfetch's core codebase (methods or parameters added)
|
||||
- [ ] This PR includes breaking changes (methods removed or renamed, parameters moved or removed)
|
||||
- [ ] This PR **only** includes non-code changes, like changes to documentation, README, etc.
|
||||
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
node_modules/
|
||||
dist/
|
||||
89
.vscode/settings.json
vendored
Normal file
89
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
General vscode settings for all my projects
|
||||
Made by Wessel "wesselgame" T <discord@go2it.eu> (https://github.com/PassTheWessel)
|
||||
|
||||
Extensions: rainglow, eslint, tslint, material icon theme, gitlens, fish-vscode, Docker,
|
||||
Popping and Locking theme, Sass, stylelint, SVG viewer, Trailing spaces, Auto renaming tags
|
||||
|
||||
Color themes: Banner (rainglow), Hyrule (rainglow), Azure (rainglow), Github (rainglow),
|
||||
Heroku (rainglow), Popping and Locking
|
||||
*/
|
||||
{
|
||||
// Window
|
||||
"window.zoomLevel": 0,
|
||||
// Editor
|
||||
"editor.fontSize": 13,
|
||||
"editor.fontWeight": "200",
|
||||
"editor.fontFamily": "'SFMono-Regular','Consolas','Liberation Mono','Menlo','Courier','monospace'",
|
||||
"editor.lineHeight": 20,
|
||||
"editor.fontLigatures": true,
|
||||
"editor.cursorStyle": "line",
|
||||
"editor.cursorWidth": 0,
|
||||
"editor.cursorBlinking": "blink",
|
||||
"editor.multiCursorModifier": "ctrlCmd",
|
||||
"editor.minimap.enabled": true,
|
||||
"editor.smoothScrolling": true,
|
||||
"editor.minimap.renderCharacters": true,
|
||||
"editor.tabSize": 2,
|
||||
"editor.autoIndent": false,
|
||||
"editor.insertSpaces": true,
|
||||
"editor.tabCompletion": "on",
|
||||
"editor.formatOnPaste": true,
|
||||
"editor.detectIndentation": false,
|
||||
"editor.wordWrap": "off",
|
||||
"editor.matchBrackets": true,
|
||||
"editor.renderWhitespace": "none",
|
||||
"editor.autoClosingBrackets": "always",
|
||||
"editor.tokenColorCustomizations": {
|
||||
"types": "#C59F61",
|
||||
"strings": "#F6EB90",
|
||||
"numbers": "#C54121",
|
||||
"keywords": "#C59F49",
|
||||
"comments": "#6a737d",
|
||||
"variables": "#C55F45",
|
||||
"functions": "#C59F55"
|
||||
},
|
||||
//Workbench
|
||||
"workbench.iconTheme": "material-icon-theme",
|
||||
"workbench.colorTheme": "Banner (rainglow)",
|
||||
"workbench.editor.showTabs": true,
|
||||
"workbench.editor.tabSizing": "fit",
|
||||
"workbench.sideBar.location": "left",
|
||||
// Debug
|
||||
"debug.toolBarLocation": "floating",
|
||||
"debug.allowBreakpointsEverywhere": true,
|
||||
// Console
|
||||
"terminal.integrated.shell.windows": "C:\\Windows\\System32\\bash.exe", // C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe
|
||||
// Files
|
||||
"files.autoSave": "off",
|
||||
"files.exclude": {
|
||||
"**/.git": false,
|
||||
"**/.svn": false,
|
||||
"**/.hg": false,
|
||||
"**/CVS": false,
|
||||
"**/.DS_Store": false
|
||||
},
|
||||
// Breadcrumbs
|
||||
"breadcrumbs.enabled": true,
|
||||
"breadcrumbs.filePath": "last",
|
||||
"breadcrumbs.symbolPath": "on",
|
||||
"breadcrumbs.symbolSortOrder": "name",
|
||||
// Git
|
||||
"git.enableSmartCommit": true,
|
||||
"git.ignoreLimitWarning": true,
|
||||
// ESlint
|
||||
"eslint.enable": true,
|
||||
"eslint.packageManager": "yarn",
|
||||
// Languages
|
||||
"[yaml]": {
|
||||
"editor.tabSize": 2,
|
||||
"editor.autoIndent": false,
|
||||
"editor.insertSpaces": true
|
||||
},
|
||||
"[markdown]": {
|
||||
"editor.wordWrap": "on",
|
||||
"editor.quickSuggestions": false
|
||||
},
|
||||
// Live share
|
||||
"liveshare.featureSet": "insiders",
|
||||
}
|
||||
18
.vscode/snippets.code-snippets
vendored
Normal file
18
.vscode/snippets.code-snippets
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
// Place your global snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and
|
||||
// description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope
|
||||
// is left empty or omitted, the snippet gets applied to all languages. The prefix is what is
|
||||
// used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
|
||||
// $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders.
|
||||
// Placeholders with the same ids are connected.
|
||||
// Example:
|
||||
// "Print to console": {
|
||||
// "scope": "javascript,typescript",
|
||||
// "prefix": "log",
|
||||
// "body": [
|
||||
// "console.log('$1');",
|
||||
// "$2"
|
||||
// ],
|
||||
// "description": "Log output to console"
|
||||
// }
|
||||
}
|
||||
1
CODEOWNERS
Normal file
1
CODEOWNERS
Normal file
@@ -0,0 +1 @@
|
||||
* @PassTheWessel
|
||||
76
CODE_OF_CONDUCT.md
Normal file
76
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||
level of experience, education, socio-economic status, nationality, personal
|
||||
appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at discord@go2it.eu. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see
|
||||
https://www.contributor-covenant.org/faq
|
||||
2
LICENSE
2
LICENSE
@@ -1,7 +1,7 @@
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2019-present Wessel "wesselgame" T
|
||||
Copyright (c) 2019-present Wessel "wesselgame" T <discord@go2it.eu>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
58
README.md
58
README.md
@@ -5,38 +5,58 @@
|
||||
|
||||
## Installing
|
||||
```sh
|
||||
$ yarn add snowflakey # Install w/ Yarn (the superior package manager)
|
||||
$ yarn add snowflakey # Install w/ Yarn
|
||||
$ npm i snowflakey # Install w/ NPM
|
||||
```
|
||||
|
||||
## Usage
|
||||
##### Code
|
||||
##### Using a worker
|
||||
```js
|
||||
// require & generate the instance
|
||||
const Snowflake = require( './generator' );
|
||||
const snowflake = new Snowflake.generator({
|
||||
processBits: 0,
|
||||
// Declare snowflakey
|
||||
const snowflakey = require('snowflakey');
|
||||
// Create the worker instance
|
||||
const Worker = new snowflakey.Worker({
|
||||
name: 'starling',
|
||||
epoch: 1420070400000,
|
||||
workerId: process.env.CLUSTER_ID || 31,
|
||||
processId: process.pid || undefined,
|
||||
workerBits: 8,
|
||||
incrementBits: 14,
|
||||
workerId: process.env.CLUSTER_ID || 31
|
||||
processBits: 0,
|
||||
incrementBits: 14
|
||||
});
|
||||
|
||||
// exports for global use
|
||||
exports.makeSnowflake = ( date ) => { return snowflake._generate( date ); };
|
||||
exports.unmakeSnowflake = ( flake ) => { let decon = snowflake.deconstruct( flake ); return decon.timestamp.valueOf(); };
|
||||
// Generate the snowflake
|
||||
const flake = Worker.generate();
|
||||
console.log(`Created snowflake: ${flake}`);
|
||||
console.log(`Creation date : ${snowflakey.lookup(flake, worker.options.epoch)}`);
|
||||
console.log(`Deconstructed : ${Worker.deconstruct(flake).timestamp.valueOf()}`);
|
||||
```
|
||||
|
||||
// example
|
||||
const flake = this.makeSnowflake( Date.now() );
|
||||
console.log( flake );
|
||||
console.log( `Creation date: ${Snowflake.lookup( flake, 1420070400000 )}` );
|
||||
console.log( this.unmakeSnowflake( flake ) );
|
||||
##### Using a master
|
||||
```js
|
||||
// ... Worker code
|
||||
// Create the master instance and add the worker
|
||||
const Master = new snowflakey.Master();
|
||||
master.addWorker(Worker);
|
||||
// Listen to the events
|
||||
master.on('newSnowflake', (data) => {
|
||||
console.log(`created snowflake: ${data.snowflake} by Worker ${data.worker.options.name || data.worker.options.workerId}`)
|
||||
console.log(`Creation date : ${Snowflake.lookup(flake, data.worker.options.epoch)}`);
|
||||
data.worker.deconstruct(data.snowflake);
|
||||
});
|
||||
|
||||
master.on('deconstructedFlake', (data) => {
|
||||
console.log(`Deconstructed : ${data.timestamp.valueOf()} by Worker ${data.worker.options.name || data.worker.options.workerId}`);
|
||||
});
|
||||
// Make the worker generate a snowflake
|
||||
worker.generate();
|
||||
```
|
||||
##### Result
|
||||
```sh
|
||||
$ node test.js
|
||||
534760094454759424
|
||||
Creation date: 2019-1-15 16:45:41
|
||||
1547567141880
|
||||
Created snowflake: 534760094454759424
|
||||
Creation date : 2019-1-15 16:45:41
|
||||
Deconstructed : 1547567141880
|
||||
```
|
||||
|
||||
### What is a snowflake?
|
||||
|
||||
129
generator.js
129
generator.js
@@ -1,129 +0,0 @@
|
||||
const big = require( './fake_node_modules/bigInt' );
|
||||
|
||||
const sleep = ( time = 1 ) => { return new Promise( res => { setTimeout( res, time ); } ); };
|
||||
const getBits = ( bits ) => { return ( 2 ** bits ) - 1; };
|
||||
|
||||
exports.lookup = ( flake = 0, epoch = 1420070400000 ) => { return new Date( ( flake / 4194304 ) + epoch ).toLocaleString(); };
|
||||
exports.generator = class Snowflake {
|
||||
constructor( options = {} ) {
|
||||
this.options = Object.assign({
|
||||
async : false,
|
||||
epoch : 1420070400000,
|
||||
workerId : 0,
|
||||
processId : 0,
|
||||
stringify : true,
|
||||
workerBits : 5,
|
||||
processBits : 5,
|
||||
incrementBits: 12
|
||||
}, options);
|
||||
|
||||
// an object containing mutable (unfrozen) properties
|
||||
this.mutable = {
|
||||
locks : [],
|
||||
locked : false,
|
||||
increment : big.zero.subtract( 1 ),
|
||||
lastTimestamp: Date.now()
|
||||
};
|
||||
|
||||
if ( this.options.incrementBits + this.options.processBits + this.options.workerBits !== 22) throw new Error( 'incrementBits, processBits, and workerBits must add up to 22.' );
|
||||
|
||||
// ensure that ids conform to the number of bits
|
||||
this.options.workerId = this.options.workerId % ( 2 ** this.options.workerBits );
|
||||
this.options.processId = this.options.processId % ( 2 ** this.options.processBits );
|
||||
|
||||
// check if NaN
|
||||
if ( isNaN( this.options.workerId ) ) this.options.workerId = 0;
|
||||
if ( isNaN( this.options.processId ) ) this.options.processId = 0;
|
||||
|
||||
// store the maximum increment bound
|
||||
this.maxIncrement = 2 ** this.options.incrementBits;
|
||||
|
||||
// calculate the shifted worker/process ids for later reference
|
||||
this.workerId = big( this.options.workerId ).shiftLeft( this.options.incrementBits + this.options.processBits );
|
||||
this.processId = big( this.options.processId ).shiftLeft( this.options.incrementBits );
|
||||
|
||||
// freeze options and this object, to prevent tampering
|
||||
Object.freeze( this.options );
|
||||
Object.freeze( this );
|
||||
}
|
||||
|
||||
get increment() { return this.mutable.increment = this.mutable.increment.next().mod( this.maxIncrement ); }
|
||||
|
||||
generate() {
|
||||
if ( this.options.async ) return this._generateAsync();
|
||||
else return this._generate();
|
||||
}
|
||||
|
||||
_generate( date, increment = null ) {
|
||||
// 0000000000000000000000000000000000000000000000000000000000000000
|
||||
// aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000000000000000000
|
||||
let flake = big( date || Date.now() ).minus( this.options.epoch ).shiftLeft( 22 )
|
||||
// aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbb00000000000000000
|
||||
.add( this.workerId )
|
||||
// aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbccccc000000000000
|
||||
.add( this.processId )
|
||||
// aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbcccccdddddddddddd
|
||||
.add( increment || this.increment );
|
||||
|
||||
if ( this.options.stringify ) flake = flake.toString();
|
||||
|
||||
return flake;
|
||||
}
|
||||
|
||||
_lock() {
|
||||
if ( this.mutable.locked ) return new Promise( res => this.mutable.locks.push( res ) );
|
||||
else this.mutable.locked = true;
|
||||
}
|
||||
|
||||
_unlock() {
|
||||
if ( this.mutable.locks.length > 0 ) this.mutable.locks.shift()();
|
||||
else this.mutable.locked = false;
|
||||
}
|
||||
|
||||
async _generateAsync() {
|
||||
let lock = this._lock();
|
||||
if( lock ) await lock;
|
||||
let now = Date.now();
|
||||
// check if increment should be reset
|
||||
if ( this.mutable.lastTimestamp !== now ) {
|
||||
// last timestamp didnt match, reset increment
|
||||
this.mutable.increment = big.zero;
|
||||
this.mutable.lastTimestamp = now;
|
||||
} else {
|
||||
// last timestamp matched, increase increment
|
||||
this.mutable.increment = this.mutable.increment.next();
|
||||
// check if increment exceeds max bounds
|
||||
if ( this.mutable.increment.greaterOrEquals( this.maxIncrement ) ) {
|
||||
// sleep for 2ms - 1ms has a risk of timestamp not incrementing for some reason?
|
||||
await sleep( 2 );
|
||||
// reset increment
|
||||
this.mutable.increment = big.zero;
|
||||
now = this.mutable.lastTimestamp = Date.now();
|
||||
}
|
||||
}
|
||||
|
||||
// generate a snowflake with the new increment
|
||||
let flake = this._generate( now, this.mutable.increment );
|
||||
this._unlock();
|
||||
return flake;
|
||||
}
|
||||
|
||||
deconstruct( snowflake ) {
|
||||
// turn snowflake into a bigint
|
||||
let flake = big( snowflake );
|
||||
// shift right, and add epoch to obtain timestamp
|
||||
let timestamp = flake.shiftRight( 22 ).add( this.options.epoch );
|
||||
|
||||
//obtain workerId
|
||||
let wBitShift = this.options.incrementBits + this.options.processBits;
|
||||
let workerId = flake.and( big( getBits( this.options.workerBits ) ).shiftLeft( wBitShift ) ).shiftRight( wBitShift );
|
||||
|
||||
// obtain processId
|
||||
let processId = flake.and( big( getBits( this.options.processBits ) ).shiftLeft( this.options.incrementBits ) ).shiftRight( this.options.incrementBits );
|
||||
|
||||
// obtain increment
|
||||
let increment = flake.and(getBits(this.options.incrementBits));
|
||||
|
||||
return { timestamp, workerId, processId, increment };
|
||||
}
|
||||
};
|
||||
68
index.d.ts
vendored
Normal file
68
index.d.ts
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
// Type definitions for Snowflakey 0.1.0
|
||||
// Project: Snowflakey
|
||||
// Definitions by: Wessel "wesselgame" T <discord@go2it.eu>
|
||||
declare module 'snowflakey' {
|
||||
import { EventEmitter } from 'events';
|
||||
|
||||
export type Snowflake = number;
|
||||
export function lookup(flake: number, epoch: number);
|
||||
|
||||
export class Master extends EventEmitter {
|
||||
public workers: SnowflakeWorker[];
|
||||
public refresh(): void;
|
||||
public listWorkers(): SnowflakeWorker[];
|
||||
public addWorkers(...workers: any[]): void;
|
||||
public removeWorkers(...workers: string[] | number[]): { removed: number };
|
||||
}
|
||||
|
||||
export class Worker extends EventEmitter {
|
||||
public options: SnowflakeConfig;
|
||||
private _mutable: SnowflakeMutable;
|
||||
constructor(options: SnowflakeConfig);
|
||||
private _lock(): void;
|
||||
private _unlock(): void;
|
||||
public generate(): Snowflake;
|
||||
public deconstruct(flake: Snowflake): DeconstructedSnowflake;
|
||||
private _generate(): Snowflake;
|
||||
private _generateAsync(): Snowflake;
|
||||
}
|
||||
|
||||
export interface DeconstructedSnowflake {
|
||||
workerId: number,
|
||||
timestamp: number,
|
||||
processId: number,
|
||||
increment: number
|
||||
}
|
||||
export interface SnowflakeConfig {
|
||||
name?: string;
|
||||
async?: boolean;
|
||||
epoch: number;
|
||||
workerId?: any,
|
||||
processId?: number,
|
||||
stringify?: boolean,
|
||||
workerBits: number,
|
||||
processBits: number,
|
||||
incrementBits: number
|
||||
}
|
||||
|
||||
export interface SnowflakeMutable {
|
||||
locks: any;
|
||||
locked: boolean;
|
||||
increment: any;
|
||||
lastTimestamp: number;
|
||||
}
|
||||
|
||||
|
||||
export interface SnowflakeWorker {
|
||||
options: SnowflakeConfig;
|
||||
workerId: number;
|
||||
_mutable: SnowflakeMutable;
|
||||
processId: number;
|
||||
_maxIncrement: number;
|
||||
_lock(): void;
|
||||
_unlock(): void;
|
||||
generate(): Snowflake;
|
||||
_generate(): Snowflake;
|
||||
_generateAsync(): Snowflake;
|
||||
}
|
||||
}
|
||||
48
lib/Master.ts
Normal file
48
lib/Master.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { EventEmitter } from 'events';
|
||||
import { SnowflakeWorker } from './types';
|
||||
|
||||
export default class SnowflakeMaster extends EventEmitter {
|
||||
public workers: any[]
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.setMaxListeners(1000);
|
||||
this.workers = [];
|
||||
}
|
||||
|
||||
addWorkers(...workers: SnowflakeWorker[]): void {
|
||||
for (const worker of workers) {
|
||||
this.workers.push(worker);
|
||||
}
|
||||
|
||||
return this.refresh();
|
||||
}
|
||||
|
||||
listWorkers(): SnowflakeWorker[] {
|
||||
return this.workers;
|
||||
}
|
||||
|
||||
removeWorkers(...identities: string[] | number[]): { 'removed': number } {
|
||||
let found = 0;
|
||||
|
||||
for (const identity of identities) {
|
||||
for (let i in this.workers) {
|
||||
const worker = this.workers[i];
|
||||
if (worker.options.name === identity || worker.options.workerId === identity) {
|
||||
found++;
|
||||
this.workers.splice(parseInt(i), 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { removed: found }
|
||||
}
|
||||
|
||||
refresh(): void {
|
||||
for (let worker of this.workers) {
|
||||
worker.on('newSnowflake', (...args) => this.emit('newSnowflake', ...args));
|
||||
worker.on('deconstructedFlake', (...args) => this.emit('deconstructedFlake', ...args));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
156
lib/Worker.ts
Normal file
156
lib/Worker.ts
Normal file
@@ -0,0 +1,156 @@
|
||||
import big from './bigInt';
|
||||
import { EventEmitter } from 'events';
|
||||
import { sleep, getBits } from './util'
|
||||
import { Snowflake, SnowflakeConfig, SnowflakeMutable } from './types';
|
||||
|
||||
export default class SnowflakeWorker extends EventEmitter {
|
||||
public options: SnowflakeConfig;
|
||||
private _mutable: SnowflakeMutable;
|
||||
private _maxIncrement: Number;
|
||||
public workerId: number;
|
||||
public processId: number;
|
||||
|
||||
constructor(options: SnowflakeConfig) {
|
||||
super();
|
||||
// The default options for the generator
|
||||
this.setMaxListeners(100);
|
||||
this.options = {
|
||||
name: undefined,
|
||||
async: false,
|
||||
epoch: null,
|
||||
workerId: 0,
|
||||
processId: 0,
|
||||
stringify: true,
|
||||
workerBits: 5,
|
||||
processBits: 5,
|
||||
incrementBits: 12,
|
||||
...options
|
||||
};
|
||||
|
||||
// an object containing mutable (unfrozen) properties
|
||||
this._mutable = {
|
||||
locks: [],
|
||||
locked: false,
|
||||
increment: big.zero.subtract(1),
|
||||
lastTimestamp: Date.now()
|
||||
};
|
||||
|
||||
if (this.options.incrementBits + this.options.processBits + this.options.workerBits !== 22) throw new Error('incrementBits, processBits, and workerBits must add up to 22.');
|
||||
// ensure that ids conform to the number of bits
|
||||
this.options.workerId = this.options.workerId % (2 ** this.options.workerBits);
|
||||
this.options.processId = this.options.processId % (2 ** this.options.processBits);
|
||||
|
||||
// check if NaN
|
||||
if (isNaN(this.options.workerId)) this.options.workerId = 0;
|
||||
if (isNaN(this.options.processId)) this.options.processId = 0;
|
||||
|
||||
// store the maximum increment bound
|
||||
this._maxIncrement = 2 ** this.options.incrementBits;
|
||||
|
||||
// calculate the shifted worker/process ids for later reference
|
||||
this.workerId = big(this.options.workerId).shiftLeft(this.options.incrementBits + this.options.processBits);
|
||||
this.processId = big(this.options.processId).shiftLeft(this.options.incrementBits);
|
||||
|
||||
// freeze immutable objects to prevent tampering
|
||||
Object.freeze(this.options);
|
||||
// Object.freeze(this);
|
||||
}
|
||||
|
||||
get increment(): number {
|
||||
return this._mutable.increment = this._mutable.increment.next().mod(this._maxIncrement);
|
||||
}
|
||||
|
||||
generate(): Snowflake | Promise<Snowflake> {
|
||||
if (this.options.async) return this._generateAsync();
|
||||
else return this._generate();
|
||||
}
|
||||
|
||||
_generate(date: number = Date.now(), increment: number = this.increment): Snowflake {
|
||||
// 0000000000000000000000000000000000000000000000000000000000000000
|
||||
// aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000000000000000000
|
||||
let flake: any = big(date).minus(this.options.epoch).shiftLeft(22)
|
||||
// aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbb00000000000000000
|
||||
.add(this.workerId)
|
||||
// aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbccccc000000000000
|
||||
.add(this.processId)
|
||||
// aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbcccccdddddddddddd
|
||||
.add(increment);
|
||||
|
||||
this.emit('newSnowflake', {
|
||||
worker: this,
|
||||
method: 'sync',
|
||||
snowflake: flake.toString(),
|
||||
});
|
||||
|
||||
if (this.options.stringify) flake = flake.toString();
|
||||
return flake;
|
||||
}
|
||||
|
||||
_lock() {
|
||||
if (this._mutable.locked) return new Promise(res => this._mutable.locks.push(res));
|
||||
else this._mutable.locked = true;
|
||||
}
|
||||
|
||||
_unlock(): void {
|
||||
if (this._mutable.locks.length > 0) this._mutable.locks.shift()();
|
||||
else this._mutable.locked = false;
|
||||
}
|
||||
|
||||
async _generateAsync(): Promise<Snowflake> {
|
||||
let lock = this._lock();
|
||||
if (lock) await lock;
|
||||
let now = Date.now();
|
||||
// check if increment should be reset
|
||||
if (this._mutable.lastTimestamp !== now) {
|
||||
// last timestamp didnt match, reset increment
|
||||
this._mutable.increment = big.zero;
|
||||
this._mutable.lastTimestamp = now;
|
||||
} else {
|
||||
// last timestamp matched, increase increment
|
||||
this._mutable.increment = this._mutable.increment.next();
|
||||
// check if increment exceeds max bounds
|
||||
if (this._mutable.increment.greaterOrEquals(this._maxIncrement)) {
|
||||
// sleep for 2ms - 1ms has a risk of timestamp not incrementing for some reason?
|
||||
await sleep(2 / 1000);
|
||||
// reset increment
|
||||
this._mutable.increment = big.zero;
|
||||
now = this._mutable.lastTimestamp = Date.now();
|
||||
}
|
||||
}
|
||||
|
||||
// generate a snowflake with the new increment
|
||||
let flake: Snowflake = this._generate(now, this._mutable.increment);
|
||||
this._unlock();
|
||||
this.emit('newSnowflake', {
|
||||
worker: this,
|
||||
method: 'async',
|
||||
snowflake: flake.toString(),
|
||||
});
|
||||
|
||||
return flake;
|
||||
}
|
||||
|
||||
deconstruct(snowflake: Snowflake): object {
|
||||
// turn snowflake into a bigint
|
||||
let flake = big(snowflake);
|
||||
// shift right, and add epoch to obtain timestamp
|
||||
let timestamp = flake.shiftRight(22).add(this.options.epoch);
|
||||
|
||||
//obtain workerId
|
||||
let wBitShift = this.options.incrementBits + this.options.processBits;
|
||||
let workerId = flake.and(big(getBits(this.options.workerBits)).shiftLeft(wBitShift)).shiftRight(wBitShift);
|
||||
|
||||
// obtain processId
|
||||
let processId = flake.and(big(getBits(this.options.processBits)).shiftLeft(this.options.incrementBits)).shiftRight(this.options.incrementBits);
|
||||
|
||||
// obtain increment
|
||||
let increment = flake.and(getBits(this.options.incrementBits));
|
||||
|
||||
this.emit('deconstructedFlake', {
|
||||
worker: this,
|
||||
method: 'sync',
|
||||
timestamp, workerId, processId, increment
|
||||
});
|
||||
return { timestamp, workerId, processId, increment };
|
||||
}
|
||||
};
|
||||
6
lib/snowflakey.ts
Normal file
6
lib/snowflakey.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export const Master = require('./Master').default;
|
||||
export const Worker = require('./Worker').default;
|
||||
export const lookup = (flake: number, epoch: number): string => {
|
||||
return new Date((flake / 4194304) + epoch).toLocaleString();
|
||||
};
|
||||
|
||||
33
lib/types.ts
Normal file
33
lib/types.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
export interface SnowflakeConfig {
|
||||
name?: string;
|
||||
async?: boolean;
|
||||
epoch?: number;
|
||||
workerId?: any,
|
||||
processId?: number,
|
||||
stringify?: boolean,
|
||||
workerBits: number,
|
||||
processBits: number,
|
||||
incrementBits: number
|
||||
}
|
||||
|
||||
export interface SnowflakeMutable {
|
||||
locks: any;
|
||||
locked: boolean;
|
||||
increment: any;
|
||||
lastTimestamp: number;
|
||||
}
|
||||
|
||||
export type Snowflake = number;
|
||||
|
||||
export interface SnowflakeWorker {
|
||||
options: SnowflakeConfig;
|
||||
workerId: number;
|
||||
_mutable: SnowflakeMutable;
|
||||
processId: number;
|
||||
_maxIncrement: number;
|
||||
_lock(): void;
|
||||
_unlock(): void;
|
||||
generate(): Snowflake;
|
||||
_generate(): Snowflake;
|
||||
_generateAsync(): Snowflake;
|
||||
}
|
||||
18
lib/util.ts
Normal file
18
lib/util.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Halt the event loop for `duration` seconds
|
||||
*
|
||||
* @param {number} [duration=1] - The duration in seconds to wait for
|
||||
*/
|
||||
export const sleep = (duration: number = 1): void => {
|
||||
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, (duration * 1000));
|
||||
};
|
||||
|
||||
/**
|
||||
* Get all bits from `bits`
|
||||
*
|
||||
* @param {number} bits - The bits to get bits from
|
||||
* @returns {number} - The found bits
|
||||
*/
|
||||
export const getBits = (bits: number): number => {
|
||||
return (2 ** bits) - 1;
|
||||
};
|
||||
23
package.json
23
package.json
@@ -1,14 +1,15 @@
|
||||
{
|
||||
"name": "snowflakey",
|
||||
"version": "0.0.2",
|
||||
"description": "❄️ Snowflake generation/lookup",
|
||||
"version": "0.1.1",
|
||||
"description": "❄️ Fast and easy snowflake generation and lookup",
|
||||
"author": "Wessel \"wesselgame\" T <discord@go2it.eu>",
|
||||
"main": "generator.js",
|
||||
"main": "dist/lib/snowflakey.js",
|
||||
"types": "index.d.ts",
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
"generator.js",
|
||||
"LICENSE",
|
||||
"fake_node_modules/"
|
||||
"dist/lib",
|
||||
"index.d.ts"
|
||||
],
|
||||
"bugs": {
|
||||
"url": "https://www.github.com/PassTheWessel/Snowflakey/issues"
|
||||
@@ -21,9 +22,17 @@
|
||||
"keywords": [
|
||||
"snowflake",
|
||||
"generator",
|
||||
"cli",
|
||||
"accounts",
|
||||
"lookup",
|
||||
"lightweight"
|
||||
]
|
||||
],
|
||||
"scripts": {
|
||||
"test": "cd ./dist/tests && node main.test.js",
|
||||
"compile": "tsc"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^12.6.8",
|
||||
"@typescript-eslint/parser": "^1.13.0"
|
||||
},
|
||||
"dependencies": {}
|
||||
}
|
||||
|
||||
18
test.js
18
test.js
@@ -1,18 +0,0 @@
|
||||
// require & generate the instance
|
||||
const Snowflake = require( './generator' );
|
||||
const snowflake = new Snowflake.generator({
|
||||
processBits: 0,
|
||||
workerBits: 8,
|
||||
incrementBits: 14,
|
||||
workerId: process.env.CLUSTER_ID || 31
|
||||
});
|
||||
|
||||
// exports for global use
|
||||
exports.makeSnowflake = ( date ) => { return snowflake._generate( date ); };
|
||||
exports.unmakeSnowflake = ( flake ) => { let decon = snowflake.deconstruct( flake ); return decon.timestamp.valueOf(); };
|
||||
|
||||
// example
|
||||
const flake = this.makeSnowflake( Date.now() );
|
||||
console.log( flake );
|
||||
console.log( `Creation date: ${Snowflake.lookup( flake, 1420070400000 )}` );
|
||||
console.log( this.unmakeSnowflake( flake ) );
|
||||
40
tests/main.test.ts
Normal file
40
tests/main.test.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
// require & generate the instance
|
||||
import * as Snowflake from '../lib/snowflakey';
|
||||
|
||||
const master = new Snowflake.Master();
|
||||
const worker = new Snowflake.Worker({
|
||||
name: 'starling',
|
||||
epoch: 1420070400000,
|
||||
workerId: process.env.CLUSTER_ID || 31,
|
||||
processId: process.pid || undefined,
|
||||
workerBits: 8,
|
||||
processBits: 0,
|
||||
incrementBits: 14
|
||||
});
|
||||
|
||||
master.addWorkers(worker);
|
||||
|
||||
// Using the worker directly
|
||||
const flake = worker.generate();
|
||||
const epoch = 1420070400000;
|
||||
|
||||
console.log('----------[ Worker ]----------')
|
||||
console.log(`Created snowflake: ${flake}`);
|
||||
console.log(`Creation date : ${Snowflake.lookup(flake, worker.options.epoch)}`);
|
||||
console.log(`Deconstructed : ${worker.deconstruct(flake).timestamp.valueOf()}`);
|
||||
// Using the master to get events
|
||||
console.log('----------[ Master ]----------')
|
||||
master.on('newSnowflake', (data) => {
|
||||
console.log(`created snowflake: ${data.snowflake} by Worker ${data.worker.options.name || data.worker.options.workerId}`)
|
||||
console.log(`Creation date : ${Snowflake.lookup(flake, data.worker.options.epoch)}`);
|
||||
data.worker.deconstruct(data.snowflake);
|
||||
});
|
||||
|
||||
master.on('deconstructedFlake', (data) => {
|
||||
console.log(`Deconstructed : ${data.timestamp.valueOf()} by Worker ${data.worker.options.name || data.worker.options.workerId}`);
|
||||
});
|
||||
|
||||
worker.generate();
|
||||
|
||||
master.removeWorkers(worker.options.name);
|
||||
console.log(master.listWorkers())
|
||||
23
tsconfig.json
Normal file
23
tsconfig.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": [ "es2015", "es2016", "es2017", "esnext" ],
|
||||
"watch": true,
|
||||
"types": [ "node" ],
|
||||
"outDir": "./dist",
|
||||
"target": "esnext",
|
||||
"module": "commonjs",
|
||||
"strict": false,
|
||||
"allowJs": true,
|
||||
"rootDirs": [ "./lib", "./tests" ],
|
||||
"declaration": false,
|
||||
"noImplicitAny": false,
|
||||
"removeComments": true,
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": [ "lib/**/*", "tests/**/*" ],
|
||||
"exclude": [ "node_modules" ]
|
||||
}
|
||||
80
yarn.lock
Normal file
80
yarn.lock
Normal file
@@ -0,0 +1,80 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@types/eslint-visitor-keys@^1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d"
|
||||
integrity sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==
|
||||
|
||||
"@types/json-schema@^7.0.3":
|
||||
version "7.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.3.tgz#bdfd69d61e464dcc81b25159c270d75a73c1a636"
|
||||
integrity sha512-Il2DtDVRGDcqjDtE+rF8iqg1CArehSK84HZJCT7AMITlyXRBpuPhqGLDQMowraqqu1coEaimg4ZOqggt6L6L+A==
|
||||
|
||||
"@types/node@^12.6.8":
|
||||
version "12.6.8"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.6.8.tgz#e469b4bf9d1c9832aee4907ba8a051494357c12c"
|
||||
integrity sha512-aX+gFgA5GHcDi89KG5keey2zf0WfZk/HAQotEamsK2kbey+8yGKcson0hbK8E+v0NArlCJQCqMP161YhV6ZXLg==
|
||||
|
||||
"@typescript-eslint/experimental-utils@1.13.0":
|
||||
version "1.13.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-1.13.0.tgz#b08c60d780c0067de2fb44b04b432f540138301e"
|
||||
integrity sha512-zmpS6SyqG4ZF64ffaJ6uah6tWWWgZ8m+c54XXgwFtUv0jNz8aJAVx8chMCvnk7yl6xwn8d+d96+tWp7fXzTuDg==
|
||||
dependencies:
|
||||
"@types/json-schema" "^7.0.3"
|
||||
"@typescript-eslint/typescript-estree" "1.13.0"
|
||||
eslint-scope "^4.0.0"
|
||||
|
||||
"@typescript-eslint/parser@^1.13.0":
|
||||
version "1.13.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-1.13.0.tgz#61ac7811ea52791c47dc9fd4dd4a184fae9ac355"
|
||||
integrity sha512-ITMBs52PCPgLb2nGPoeT4iU3HdQZHcPaZVw+7CsFagRJHUhyeTgorEwHXhFf3e7Evzi8oujKNpHc8TONth8AdQ==
|
||||
dependencies:
|
||||
"@types/eslint-visitor-keys" "^1.0.0"
|
||||
"@typescript-eslint/experimental-utils" "1.13.0"
|
||||
"@typescript-eslint/typescript-estree" "1.13.0"
|
||||
eslint-visitor-keys "^1.0.0"
|
||||
|
||||
"@typescript-eslint/typescript-estree@1.13.0":
|
||||
version "1.13.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-1.13.0.tgz#8140f17d0f60c03619798f1d628b8434913dc32e"
|
||||
integrity sha512-b5rCmd2e6DCC6tCTN9GSUAuxdYwCM/k/2wdjHGrIRGPSJotWMCe/dGpi66u42bhuh8q3QBzqM4TMA1GUUCJvdw==
|
||||
dependencies:
|
||||
lodash.unescape "4.0.1"
|
||||
semver "5.5.0"
|
||||
|
||||
eslint-scope@^4.0.0:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848"
|
||||
integrity sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==
|
||||
dependencies:
|
||||
esrecurse "^4.1.0"
|
||||
estraverse "^4.1.1"
|
||||
|
||||
eslint-visitor-keys@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d"
|
||||
integrity sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==
|
||||
|
||||
esrecurse@^4.1.0:
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.1.tgz#007a3b9fdbc2b3bb87e4879ea19c92fdbd3942cf"
|
||||
integrity sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==
|
||||
dependencies:
|
||||
estraverse "^4.1.0"
|
||||
|
||||
estraverse@^4.1.0, estraverse@^4.1.1:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13"
|
||||
integrity sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=
|
||||
|
||||
lodash.unescape@4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.unescape/-/lodash.unescape-4.0.1.tgz#bf2249886ce514cda112fae9218cdc065211fc9c"
|
||||
integrity sha1-vyJJiGzlFM2hEvrpIYzcBlIR/Jw=
|
||||
|
||||
semver@5.5.0:
|
||||
version "5.5.0"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab"
|
||||
integrity sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==
|
||||
Reference in New Issue
Block a user