Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
**/dist
**/node_modules
**/tmp
43 changes: 35 additions & 8 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,18 @@
"env": {
"browser": true,
"commonjs": true,
"es6": true,
"mocha": true,
"node": true
},
"globals": {
"msCrypto": true
},
"parser": "babel-eslint",
"parserOptions": {
"sourceType": "module",
"ecmaVersion": 2019
},
"extends": ["eslint:recommended"],
"globals": {
"msCrypto": true
Expand All @@ -20,6 +29,7 @@
"comma-spacing": ["warn", {"after": true}],
"dot-notation": "warn",
"eqeqeq": ["warn", "smart"],
"func-call-spacing": ["warn", "never"],
"indent": ["warn", 2, {
"SwitchCase": 1,
"FunctionDeclaration": {"parameters": 1},
Expand All @@ -28,23 +38,40 @@
}],
"key-spacing": ["warn", {"beforeColon": false, "afterColon": true, "mode": "minimum"}],
"keyword-spacing": "warn",
"linebreak-style": ["warn", "unix"],
"lines-around-directive": ["warn", { "before": "never", "after": "always"}],
"max-len": ["off", {"code": 120, "tabWidth": 2, "ignoreTrailingComments": true, "ignoreTemplateLiterals": true}],
"no-console": "off",
"no-constant-condition": "off",
"no-else-return": "warn",
"no-empty": "off",
"no-multi-spaces": "warn",
"no-ex-assign": "off",
"no-extra-bind": "warn",
"no-floating-decimal": "warn",
"no-multi-spaces": ["warn", {"ignoreEOLComments": true}],
"no-multiple-empty-lines": ["warn", {"max": 2, "maxBOF": 0, "maxEOF": 0}],
"no-redeclare": "off",
"no-restricted-globals": ["warn", "Promise"],
"no-restricted-globals": ["warn"],
"no-trailing-spaces": "warn",
"no-undef": "error",
"no-unused-vars": ["warn", {"args": "none"}],
"no-useless-return": "error",
"no-var": "warn",
"no-whitespace-before-property": "warn",
"object-curly-spacing": ["warn", "never"],
"one-var": ["warn", "never"],
"padded-blocks": ["warn", "never"],
"object-curly-spacing": ["warn", "never"],
"quotes": ["warn", "single"],
"react/prop-types": "off",
"react/jsx-no-bind": "off",
"prefer-const": "warn",
"quote-props": ["warn", "as-needed"],
"quotes": ["warn", "single", {"allowTemplateLiterals": true }],
"semi": ["warn", "always"],
"space-before-blocks": ["warn", "always"],
"space-before-function-paren": ["warn", "never"],
"space-in-parens": ["warn", "never"]
"space-before-function-paren": ["warn", {
"anonymous": "never",
"named": "never",
"asyncArrow": "always"
}],
"space-in-parens": ["warn", "never"],
"space-infix-ops": ["warn"]
}
}
2 changes: 1 addition & 1 deletion .npmrc
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
tag-version-prefix="v"
save-prefix="~"
save-prefix=
185 changes: 185 additions & 0 deletions UUID.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
import bytesToUuid from './lib/bytesToUuid.js';

const UUID_RE = /^[\da-f]{8}-[\da-f]{4}-[1-5][\da-f]{3}-[89ab][\da-f]{3}-[\da-f]{12}$/i;
const NIL_UUID = '00000000-0000-0000-0000-000000000000';
const EPOCH = 12219292800000n; // Gregorian epoch offset (in JS Date units)

function assertV1(uuid, v) {
if (uuid.version !== 1) throw Error(`Not a v${uuid.version} uuid`);
}

