From 3a033f6bab6bb3780ece6d645b902548043280bc Mon Sep 17 00:00:00 2001 From: Damian Kaczmarek Date: Thu, 2 Dec 2021 16:43:13 -0600 Subject: [PATCH 1/4] feat: optimize uuid.v1 by 1.3x uuid.v4 by 4.3x (430%) (#597) This skips input validation when stringifying UUIDs that we _know_ are valid. --- src/stringify.js | 7 +++-- src/v1.js | 4 +-- src/v35.js | 4 +-- src/v4.js | 4 +-- test/unit/stringify.test.js | 60 +++++++++++++++++++++---------------- 5 files changed, 45 insertions(+), 34 deletions(-) diff --git a/src/stringify.js b/src/stringify.js index 42fd4ecd..d74b8e1d 100644 --- a/src/stringify.js +++ b/src/stringify.js @@ -10,10 +10,10 @@ for (let i = 0; i < 256; ++i) { byteToHex.push((i + 0x100).toString(16).substr(1)); } -function stringify(arr, offset = 0) { +export function unsafeStringify(arr, offset = 0) { // Note: Be careful editing this code! It's been tuned for performance // and works in ways you may not expect. See https://github.com/uuidjs/uuid/pull/434 - const uuid = ( + return ( byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + @@ -35,7 +35,10 @@ function stringify(arr, offset = 0) { byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]] ).toLowerCase(); +} +function stringify(arr, offset = 0) { + const uuid = unsafeStringify(arr, offset); // Consistency check for valid UUID. If this throws, it's likely due to one // of the following: // - One or more input array values don't map to a hex octet (leading to diff --git a/src/v1.js b/src/v1.js index 0643675e..85b7a7a8 100644 --- a/src/v1.js +++ b/src/v1.js @@ -1,5 +1,5 @@ import rng from './rng.js'; -import stringify from './stringify.js'; +import { unsafeStringify } from './stringify.js'; // **`v1()` - Generate time-based UUID** // @@ -109,7 +109,7 @@ function v1(options, buf, offset) { b[i + n] = node[n]; } - return buf || stringify(b); + return buf || unsafeStringify(b); } export default v1; diff --git a/src/v35.js b/src/v35.js index 34d9b1f3..35a7ee63 100644 --- a/src/v35.js +++ b/src/v35.js @@ -1,4 +1,4 @@ -import stringify from './stringify.js'; +import { unsafeStringify } from './stringify.js'; import parse from './parse.js'; function stringToBytes(str) { @@ -51,7 +51,7 @@ export default function v35(name, version, hashfunc) { return buf; } - return stringify(bytes); + return unsafeStringify(bytes); } // Function#name is not settable on some platforms (#270) diff --git a/src/v4.js b/src/v4.js index dbe76993..dc21c59f 100644 --- a/src/v4.js +++ b/src/v4.js @@ -1,6 +1,6 @@ import native from './native.js'; import rng from './rng.js'; -import stringify from './stringify.js'; +import { unsafeStringify } from './stringify.js'; function v4(options, buf, offset) { if (native.randomUUID && !buf && !options) { @@ -26,7 +26,7 @@ function v4(options, buf, offset) { return buf; } - return stringify(rnds); + return unsafeStringify(rnds); } export default v4; diff --git a/test/unit/stringify.test.js b/test/unit/stringify.test.js index 1c1753cd..812a1386 100644 --- a/test/unit/stringify.test.js +++ b/test/unit/stringify.test.js @@ -1,40 +1,48 @@ import assert from 'assert'; -import stringify from '../../src/stringify.js'; +import stringify, { unsafeStringify } from '../../src/stringify.js'; const BYTES = [ 0x0f, 0x5a, 0xbc, 0xd1, 0xc1, 0x94, 0x47, 0xf3, 0x90, 0x5b, 0x2d, 0xf7, 0x26, 0x3a, 0x08, 0x4b, ]; -describe('stringify', () => { - test('Stringify Array', () => { - assert.equal(stringify(BYTES), '0f5abcd1-c194-47f3-905b-2df7263a084b'); - }); +describe('unsafeStringify', () => { + describe('default', () => { + test('Stringify Array', () => { + assert.equal(unsafeStringify(BYTES), '0f5abcd1-c194-47f3-905b-2df7263a084b'); + }); - test('Stringify TypedArray', () => { - assert.equal(stringify(Uint8Array.from(BYTES)), '0f5abcd1-c194-47f3-905b-2df7263a084b'); - assert.equal(stringify(Int32Array.from(BYTES)), '0f5abcd1-c194-47f3-905b-2df7263a084b'); - }); + test('Stringify TypedArray', () => { + assert.equal(unsafeStringify(Uint8Array.from(BYTES)), '0f5abcd1-c194-47f3-905b-2df7263a084b'); + assert.equal(unsafeStringify(Int32Array.from(BYTES)), '0f5abcd1-c194-47f3-905b-2df7263a084b'); + }); - test('Stringify w/ offset', () => { - assert.equal(stringify([0, 0, 0, ...BYTES], 3), '0f5abcd1-c194-47f3-905b-2df7263a084b'); + test('Stringify w/ offset', () => { + assert.equal(unsafeStringify([0, 0, 0, ...BYTES], 3), '0f5abcd1-c194-47f3-905b-2df7263a084b'); + }); }); - test('Throws on not enough values', () => { - const bytes = [...BYTES]; - bytes.length = 15; - assert.throws(() => stringify(bytes)); - }); + describe('safe', () => { + test('Stringify Array', () => { + assert.equal(stringify(BYTES), '0f5abcd1-c194-47f3-905b-2df7263a084b'); + }); - test('Throws on undefined value', () => { - const bytes = [...BYTES]; - delete bytes[3]; - bytes.length = 15; - assert.throws(() => stringify(bytes)); - }); + test('Throws on not enough values', () => { + const bytes = [...BYTES]; + bytes.length = 15; + assert.throws(() => stringify(bytes)); + }); + + test('Throws on undefined value', () => { + const bytes = [...BYTES]; + delete bytes[3]; + bytes.length = 15; + assert.throws(() => stringify(bytes)); + }); - test('Throws on invalid value', () => { - const bytes = [...BYTES]; - bytes[3] = 256; - assert.throws(() => stringify(bytes)); + test('Throws on invalid value', () => { + const bytes = [...BYTES]; + bytes[3] = 256; + assert.throws(() => stringify(bytes)); + }); }); }); From e6e541212e75e098ff0a036c38af2edffbb8b4f7 Mon Sep 17 00:00:00 2001 From: Christoph Tavan Date: Thu, 17 Mar 2022 08:42:49 +0100 Subject: [PATCH 2/4] chore: run node-esmodule package.json test only in latest node LTS (#621) While ESM support has been stable in current versions of Node.js 14.x and 16.x, support for importing JSON files is still experimental and evolving. Since we still want to cover the fact that the package is exporting it's package.json file, this test is now extracted to only run with the updated JSON import syntax which requires import assertions and which was introduced in Node.js 16.14.0. Fixes #619 Co-authored-by: Robert Kieffer --- a | 0 examples/node-esmodules/example.mjs | 4 ---- examples/node-esmodules/package.json | 4 +++- examples/node-esmodules/package.mjs | 4 ++++ scripts/testpack.sh | 8 +++++++- 5 files changed, 14 insertions(+), 6 deletions(-) create mode 100644 a create mode 100644 examples/node-esmodules/package.mjs diff --git a/a b/a new file mode 100644 index 00000000..e69de29b diff --git a/examples/node-esmodules/example.mjs b/examples/node-esmodules/example.mjs index a265b459..c67927bb 100644 --- a/examples/node-esmodules/example.mjs +++ b/examples/node-esmodules/example.mjs @@ -10,7 +10,6 @@ import { version as uuidVersion, } from 'uuid'; import * as uuid from 'uuid'; -import pkg from 'uuid/package.json'; console.log('uuidv1()', uuidv1()); @@ -65,6 +64,3 @@ console.log('uuid.parse()', uuid.parse(MY_NAMESPACE)); console.log('uuid.stringify()', uuid.stringify(uuid.parse(MY_NAMESPACE))); console.log('uuid.validate()', uuid.validate(MY_NAMESPACE)); console.log('uuid.version()', uuid.version(MY_NAMESPACE)); - -// Some tools like react-native need to introspect the package.json file -console.log('pkg.name', pkg.name); diff --git a/examples/node-esmodules/package.json b/examples/node-esmodules/package.json index 8ab859e8..c86768b1 100644 --- a/examples/node-esmodules/package.json +++ b/examples/node-esmodules/package.json @@ -3,7 +3,9 @@ "version": "0.0.0", "private": true, "scripts": { - "test": "node --experimental-modules --experimental-json-modules example.mjs" + "test:package": "( node --version | grep -vq 'v16' ) || ( node --experimental-json-modules package.mjs )", + "test:example": "node example.mjs", + "test": "npm-run-all test:*" }, "dependencies": { "uuid": "file:../../.local/uuid" diff --git a/examples/node-esmodules/package.mjs b/examples/node-esmodules/package.mjs new file mode 100644 index 00000000..5979c8e6 --- /dev/null +++ b/examples/node-esmodules/package.mjs @@ -0,0 +1,4 @@ +import pkg from 'uuid/package.json' assert { type: 'json' }; + +// Some tools like react-native need to introspect the package.json file +console.log('pkg.name', pkg.name); diff --git a/scripts/testpack.sh b/scripts/testpack.sh index 0fdafaf5..51b3849c 100755 --- a/scripts/testpack.sh +++ b/scripts/testpack.sh @@ -10,6 +10,7 @@ mkdir -p ../test-pack cp examples/node-commonjs/example.js ../test-pack/commonjs.js cp examples/node-esmodules/example.mjs ../test-pack/esmodules.mjs +cp examples/node-esmodules/package.mjs ../test-pack/esmodules-package.mjs cd ../test-pack @@ -18,4 +19,9 @@ npm init -y npm install ../uuid/uuid-*.tgz node commonjs.js -node --experimental-json-modules esmodules.mjs +node esmodules.mjs + +# Support for json esm imports requires import assertions starting in Node.js v16.14.0 which were +# not supported in earlier versions. Therefore we restrict the ESM test to more recent versions of +# Node.js: +( node --version | grep -vq 'v16' ) || ( node --experimental-json-modules esmodules-package.mjs ) From 4f99b5e990e30cce2eb914e86749b1e7abe734f3 Mon Sep 17 00:00:00 2001 From: CommanderRoot Date: Thu, 17 Mar 2022 17:56:37 +0100 Subject: [PATCH 3/4] Replace deprecated String.prototype.substr() (#623) String.prototype.substr() is deprecated (see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/substr) so we replace it with slice() which works similarily but isn't deprecated. Signed-off-by: Tobias Speicher --- src/stringify.js | 2 +- src/version.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/stringify.js b/src/stringify.js index d74b8e1d..3e66728d 100644 --- a/src/stringify.js +++ b/src/stringify.js @@ -7,7 +7,7 @@ import validate from './validate.js'; const byteToHex = []; for (let i = 0; i < 256; ++i) { - byteToHex.push((i + 0x100).toString(16).substr(1)); + byteToHex.push((i + 0x100).toString(16).slice(1)); } export function unsafeStringify(arr, offset = 0) { diff --git a/src/version.js b/src/version.js index 2b993703..08ce2caa 100644 --- a/src/version.js +++ b/src/version.js @@ -5,7 +5,7 @@ function version(uuid) { throw TypeError('Invalid UUID'); } - return parseInt(uuid.substr(14, 1), 16); + return parseInt(uuid.slice(14, 15), 16); } export default version; From fcd73881692d9fabb63872576ba28e30ff852091 Mon Sep 17 00:00:00 2001 From: Christoph Tavan Date: Thu, 17 Mar 2022 18:27:50 +0100 Subject: [PATCH 4/4] fix: handle error when parameter is not set in v3 and v5 (#622) * fix: unhandle error when parameter is not set in v3 and v5 * test: add unit test for v3 and v5 to test undefined/null `namespace` Authored-by: rzkytmgr --- src/v35.js | 2 +- test/unit/v35.test.js | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/src/v35.js b/src/v35.js index 35a7ee63..1e56eea5 100644 --- a/src/v35.js +++ b/src/v35.js @@ -26,7 +26,7 @@ export default function v35(name, version, hashfunc) { namespace = parse(namespace); } - if (namespace.length !== 16) { + if (namespace?.length !== 16) { throw TypeError('Namespace must be array-like (16 iterable integer values, 0-255)'); } diff --git a/test/unit/v35.test.js b/test/unit/v35.test.js index 65f1e752..b0b26433 100644 --- a/test/unit/v35.test.js +++ b/test/unit/v35.test.js @@ -148,6 +148,24 @@ describe('v35', () => { assert.deepEqual(buf, ['landmaster', 'landmaster', 'landmaster'].concat(testBuf)); }); + test('v3 undefined/null', () => { + assert.throws(() => { + v3(); + }); + + assert.throws(() => { + v3('hello'); + }); + + assert.throws(() => { + v3('hello.example.com', undefined); + }); + + assert.throws(() => { + v3('hello.example.com', null, new Array(16)); + }); + }); + test('v5', () => { // Expect to get the same results as http://tools.adjet.org/uuid-v5 assert.strictEqual(v5('hello.example.com', v5.DNS), 'fdda765f-fc57-5604-a269-52a7df8164ec'); @@ -229,6 +247,24 @@ describe('v35', () => { assert.deepEqual(buf, ['landmaster', 'landmaster', 'landmaster'].concat(testBuf)); }); + test('v5 undefined/null', () => { + assert.throws(() => { + v5(); + }); + + assert.throws(() => { + v5('hello'); + }); + + assert.throws(() => { + v5('hello.example.com', undefined); + }); + + assert.throws(() => { + v5('hello.example.com', null, new Array(16)); + }); + }); + test('v3/v5 constants', () => { assert.strictEqual(v3.DNS, '6ba7b810-9dad-11d1-80b4-00c04fd430c8'); assert.strictEqual(v3.URL, '6ba7b811-9dad-11d1-80b4-00c04fd430c8');