├── .eslintrc.json ├── .gitignore ├── .travis.yml ├── LICENSE.txt ├── README.md ├── index.js ├── package.json └── test └── test.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": "@nwoltman/eslint-config", 4 | "rules": { 5 | "consistent-return": "off" 6 | }, 7 | "overrides": [{ 8 | "files": ["test/test.js"], 9 | "env": { 10 | "mocha": true 11 | }, 12 | "rules": { 13 | "max-len": "off", 14 | "max-nested-callbacks": "off", 15 | "no-sync": "off", 16 | "padded-blocks": "off", 17 | "prefer-arrow-callback": "off" 18 | } 19 | }] 20 | } 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Coverage directory used by tools like istanbul 7 | coverage 8 | 9 | # nyc test coverage 10 | .nyc_output 11 | 12 | # Dependency directories 13 | node_modules 14 | 15 | # Mac files 16 | .DS_Store 17 | 18 | # vim swap files 19 | *.swp 20 | 21 | # Sublime Text 22 | *.sublime-* 23 | 24 | # Webstorm 25 | .idea 26 | 27 | # VS Code 28 | .vscode 29 | *code-workspace 30 | 31 | # Flamegraphs 32 | profile* 33 | 34 | # Lock files 35 | yarn.lock 36 | package-lock.json 37 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 13 4 | - 12 5 | - 10 6 | after_success: npm run coveralls 7 | stages: 8 | - test 9 | - name: npm release 10 | if: tag IS present 11 | jobs: 12 | include: 13 | - stage: npm release 14 | install: skip 15 | script: skip 16 | after_success: true # skip step with success exit code 17 | deploy: 18 | provider: npm 19 | email: nwoltman@outlook.com 20 | on: 21 | tags: true 22 | api_key: 23 | secure: Xflnpr/yCM0nEeYINGGDrGUzWdVg1wnmh9BNtUCAutzZYJ2OgoIirZEd5P3ANuKbnuDbFbF8ynWX1tvJTxETjQp1Pgov54m9Yb+cHj/C1jaEhGODL3vmxa1Rc1xpmb5p7YXcO3Yy/E93lgymZtV0U33ChLnMjNyaz+/4WtkeRgYlXjS88VT5n2w1w2QP3AJpOw6JyyRoJR3hE2yIpYqlND7D8JcKHRHJKipnZY4lmqPh4OSW6tA2tMG4f1nfzdAQFfZPcSZthW3U5M0wd9Otd9le4xOwYgBhax3QBTD5ksL+53GqsBpEgh2wh8d92LSdUOu9s1PffGF8i8014xI+GJTCZvN6Li3VBcqZEsGxasdQs5ntdDQdufxfKb4Xr5v2u3hLi3QkpTQD4bHy/ix7bud6yzZMTAt9VNe4zUT/Pde+OUSKFN8d8JIpUqqcUKFwGd7v19BMf3T+zEI8VGzvrjJRtRPdu7A5TS717yknSO2lIAWOPyDTCMBXMUBKda4ZtVrT4ixZ+fc328vvYLfoo3JH77vKf8nSt/MF5r8ugoQnUNa3pN2SzzvKG2CYuOso8JRJsvQA3FVx6eT+UMooWVgGYvHED2BPh4XSE59uefdzsiuSNdYLgm+Zau8PGff8eAa4Xxhtu/UZQsaIG10zI3kL9ebn51Z90P0fWeUDOWY= 24 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016-present Nathan Woltman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # uid-generator 2 | 3 | [![NPM Version](https://img.shields.io/npm/v/uid-generator.svg)](https://www.npmjs.com/package/uid-generator) 4 | [![Build Status](https://travis-ci.org/nwoltman/node-uid-generator.svg?branch=master)](https://travis-ci.org/nwoltman/node-uid-generator) 5 | [![Coverage Status](https://coveralls.io/repos/github/nwoltman/node-uid-generator/badge.svg?branch=master)](https://coveralls.io/github/nwoltman/node-uid-generator?branch=master) 6 | [![devDependency Status](https://david-dm.org/nwoltman/node-uid-generator/dev-status.svg)](https://david-dm.org/nwoltman/node-uid-generator?type=dev) 7 | 8 | Generates cryptographically strong, pseudo-random UIDs with custom size and base-encoding. Generated UIDs are strings that are guaranteed to always be the same length depending on the specified [bit-size](#api). 9 | 10 | Great for generating things like compact API keys and UIDs that are safe to use in URLs and cookies. 11 | 12 | **Tip:** The main benefit of this module is the ability to easily generate human-safe UIDs (using [base58](#uidgeneratorbase58--string)) and large UIDs that are more compact (using something like [base94](#uidgeneratorbase94--string)). If you’re just looking to generate URL-safe base64 IDs, the best package for that is [`uid-safe`](https://github.com/crypto-utils/uid-safe). 13 | 14 | 15 | ## Installation 16 | 17 | ```sh 18 | npm install uid-generator 19 | # or 20 | yarn add uid-generator 21 | ``` 22 | 23 | 24 | ## Usage 25 | 26 | ```js 27 | const UIDGenerator = require('uid-generator'); 28 | const uidgen = new UIDGenerator(); // Default is a 128-bit UID encoded in base58 29 | 30 | // Async with `await` 31 | await uidgen.generate(); // -> 'B1q2hUEKmeVp9zWepx9cnp' 32 | 33 | // Async with promise 34 | uidgen.generate() 35 | .then(uid => console.log(uid)); // -> 'PXmRJVrtzFAHsxjs7voD5R' 36 | 37 | // Async with callback 38 | uidgen.generate((err, uid) => { 39 | if (err) throw err; 40 | console.log(uid); // -> '4QhmRwHwwrgFqXULXNtx4d' 41 | }); 42 | 43 | // Sync 44 | uidgen.generateSync(); // -> '8Vw3bgbMMzeYfrQHQ8p3Jr' 45 | ``` 46 | 47 | 48 | ## API 49 | 50 | ### new UIDGenerator([bitSize][, baseEncoding]) 51 | 52 | Creates a new `UIDGenerator` instance that generates `bitSize`-bit or `uidLength`-sized UIDs encoded using the characters in `baseEncoding`. 53 | 54 | | Param | Type | Default | Description 55 | |----------------|--------- |-----------------------|------------- 56 | | [bitSize] | `number` | `128` | The size of the UID to generate in bits. Must be a multiple of `8`. 57 | | [baseEncoding] | `string` | `UIDGenerator.BASE58` | One of the `UIDGenerator.BASE##` constants or a custom string of characters to use to encode the UID. 58 | 59 | **Note:** If a custom `baseEncoding` that has URL-unsafe characters is used, it is up to you to URL-encode the resulting UID. 60 | 61 | **Example** 62 | 63 | ```js 64 | new UIDGenerator(); 65 | new UIDGenerator(256); 66 | new UIDGenerator(UIDGenerator.BASE16); 67 | new UIDGenerator(512, UIDGenerator.BASE62); 68 | new UIDGenerator('01'); // Custom encoding (base2) 69 | ``` 70 | 71 | --- 72 | 73 | ### uidgen.generate([cb]) ⇒ `?Promise` 74 | 75 | Asynchronously generates a UID. 76 | 77 | | Param | Type | Description | 78 | |-------|------|-------------| 79 | | [cb] | `?function(error, uid)` | An optional callback that will be called with the results of generating the UID.
If not specified, the function will return a promise. | 80 | 81 | **Returns**: `?Promise` - A promise that will resolve with the UID or reject with an error. Returns nothing if the `cb` parameter is specified. 82 | 83 | **`async`/`await` Example** 84 | 85 | ```js 86 | const uidgen = new UIDGenerator(); 87 | // This must be inside an async function 88 | const uid = await uidgen.generate(); 89 | ``` 90 | 91 | **Promise Example** 92 | 93 | ```js 94 | const uidgen = new UIDGenerator(); 95 | 96 | uidgen.generate() 97 | .then(uid => { 98 | // Use uid here 99 | }); 100 | ``` 101 | 102 | **Callback Example** 103 | 104 | ```js 105 | const uidgen = new UIDGenerator(); 106 | 107 | uidgen.generate((err, uid) => { 108 | if (err) throw err; 109 | // Use uid here 110 | }); 111 | ``` 112 | 113 | --- 114 | 115 | ### uidgen.generateSync() ⇒ `string` 116 | 117 | Synchronously generates a UID. 118 | 119 | **Returns**: `string` - The generated UID. 120 | 121 | **Example** 122 | 123 | ```js 124 | const uidgen = new UIDGenerator(); 125 | const uid = uidgen.generateSync(); 126 | ``` 127 | 128 | --- 129 | 130 | ### (readonly) uidgen.bitSize : `number` 131 | 132 | The size of the UID that will be generated in bits (the `bitSize` value passed to the `UIDGenerator` constructor). 133 | If the `uidLength` parameter is passed to the constructor instead of `bitSize`, `bitSize` is calculated as follows: 134 | 135 | ```js 136 | bitSize = Math.ceil(length * Math.log2(base)); 137 | ``` 138 | 139 | **Example** 140 | 141 | ```js 142 | new UIDGenerator().bitSize // -> 128 143 | new UIDGenerator(256).bitSize // -> 256 144 | ``` 145 | 146 | --- 147 | 148 | ### (readonly) uidgen.uidLength : `number` 149 | 150 | The length of the UID string that will be generated. The generated UID will always be this length. 151 | This will be the same as the `uidLength` parameter passed to the `UIDGenerator` constructor. 152 | If the `uidLength` parameter is not passed to the constructor, it will be calculated using the `bitSize` parameter as follows: 153 | 154 | ```js 155 | uidLength = Math.ceil(bitSize / Math.log2(base)) 156 | ``` 157 | 158 | **Example** 159 | 160 | ```js 161 | new UIDGenerator().uidLength // -> 22 162 | new UIDGenerator(256, UIDGenerator.BASE62).uidLength // -> 43 163 | ``` 164 | 165 | --- 166 | 167 | ### (readonly) uidgen.baseEncoding : `string` 168 | 169 | The set of characters used to encode the UID string (the `baseEncoding` value passed to the `UIDGenerator` constructor). 170 | 171 | **Example** 172 | 173 | ```js 174 | new UIDGenerator().baseEncoding // -> '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' 175 | new UIDGenerator(UIDGenerator.BASE16).baseEncoding // -> '0123456789abcdef' 176 | new UIDGenerator('01').baseEncoding // -> '01' 177 | ``` 178 | 179 | --- 180 | 181 | ### (readonly) uidgen.base : `number` 182 | 183 | The base of the UID that will be generated (which is the number of characters in the `baseEncoding`). 184 | 185 | **Example** 186 | 187 | ```js 188 | new UIDGenerator().base // -> 58 189 | new UIDGenerator(UIDGenerator.BASE16).base // -> 16 190 | new UIDGenerator('01').base // -> 2 191 | ``` 192 | 193 | --- 194 | 195 | ### UIDGenerator.BASE16 : `string` 196 | `0123456789abcdef` 197 | 198 | ### UIDGenerator.BASE36 : `string` 199 | `0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ` 200 | 201 | ### UIDGenerator.BASE58 : `string` 202 | `123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz` 203 | 204 | (all alphanumeric characters except for `0`, `O`, `I`, and `l` — characters easily mistaken for each other) 205 | 206 | The default base. 207 | 208 | **Tip:** Use this base to create UIDs that are easy to type in manually. 209 | 210 | ### UIDGenerator.BASE62 : `string` 211 | `0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz` 212 | 213 | ### UIDGenerator.BASE66 : `string` 214 | `0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-._~` 215 | 216 | (all ASCII characters that do not need to be encoded in a URI as specified by [RFC 3986](https://tools.ietf.org/html/rfc3986#section-2.3)) 217 | 218 | ### UIDGenerator.BASE71 : `string` 219 | `0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!'()*-._~` 220 | 221 | (all ASCII characters that are not encoded by `encodeURIComponent()`) 222 | 223 | ### UIDGenerator.BASE94 : `string` 224 | ``!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~`` 225 | 226 | (all readable ASCII characters) 227 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const crypto = require('crypto'); 4 | 5 | class UIDGenerator { 6 | constructor(bitSize, baseEncoding) { 7 | if (bitSize === undefined) { 8 | bitSize = 128; 9 | } else if (typeof bitSize === 'string') { 10 | baseEncoding = bitSize; 11 | bitSize = 128; 12 | } else if (!Number.isInteger(bitSize) || bitSize <= 0 || bitSize % 8 !== 0) { 13 | throw new TypeError('bitSize must be a positive integer that is a multiple of 8'); 14 | } 15 | 16 | if (baseEncoding === undefined) { 17 | baseEncoding = UIDGenerator.BASE58; 18 | } else { 19 | validateBaseEncoding(baseEncoding); 20 | } 21 | 22 | this.bitSize = bitSize; 23 | this.baseEncoding = baseEncoding; 24 | this.base = baseEncoding.length; 25 | this.uidLength = Math.ceil(bitSize / Math.log2(baseEncoding.length)); 26 | 27 | this._byteSize = bitSize / 8; 28 | } 29 | 30 | generate(cb) { 31 | if (!cb) { 32 | return new Promise((resolve, reject) => { 33 | crypto.randomBytes(this._byteSize, (err, buffer) => { 34 | if (err) { 35 | reject(err); 36 | } else { 37 | resolve(bufferToString(buffer, this.baseEncoding, this.uidLength)); 38 | } 39 | }); 40 | }); 41 | } 42 | 43 | crypto.randomBytes(this._byteSize, (err, buffer) => { 44 | if (err) { 45 | cb(err); 46 | } else { 47 | cb(null, bufferToString(buffer, this.baseEncoding, this.uidLength)); 48 | } 49 | }); 50 | } 51 | 52 | generateSync() { 53 | const buffer = crypto.randomBytes(this._byteSize); 54 | return bufferToString(buffer, this.baseEncoding, this.uidLength); 55 | } 56 | } 57 | 58 | // Encoding algorithm based on the encode function in Daniel Cousens' base-x package 59 | // https://github.com/cryptocoinjs/base-x/blob/master/index.js 60 | function bufferToString(buffer, baseEncoding, uidLength) { 61 | const base = baseEncoding.length; 62 | const digits = [0]; 63 | var i; 64 | var j; 65 | var carry; 66 | 67 | for (i = 0; i < buffer.length; ++i) { 68 | carry = buffer[i]; 69 | 70 | for (j = 0; j < digits.length; ++j) { 71 | carry += digits[j] << 8; 72 | digits[j] = carry % base; 73 | carry = (carry / base) | 0; 74 | } 75 | 76 | while (carry > 0) { 77 | digits.push(carry % base); 78 | carry = (carry / base) | 0; 79 | } 80 | } 81 | 82 | // Convert digits to a string 83 | var str = digits.length < uidLength 84 | ? baseEncoding[0].repeat(uidLength - digits.length) // Handle leading zeros 85 | : ''; 86 | 87 | for (i = digits.length - 1; i >= 0; --i) { 88 | str += baseEncoding[digits[i]]; 89 | } 90 | 91 | return str; 92 | } 93 | 94 | function validateBaseEncoding(baseEncoding) { 95 | if (typeof baseEncoding !== 'string') { 96 | throw new TypeError('baseEncoding must be a string'); 97 | } 98 | 99 | switch (baseEncoding) { 100 | case UIDGenerator.BASE16: 101 | case UIDGenerator.BASE36: 102 | case UIDGenerator.BASE58: 103 | case UIDGenerator.BASE62: 104 | case UIDGenerator.BASE66: 105 | case UIDGenerator.BASE71: 106 | case UIDGenerator.BASE94: 107 | return; 108 | } 109 | 110 | if (baseEncoding.length < 2) { 111 | throw new Error('baseEncoding must have 2 or more characters'); 112 | } 113 | 114 | for (var i = 0; i < baseEncoding.length - 1; i++) { 115 | if (baseEncoding.indexOf(baseEncoding[i], i + 1) >= 0) { 116 | throw new Error( 117 | `Invalid baseEncoding due to duplicated character: '${baseEncoding[i]}'` 118 | ); 119 | } 120 | } 121 | } 122 | 123 | UIDGenerator.BASE16 = '0123456789abcdef'; 124 | UIDGenerator.BASE36 = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'; 125 | UIDGenerator.BASE58 = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; 126 | UIDGenerator.BASE62 = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; 127 | UIDGenerator.BASE66 = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-._~'; 128 | UIDGenerator.BASE71 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!'()*-._~"; 129 | UIDGenerator.BASE94 = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; // eslint-disable-line max-len 130 | 131 | module.exports = UIDGenerator; 132 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "uid-generator", 3 | "version": "2.0.0", 4 | "description": "Generates cryptographically strong pseudo-random UIDs with custom size and base-encoding", 5 | "author": "Nathan Woltman ", 6 | "license": "MIT", 7 | "main": "index.js", 8 | "files": [ 9 | "index.js" 10 | ], 11 | "engines": { 12 | "node": ">=4" 13 | }, 14 | "repository": "github:nwoltman/node-uid-generator", 15 | "homepage": "https://github.com/nwoltman/node-uid-generator", 16 | "bugs": "https://github.com/nwoltman/node-uid-generator/issues", 17 | "keywords": [ 18 | "uid", 19 | "generator", 20 | "random", 21 | "token", 22 | "unique", 23 | "crypto", 24 | "strong", 25 | "base", 26 | "encoding" 27 | ], 28 | "nyc": { 29 | "reporter": [ 30 | "html", 31 | "text-summary" 32 | ], 33 | "check-coverage": true, 34 | "branches": 100, 35 | "lines": 100, 36 | "statements": 100 37 | }, 38 | "devDependencies": { 39 | "@nwoltman/eslint-config": "~0.5.1", 40 | "coveralls": "^3.0.7", 41 | "eslint": "^6.6.0", 42 | "mocha": "^6.2.2", 43 | "nyc": "^14.1.1", 44 | "should": "^13.2.3" 45 | }, 46 | "scripts": { 47 | "lint": "eslint index.js test/*.js", 48 | "test": "eslint index.js test/*.js && nyc mocha", 49 | "coveralls": "nyc report --reporter=text-lcov | coveralls" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const UIDGenerator = require('../index'); 4 | 5 | const crypto = require('crypto'); 6 | const should = require('should'); 7 | 8 | describe('UIDGenerator', () => { 9 | 10 | describe('new UIDGenerator(bitSize, baseEncoding)', () => { 11 | 12 | it('accepts 0 parameters', () => { 13 | const uidgen = new UIDGenerator(); 14 | 15 | uidgen.should.be.an.instanceOf(UIDGenerator); 16 | uidgen.bitSize.should.be.exactly(128); 17 | uidgen.baseEncoding.should.be.exactly(UIDGenerator.BASE58); 18 | uidgen.uidLength.should.be.exactly(22); 19 | uidgen.base.should.be.exactly(58); 20 | }); 21 | 22 | it('accepts just the bitSize parameter', () => { 23 | const uidgen = new UIDGenerator(256); 24 | 25 | uidgen.bitSize.should.be.exactly(256); 26 | uidgen.baseEncoding.should.be.exactly(UIDGenerator.BASE58); 27 | uidgen.uidLength.should.be.exactly(44); 28 | uidgen.base.should.be.exactly(58); 29 | }); 30 | 31 | it('accepts just the baseEncoding parameter', () => { 32 | const uidgen = new UIDGenerator(UIDGenerator.BASE16); 33 | 34 | uidgen.bitSize.should.be.exactly(128); 35 | uidgen.baseEncoding.should.be.exactly(UIDGenerator.BASE16); 36 | uidgen.uidLength.should.be.exactly(32); 37 | uidgen.base.should.be.exactly(16); 38 | }); 39 | 40 | it('accepts the bitSize and baseEncoding parameters', () => { 41 | const uidgen = new UIDGenerator(512, UIDGenerator.BASE62); 42 | 43 | uidgen.bitSize.should.be.exactly(512); 44 | uidgen.baseEncoding.should.be.exactly(UIDGenerator.BASE62); 45 | uidgen.uidLength.should.be.exactly(86); 46 | uidgen.base.should.be.exactly(62); 47 | 48 | new UIDGenerator(undefined, UIDGenerator.BASE16) 49 | .should.deepEqual(new UIDGenerator(UIDGenerator.BASE16)); 50 | }); 51 | 52 | it('accepts a custom baseEncoding', () => { 53 | new UIDGenerator('123abc').baseEncoding.should.be.exactly('123abc'); 54 | new UIDGenerator(512, 'abc123').baseEncoding.should.be.exactly('abc123'); 55 | }); 56 | 57 | it('throws if bitSize is not a positive integer that is a multiple of 8', () => { 58 | should.throws(() => new UIDGenerator(0), TypeError); 59 | should.throws(() => new UIDGenerator(1), TypeError); 60 | should.throws(() => new UIDGenerator(-8), TypeError); 61 | should.throws(() => new UIDGenerator(-128), TypeError); 62 | should.throws(() => new UIDGenerator(127), TypeError); 63 | should.throws(() => new UIDGenerator(1.5), TypeError); 64 | should.throws(() => new UIDGenerator(Math.PI), TypeError); 65 | should.throws(() => new UIDGenerator(Infinity), TypeError); 66 | should.throws(() => new UIDGenerator(null), TypeError); 67 | should.throws(() => new UIDGenerator(true), TypeError); 68 | should.throws(() => new UIDGenerator({}), TypeError); 69 | should.throws(() => new UIDGenerator([]), TypeError); 70 | should.throws(() => new UIDGenerator(/regex/), TypeError); 71 | }); 72 | 73 | it('throws if baseEncoding is not a string', () => { 74 | should.throws(() => new UIDGenerator(128, null), TypeError); 75 | should.throws(() => new UIDGenerator(128, false), TypeError); 76 | should.throws(() => new UIDGenerator(128, 256), TypeError); 77 | should.throws(() => new UIDGenerator(128, {}), TypeError); 78 | should.throws(() => new UIDGenerator(128, []), TypeError); 79 | should.throws(() => new UIDGenerator(128, /regex/), TypeError); 80 | should.throws(() => new UIDGenerator(128, new Date()), TypeError); 81 | }); 82 | 83 | it('throws if baseEncoding is too short', () => { 84 | should.throws(() => new UIDGenerator(''), Error); 85 | should.throws(() => new UIDGenerator('1'), Error); 86 | }); 87 | 88 | it('throws if baseEncoding contains non-unique characters', () => { 89 | should.throws(() => new UIDGenerator('11'), Error); 90 | should.throws(() => new UIDGenerator('011'), Error); 91 | should.throws(() => new UIDGenerator('110'), Error); 92 | should.throws(() => new UIDGenerator('101'), Error); 93 | should.throws(() => new UIDGenerator('0121'), Error); 94 | should.throws(() => new UIDGenerator('01213'), Error); 95 | }); 96 | 97 | }); 98 | 99 | 100 | describe('#bitSize', () => { 101 | 102 | it('is the same value as is passed to the constructor or calculated from uidLength', () => { 103 | new UIDGenerator().bitSize.should.be.exactly(128); 104 | new UIDGenerator(256).bitSize.should.be.exactly(256); 105 | new UIDGenerator(8, '01').bitSize.should.be.exactly(8); 106 | }); 107 | 108 | }); 109 | 110 | 111 | describe('#baseEncoding', () => { 112 | 113 | it('is the same value as is passed to the constructor', () => { 114 | new UIDGenerator().baseEncoding.should.be.exactly(UIDGenerator.BASE58); 115 | new UIDGenerator(UIDGenerator.BASE62).baseEncoding.should.be.exactly(UIDGenerator.BASE62); 116 | new UIDGenerator(8, '01').baseEncoding.should.be.exactly('01'); 117 | }); 118 | 119 | }); 120 | 121 | 122 | describe('#base', () => { 123 | 124 | it('is the encoding base number (which is the length of the baseEncoding)', () => { 125 | new UIDGenerator().base.should.be.exactly(58); 126 | new UIDGenerator(UIDGenerator.BASE62).base.should.be.exactly(62); 127 | new UIDGenerator(UIDGenerator.BASE16, 11).base.should.be.exactly(16); 128 | new UIDGenerator(256, '123abc').base.should.be.exactly(6); 129 | }); 130 | 131 | }); 132 | 133 | 134 | describe('#uidLength', () => { 135 | 136 | it('is the correct, calculated UID length', () => { 137 | new UIDGenerator().uidLength.should.be.exactly(22); 138 | new UIDGenerator(256, UIDGenerator.BASE62).uidLength.should.be.exactly(43); 139 | new UIDGenerator(512, '01').uidLength.should.be.exactly(512); 140 | }); 141 | 142 | }); 143 | 144 | 145 | describe('UIDGenerator instance', () => { 146 | 147 | it('generates UIDs asynchronously with a callback', (done) => { 148 | new UIDGenerator().generate((err, uid) => { 149 | uid.should.have.type('string'); 150 | done(err); 151 | }); 152 | }); 153 | 154 | it('generates UIDs asynchronously with a promise', () => { 155 | return new UIDGenerator().generate() 156 | .then((uid) => { 157 | uid.should.have.type('string'); 158 | }); 159 | }); 160 | 161 | it('generates UIDs asynchronously with async/await', async () => { 162 | const uidgen = new UIDGenerator(); 163 | const uid = await uidgen.generate(); 164 | uid.should.have.type('string'); 165 | }); 166 | 167 | it('generates UIDs synchronously', () => { 168 | new UIDGenerator().generateSync().should.have.type('string'); 169 | }); 170 | 171 | it('generates UIDs with the specified baseEncoding and bitSize or uidLength', () => { 172 | let uidgen = new UIDGenerator(); 173 | let uid = uidgen.generateSync(); 174 | uid.should.match(new RegExp('^[' + uidgen.baseEncoding + ']{' + uidgen.uidLength + '}$')); 175 | 176 | uidgen = new UIDGenerator(UIDGenerator.BASE16); 177 | uid = uidgen.generateSync(); 178 | uid.should.match(new RegExp('^[' + uidgen.baseEncoding + ']{' + uidgen.uidLength + '}$')); 179 | 180 | uidgen = new UIDGenerator('01'); 181 | uid = uidgen.generateSync(); 182 | uid.should.match(new RegExp('^[' + uidgen.baseEncoding + ']{' + uidgen.uidLength + '}$')); 183 | 184 | uidgen = new UIDGenerator(256); 185 | uid = uidgen.generateSync(); 186 | uid.should.match(new RegExp('^[' + uidgen.baseEncoding + ']{' + uidgen.uidLength + '}$')); 187 | 188 | uidgen = new UIDGenerator(512, UIDGenerator.BASE62); 189 | uid = uidgen.generateSync(); 190 | uid.should.match(new RegExp('^[' + uidgen.baseEncoding + ']{' + uidgen.uidLength + '}$')); 191 | }); 192 | 193 | it('produces UIDs of the correct length even if crypto.randomBytes() returns a Buffer with leading zeros', () => { 194 | // Mock crypto.randomBytes 195 | const {randomBytes} = crypto; 196 | crypto.randomBytes = function(size) { 197 | return Buffer.alloc(size, 1); 198 | }; 199 | 200 | let uidgen = new UIDGenerator(256); 201 | let uid = uidgen.generateSync(); 202 | uid.should.match(new RegExp('^[' + uidgen.baseEncoding + ']{' + uidgen.uidLength + '}$')); 203 | 204 | crypto.randomBytes = function(size) { 205 | return Buffer.alloc(size); 206 | }; 207 | 208 | uidgen = new UIDGenerator(); 209 | uid = uidgen.generateSync(); 210 | uid.should.match(new RegExp('^[' + uidgen.baseEncoding + ']{' + uidgen.uidLength + '}$')); 211 | 212 | uidgen = new UIDGenerator('abc'); 213 | uid = uidgen.generateSync(); 214 | uid.should.match(new RegExp('^[' + uidgen.baseEncoding + ']{' + uidgen.uidLength + '}$')); 215 | 216 | crypto.randomBytes = function(size) { 217 | const buffer = Buffer.alloc(size); 218 | buffer[size - 1] = 255; 219 | return buffer; 220 | }; 221 | 222 | crypto.randomBytes = randomBytes; // Restore original 223 | }); 224 | 225 | it('base-encodes UIDs in a standard fashion', () => { 226 | let buffer; 227 | 228 | // Mock crypto.randomBytes 229 | const {randomBytes} = crypto; 230 | crypto.randomBytes = function(size) { 231 | buffer = randomBytes(size); 232 | return buffer; 233 | }; 234 | 235 | new UIDGenerator(UIDGenerator.BASE16).generateSync() 236 | .should.equal(buffer.toString('hex')); 237 | 238 | new UIDGenerator(256, UIDGenerator.BASE16).generateSync() 239 | .should.equal(buffer.toString('hex')); 240 | 241 | crypto.randomBytes = randomBytes; // Restore original 242 | }); 243 | 244 | it('correctly handles asynchronous errors', (done) => { 245 | const error = new Error('Fake error'); 246 | 247 | // Mock crypto.randomBytes 248 | const {randomBytes} = crypto; 249 | crypto.randomBytes = function(size, cb) { 250 | process.nextTick(() => { 251 | cb(error); 252 | }); 253 | }; 254 | 255 | new UIDGenerator().generate() 256 | .then( 257 | () => { 258 | should.fail('generate() should have thrown'); 259 | }, 260 | (err) => { 261 | err.should.be.exactly(error); 262 | 263 | new UIDGenerator().generate((err) => { 264 | err.should.be.exactly(error); 265 | crypto.randomBytes = randomBytes; // Restore original 266 | done(); 267 | }); 268 | } 269 | ); 270 | }); 271 | 272 | }); 273 | 274 | }); 275 | --------------------------------------------------------------------------------