├── .gitignore ├── .travis.yml ├── README.md ├── example.js ├── index.js ├── package.json └── test └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log* 3 | /coverage 4 | /.nyc_output 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | node_js: 2 | - "6" 3 | - "7" 4 | sudo: false 5 | language: node_js 6 | script: "npm run test:coverage && npm run test:coverage:report" 7 | after_script: "npm i -g codecov.io && cat ./coverage/lcov.info | codecov" 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # incremental-id 2 | 3 | unique client ids using monotonic increasing numbers with no upper bound. 4 | 5 | only unique for a single client during a single session, not across clients or sessions. 6 | 7 | based on code from [`bigeasy/monotonic`](https://github.com/bigeasy/monotonic) 8 | 9 | ```shell 10 | npm install --save incremental-id 11 | ``` 12 | 13 | ## example 14 | 15 | ```js 16 | const IncrementalId = require('incremental-id') 17 | 18 | while (true) { 19 | const incrementalId = IncrementalId() 20 | process.stdout.write(incrementalId + ' ') 21 | } 22 | 23 | // 0 1 2 3 4 5 6 7 8 9 a b c d e f 10 11 12 13 14 15 16 24 | // 17 18 19 1a 1b 1c 1d 1e 1f 20 ...................... 25 | // ... ffffffff 100000000 100000001 100000002 100000003 26 | // ... 1fffffffffffff 20000000000000 20000000000001 27 | ``` 28 | 29 | ## usage 30 | 31 | ### `IncrementalId = require('incremental-id')` 32 | 33 | ### `incrementalId = IncrementalId()` 34 | 35 | `incrementalId` returned will be a string, so we are not bounded by [Number.MAX_SAFE_INTEGER](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER) 36 | 37 | ## license 38 | 39 | The Apache License 40 | 41 | Copyright © 2017 Michael Williams 42 | 43 | Licensed under the Apache License, Version 2.0 (the "License"); 44 | you may not use this file except in compliance with the License. 45 | You may obtain a copy of the License at 46 | 47 | http://www.apache.org/licenses/LICENSE-2.0 48 | 49 | Unless required by applicable law or agreed to in writing, software 50 | distributed under the License is distributed on an "AS IS" BASIS, 51 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 52 | See the License for the specific language governing permissions and 53 | limitations under the License. 54 | -------------------------------------------------------------------------------- /example.js: -------------------------------------------------------------------------------- 1 | const IncrementalId = require('./') 2 | 3 | function loop () { 4 | process.stdout.write(IncrementalId() + ' ') 5 | setImmediate(loop) 6 | } 7 | 8 | loop() 9 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = IncrementalId 2 | module.exports.initialValue = '0' 3 | module.exports.previous 4 | 5 | function IncrementalId () { 6 | var previous = module.exports.previous 7 | if (previous == null) { 8 | module.exports.previous = previous = toWords(module.exports.initialValue) 9 | } 10 | previous = module.exports.previous = add(previous, 1) 11 | return toString(previous) 12 | } 13 | 14 | function add (words, value) { 15 | words = words.slice() 16 | var carry = value 17 | for (var i = words.length - 1; i !== -1; i--) { 18 | words[i] += carry 19 | if (words[i] > 0xffffffff) { 20 | carry = Math.floor(words[i] / Math.pow(2, 32)) 21 | words[i] = words[i] & 0xffffffff 22 | } else { 23 | carry = 0 24 | } 25 | } 26 | if (carry !== 0) { 27 | words.unshift(carry) 28 | } 29 | return words 30 | } 31 | 32 | function toWords (string) { 33 | var padding = 8 - (string.length & 0x7) 34 | if (padding !== 8) { 35 | string = '00000000'.substring(0, padding) + string 36 | } 37 | return string.match(/(.{1,8})/g).map(function (word) { 38 | return parseInt(word, 16) 39 | }) 40 | } 41 | 42 | function toString (words) { 43 | var string = [words[0].toString(16)] 44 | for (var i = 1, I = words.length; i < I; i++) { 45 | string.push(('00000000000' + words[i].toString(16)).substr(-8)) 46 | } 47 | return string.join('') 48 | } 49 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "incremental-id", 3 | "version": "1.0.0", 4 | "description": "unique client ids using monotonic increasing numbers", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node-dev example", 8 | "test:deps": "dependency-check . && dependency-check . --extra --no-dev -i es2040", 9 | "test:lint": "standard", 10 | "test:node": "NODE_ENV=test run-default tape test/*.js --", 11 | "test:coverage": "NODE_ENV=test nyc npm run test:node", 12 | "test:coverage:report": "nyc report --reporter=lcov npm run test:node", 13 | "test": "npm-run-all -s test:node test:lint test:deps" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/ahdinosaur/incremental-id.git" 18 | }, 19 | "keywords": [], 20 | "author": "Mikey (http://dinosaur.is)", 21 | "license": "Apache-2.0", 22 | "bugs": { 23 | "url": "https://github.com/ahdinosaur/incremental-id/issues" 24 | }, 25 | "homepage": "https://github.com/ahdinosaur/incremental-id#readme", 26 | "devDependencies": { 27 | "dependency-check": "^2.7.0", 28 | "node-dev": "^3.1.3", 29 | "npm-run-all": "^4.0.1", 30 | "nyc": "^10.1.2", 31 | "run-default": "^1.0.0", 32 | "standard": "^8.6.0", 33 | "tape": "^4.6.3" 34 | }, 35 | "dependencies": {} 36 | } 37 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | const test = require('tape') 2 | 3 | const IncrementalId = require('../') 4 | 5 | test('incremental-id', function (t) { 6 | t.ok(IncrementalId, 'module is require-able') 7 | t.end() 8 | }) 9 | 10 | test('first 20 client ids', t => { 11 | const expected = [ 12 | '1', '2', '3', '4', '5', 13 | '6', '7', '8', '9', 'a', 14 | 'b', 'c', 'd', 'e', 'f', 15 | '10', '11', '12', '13', '14' 16 | ] 17 | var actual = [] 18 | IncrementalId.initialValue = '0' 19 | IncrementalId.previous = null 20 | for (var i = 0; i < 20; i++) { 21 | actual.push(IncrementalId()) 22 | } 23 | t.deepEqual(actual, expected, 'actual is expected') 24 | t.end() 25 | }) 26 | 27 | test('0xfffffff rollover', t => { 28 | const expected = [ 29 | 'fffffffe', 30 | 'ffffffff', 31 | '100000000', 32 | '100000001' 33 | ] 34 | var actual = [] 35 | IncrementalId.initialValue = 'fffffffd' 36 | IncrementalId.previous = null 37 | actual.push(IncrementalId()) 38 | actual.push(IncrementalId()) 39 | actual.push(IncrementalId()) 40 | actual.push(IncrementalId()) 41 | t.deepEqual(actual, expected, 'actual is expected') 42 | t.end() 43 | }) 44 | 45 | test('larger than Number.MAX_SAFE_INTEGER', t => { 46 | const expected = [ 47 | '1ffffffffffffe', 48 | '1fffffffffffff', 49 | '20000000000000', 50 | '20000000000001' 51 | ] 52 | var actual = [] 53 | IncrementalId.initialValue = '1ffffffffffffd' 54 | IncrementalId.previous = null 55 | actual.push(IncrementalId()) 56 | actual.push(IncrementalId()) 57 | actual.push(IncrementalId()) 58 | actual.push(IncrementalId()) 59 | t.deepEqual(actual, expected, 'actual is expected') 60 | t.end() 61 | }) 62 | --------------------------------------------------------------------------------