class UUID {
/**
* Create UUID instance from RFC4122 UUID string
*
* @param {String} str An RFC4122-compliant UUID string
* @throws {Error} If string fails to parse, or is not RFC-compliant
*/
static fromString(str) {
if (str !== NIL_UUID && !UUID_RE.test(str)) throw new TypeError('Invalid UUID string');
return new UUID(str.match(/[\da-f]{2}/g).map(v => parseInt(v, 16)));
}

/**
* Create UUID instance from array of 16 bytes.
*
* @param {Array[16]|TypedArray[16]} bytes
* @param {Number} [version] If provided, sets the variant and version
* fields (and skips verification)
* @throws If `bytes` are not a valid RFC4122
*/
constructor(bytes, version) {
this.bytes = bytes ? Uint8Array.from(bytes) : new Uint8Array(16).fill(0);

// UUIDs must be exactly 16 bytes
if (this.bytes.length !== 16) throw new TypeError('bytes.length !== 16');

// Skip verification
if (version) {
this.variant = 4;
this.version = version;
} else {
if (this.variant !== 0 && this.bytes.some(v => v !== 0)) {
// RFC Sec 4.1.7 allows for a Nil (all zeroes) UUID
throw TypeError('Variant may not be 0 for non-Nil UUIDs');
} else if ((this.variant & 0x06) !== 4) {
throw TypeError('Variant must be 4 or 5');
}

// Validate RFC version
if (this.version < 1 || this.version > 5) throw TypeError('Version must be 1-5');
}
}

/**
* @returns {Number}
*/
get variant() {
// Per sec. 4.1.1: The variant field is 3 bits, which we return here.
// Readers should be aware that for the RFC4122 variant (1 0 X) the low bit
// is part of the clockseq, so may result in either 4 or 5 being returned
// here.
return this.bytes[8] >> 5;
}

set variant(v) {
if (v !== 4 && v !== 5) throw new TypeError('Non-RFC4122 variant is not supported');
this.bytes[8] = (this.bytes[8] & 0x3f) | (0x04 << 5);
}

/**
* @returns {Number}
*/
get version() {
return this.bytes[6] >> 4;
}

/**
* @param {Number} v
*/
set version(v) {
this.bytes[6] = (this.bytes[6] & 0x0f) | (v << 4);
}

/**
* @returns {UInt8Array[6]}
*/
get node() {
assertV1(this);

// Return byte array here because it's likely to be more useful than a
// Number. Even though JS Numbers can hold 48-bit integers, working with
// such a value is problematic because boolean operations coerce values to
// 32-bit ints.
return Uint8Array.from(this.bytes.slice(10));
}

/**
* @param {Uint8Array[6]} v
*/
set node(bytes) {
assertV1(this);

this.bytes.set(bytes, 10);
}

/**
* @returns {Number}
*/
get clockseq() {
assertV1(this);

return (this.bytes[8] & 0x3f) << 8 | this.bytes[9];
}

/**
* @param {Number} v
*/
set clockseq(v) {
assertV1(this);

this.bytes[8] = (0xc0 & this.bytes[8]) | (v >> 8 & 0xff);
this.bytes[8] = v & 0xff;
}

/**
* @returns {BigInt} 100-nsec intervals start of gregorian epoch
*/
get timestamp() {
assertV1(this);

return BigInt(this.bytes[6] & 0x0f) << 56n +
BigInt(this.bytes[7]) << 48n +
BigInt(this.bytes[4]) << 40n +
BigInt(this.bytes[5]) << 32n +
BigInt(this.bytes[0]) << 24n +
BigInt(this.bytes[1]) << 16n +
BigInt(this.bytes[2]) << 8n +
BigInt(this.bytes[3]);
}

/**
* @param {BigInt} v
*/
set timestamp(v) {
assertV1(this);
const s = v.toString(16).padStart(16, '0').match(/../g).map(v => parseInt(v, 16));
this.bytes[6] = this.bytes[6] & 0xf0 | s[0] & 0x0f;
this.bytes[7] = s[1];
this.bytes[4] = s[2];
this.bytes[5] = s[3];
this.bytes[0] = s[4];
this.bytes[1] = s[5];
this.bytes[2] = s[6];
this.bytes[3] = s[7];
}

/**
* Get timestamp as JS Date. NOTE: This loses the 100-nanosecond resolution
* that is implicit in UUID timestamps.
*
* @returns {Date}
*/
get date() {
assertV1(this);
return new Date(Number(this.timestamp / 10000n - EPOCH));
}

/**
* Set timestamp as a JS Date
* @param {Date} v
*/
set date(v) {
assertV1(this);
this.timestamp = (BigInt(v) + EPOCH) * 10000n;
}

/**
* @return {String}
*/
toString() {
return bytesToUuid(this.bytes);
}
}

export default UUID;
31 changes: 15 additions & 16 deletions bin/uuid
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
#!/usr/bin/env node
var assert = require('assert');
#!/usr/bin/env node --experimental-modules
import assert from 'assert';

import v1 from '../v1.js';
import v3 from '../v3.js';
import v4 from '../v4.js';
import v5, {URL, DNS} from '../v5.js';

function usage() {
console.log('Usage:');
Expand All @@ -22,41 +27,35 @@ var version = args.shift() || 'v4';

switch (version) {
case 'v1':
var uuidV1 = require('../v1');
console.log(uuidV1());
console.log(v1());
break;

case 'v3':
var uuidV3 = require('../v3');

var name = args.shift();
var namespace = args.shift();
assert(name != null, 'v3 name not specified');
assert(namespace != null, 'v3 namespace not specified');

if (namespace == 'URL') namespace = uuidV3.URL;
if (namespace == 'DNS') namespace = uuidV3.DNS;
if (namespace == 'URL') namespace = URL;
if (namespace == 'DNS') namespace = DNS;

console.log(uuidV3(name, namespace));
console.log(v3(name, namespace));
break;

case 'v4':
var uuidV4 = require('../v4');
console.log(uuidV4());
console.log(v4());
break;

case 'v5':
var uuidV5 = require('../v5');

var name = args.shift();
var namespace = args.shift();
assert(name != null, 'v5 name not specified');
assert(namespace != null, 'v5 namespace not specified');

if (namespace == 'URL') namespace = uuidV5.URL;
if (namespace == 'DNS') namespace = uuidV5.DNS;
if (namespace == 'URL') namespace = URL;
if (namespace == 'DNS') namespace = DNS;

console.log(uuidV5(name, namespace));
console.log(v5(name, namespace));
break;

default:
Expand Down
8 changes: 0 additions & 8 deletions index.js

This file was deleted.

10 changes: 5 additions & 5 deletions lib/bytesToUuid.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
* Convert array of 16 byte values to UUID string format of the form:
* XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
*/
var byteToHex = [];
for (var i = 0; i < 256; ++i) {
const byteToHex = [];
for (let i = 0; i < 256; ++i) {
byteToHex[i] = (i + 0x100).toString(16).substr(1);
}

function bytesToUuid(buf, offset) {
var i = offset || 0;
var bth = byteToHex;
let i = offset || 0;
const bth = byteToHex;
// join used to fix memory issue caused by concatenation: https://bugs.chromium.org/p/v8/issues/detail?id=3175#c4
return ([
bth[buf[i++]], bth[buf[i++]],
Expand All @@ -23,4 +23,4 @@ function bytesToUuid(buf, offset) {
]).join('');
}

module.exports = bytesToUuid;
export default bytesToUuid;
Loading