hyperid.js 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. 'use strict'
  2. const uuidv4 = require('./uuid-node')
  3. const parser = require('uuid-parse')
  4. const Buffer = loadBuffer()
  5. function loadBuffer () {
  6. const b = require('buffer')
  7. // use third party module if no buffer module
  8. return b && b.Buffer
  9. ? b.Buffer
  10. : require('buffer/').Buffer
  11. }
  12. const base64Padding = Buffer.from('==', 'base64')
  13. function hyperid (opts) {
  14. let fixedLength = false
  15. let urlSafe = false
  16. // gaurd if instantiated using boolean for fixedLength or with no args
  17. let maxInt = Math.pow(2, 31) - 1
  18. if (typeof opts === 'boolean') {
  19. fixedLength = opts
  20. } else {
  21. opts = opts || {}
  22. maxInt = opts.maxInt || Math.pow(2, 31) - 1
  23. urlSafe = !!opts.urlSafe
  24. fixedLength = !!opts.fixedLength
  25. }
  26. generate.uuid = uuidv4()
  27. generate.decode = decode
  28. let id = baseId(generate.uuid, urlSafe)
  29. let count = Math.floor(opts.startFrom || 0)
  30. if (isNaN(maxInt)) throw new Error(`maxInt must be a number. recieved ${opts.maxInt}`)
  31. if (isNaN(count) || !(maxInt > count && count >= 0)) {
  32. throw new Error([
  33. `when passed, opts.startFrom must be a number between 0 and ${maxInt}.`,
  34. 'Only the integer part matters.',
  35. `- got: ${opts.startFrom}`
  36. ].join('\n'))
  37. }
  38. return generate
  39. function generate () {
  40. let result
  41. if (count === maxInt) {
  42. generate.uuid = uuidv4()
  43. id = baseId(generate.uuid, urlSafe) // rebase
  44. count = 0
  45. }
  46. if (fixedLength) {
  47. result = id + `0000000000${count}`.slice(-10)
  48. } else {
  49. result = id + count
  50. }
  51. count = (count + 1) | 0
  52. return result
  53. }
  54. }
  55. function baseId (id, urlSafe) {
  56. const base64Id = Buffer.concat([Buffer.from(parser.parse(id)), base64Padding]).toString('base64')
  57. if (urlSafe) {
  58. return base64Id.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '-')
  59. }
  60. return base64Id.replace(/=+$/, '/')
  61. }
  62. function decode (id, opts) {
  63. opts = opts || {}
  64. const urlSafe = !!opts.urlSafe
  65. if (urlSafe) {
  66. id = id.replace(/-([^-]*)$/, '/' + '$1')
  67. .replace(/-/g, '+')
  68. .replace(/_/g, '/')
  69. }
  70. if (id.length < 22) {
  71. return null
  72. }
  73. const lastSlashIndex = id.lastIndexOf('/')
  74. if (lastSlashIndex === -1) {
  75. return null
  76. }
  77. const uuidPart = id.substring(0, lastSlashIndex)
  78. const countPart = Number(id.substring(lastSlashIndex + 1))
  79. if (!uuidPart || isNaN(countPart)) {
  80. return null
  81. }
  82. const result = {
  83. uuid: parser.unparse(Buffer.from(uuidPart + '==', 'base64')),
  84. count: countPart
  85. }
  86. return result
  87. }
  88. module.exports = hyperid
  89. module.exports.decode = decode