From 0b8f680a2632ad28b08ec6f56ad288e874d41821 Mon Sep 17 00:00:00 2001 From: Patrick McCarren Date: Sat, 21 Jan 2023 19:36:32 -0500 Subject: [PATCH 01/21] feat: implement uuid7 (#580) --- AUTHORS | 1 + README.md | 27 +++++++++- README_js.md | 25 ++++++++- bundlewatch.config.json | 4 +- examples/benchmark/benchmark.js | 14 +++++ examples/browser-esmodules/example.js | 4 ++ examples/browser-rollup/README.md | 3 +- examples/browser-rollup/example-all.js | 4 ++ examples/browser-rollup/example-v7.html | 12 +++++ examples/browser-rollup/example-v7.js | 8 +++ examples/browser-rollup/example.html | 1 + examples/browser-rollup/rollup.config.js | 16 ++++++ examples/browser-rollup/size-v7.js | 3 ++ .../browser-webpack/example-all-require.js | 4 ++ examples/browser-webpack/example-all.js | 4 ++ examples/browser-webpack/example-v7.html | 12 +++++ examples/browser-webpack/example-v7.js | 9 ++++ examples/browser-webpack/example.html | 1 + examples/browser-webpack/size-v7.js | 3 ++ examples/browser-webpack/webpack.config.js | 2 + examples/node-commonjs/example.js | 4 ++ examples/node-esmodules/example.mjs | 4 ++ examples/node-jest/jsdom.test.js | 5 ++ examples/node-jest/node.test.js | 5 ++ examples/node-webpack/example-v7.js | 3 ++ examples/node-webpack/package.json | 2 +- examples/node-webpack/webpack.config.js | 1 + src/index.js | 1 + src/regex.js | 2 +- src/v7.js | 54 +++++++++++++++++++ test/unit/version.test.js | 2 + wrapper.mjs | 1 + 32 files changed, 235 insertions(+), 6 deletions(-) create mode 100644 examples/browser-rollup/example-v7.html create mode 100644 examples/browser-rollup/example-v7.js create mode 100644 examples/browser-rollup/size-v7.js create mode 100644 examples/browser-webpack/example-v7.html create mode 100644 examples/browser-webpack/example-v7.js create mode 100644 examples/browser-webpack/size-v7.js create mode 100644 examples/node-webpack/example-v7.js create mode 100644 src/v7.js diff --git a/AUTHORS b/AUTHORS index 5a105230..d61b4eb3 100644 --- a/AUTHORS +++ b/AUTHORS @@ -3,3 +3,4 @@ Christoph Tavan AJ ONeal Vincent Voyer Roman Shtylman +Patrick McCarren diff --git a/README.md b/README.md index 15d92a89..4809301a 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ For the creation of [RFC4122](https://www.ietf.org/rfc/rfc4122.txt) UUIDs -- **Complete** - Support for RFC4122 version 1, 3, 4, and 5 UUIDs +- **Complete** - Support for RFC4122 version 1, 3, 4, 5, and 7 UUIDs - **Cross-platform** - Support for ... - CommonJS, [ECMAScript Modules](#ecmascript-modules) and [CDN builds](#cdn-builds) - Node 12, 14, 16, 18 @@ -56,6 +56,7 @@ For timestamp UUIDs, namespace UUIDs, and other options read on ... | [`uuid.v3()`](#uuidv3name-namespace-buffer-offset) | Create a version 3 (namespace w/ MD5) UUID | | | [`uuid.v4()`](#uuidv4options-buffer-offset) | Create a version 4 (random) UUID | | | [`uuid.v5()`](#uuidv5name-namespace-buffer-offset) | Create a version 5 (namespace w/ SHA-1) UUID | | +| [`uuid.v7()`](#uuidv7unix-epoch-time-based) | Create a version 7 (Unix Epoch time-based) UUID | New in `uuid@9.?` | | [`uuid.validate()`](#uuidvalidatestr) | Test a string to see if it is a valid UUID | New in `uuid@8.3` | | [`uuid.version()`](#uuidversionstr) | Detect RFC version of a UUID | New in `uuid@8.3` | @@ -248,6 +249,28 @@ import { v5 as uuidv5 } from 'uuid'; uuidv5('https://www.w3.org/', uuidv5.URL); // ⇨ 'c106a26a-21bb-5538-8bf2-57095d1976c1' ``` +### uuid.v7([options[, buffer[, offset]]]) + +Create an RFC version 7 (random) UUID + +| | | +| --- | --- | +| [`options`] | `Object` with one or more of the following properties: | +| [`options.msecs`] | RFC "timestamp" field (`Number` of milliseconds, unix epoch) | +| [`options.random`] | `Array` of 16 random bytes (0-255) | +| [`options.rng`] | Alternative to `options.random`, a `Function` that returns an `Array` of 16 random bytes (0-255) | +| [`buffer`] | `Array \| Buffer` If specified, uuid will be written here in byte-form, starting at `offset` | +| [`offset` = 0] | `Number` Index to start writing UUID bytes in `buffer` | +| _returns_ | UUID `String` if no `buffer` is specified, otherwise returns `buffer` | + +Example: + +```javascript +import { v7 as uuidv7 } from 'uuid'; + +uuidv7(); // ⇨ '017f22e2-79b0-7cc3-98c4-dc0c0c07398f' +``` + ### uuid.validate(str) Test a string to see if it is a valid UUID @@ -322,6 +345,8 @@ Usage: uuid v3 uuid v4 uuid v5 + uuid v7 + uuid v7 uuid --help Note: may be "URL" or "DNS" to use the corresponding UUIDs diff --git a/README_js.md b/README_js.md index ccfe8cf0..3bb4bd6d 100644 --- a/README_js.md +++ b/README_js.md @@ -21,7 +21,7 @@ require('crypto').randomUUID = undefined; For the creation of [RFC4122](https://www.ietf.org/rfc/rfc4122.txt) UUIDs -- **Complete** - Support for RFC4122 version 1, 3, 4, and 5 UUIDs +- **Complete** - Support for RFC4122 version 1, 3, 4, 5, and 7 UUIDs - **Cross-platform** - Support for ... - CommonJS, [ECMAScript Modules](#ecmascript-modules) and [CDN builds](#cdn-builds) - Node 12, 14, 16, 18 @@ -71,6 +71,7 @@ For timestamp UUIDs, namespace UUIDs, and other options read on ... | [`uuid.v3()`](#uuidv3name-namespace-buffer-offset) | Create a version 3 (namespace w/ MD5) UUID | | | [`uuid.v4()`](#uuidv4options-buffer-offset) | Create a version 4 (random) UUID | | | [`uuid.v5()`](#uuidv5name-namespace-buffer-offset) | Create a version 5 (namespace w/ SHA-1) UUID | | +| [`uuid.v7()`](#uuidv7unix-epoch-time-based) | Create a version 7 (Unix Epoch time-based) UUID | New in `uuid@9.?` | | [`uuid.validate()`](#uuidvalidatestr) | Test a string to see if it is a valid UUID | New in `uuid@8.3` | | [`uuid.version()`](#uuidversionstr) | Detect RFC version of a UUID | New in `uuid@8.3` | @@ -257,6 +258,28 @@ import { v5 as uuidv5 } from 'uuid'; uuidv5('https://www.w3.org/', uuidv5.URL); // RESULT ``` +### uuid.v7([options[, buffer[, offset]]]) + +Create an RFC version 7 (random) UUID + +| | | +| --- | --- | +| [`options`] | `Object` with one or more of the following properties: | +| [`options.msecs`] | RFC "timestamp" field (`Number` of milliseconds, unix epoch) | +| [`options.random`] | `Array` of 16 random bytes (0-255) | +| [`options.rng`] | Alternative to `options.random`, a `Function` that returns an `Array` of 16 random bytes (0-255) | +| [`buffer`] | `Array \| Buffer` If specified, uuid will be written here in byte-form, starting at `offset` | +| [`offset` = 0] | `Number` Index to start writing UUID bytes in `buffer` | +| _returns_ | UUID `String` if no `buffer` is specified, otherwise returns `buffer` | + +Example: + +```javascript --run +import { v7 as uuidv7 } from 'uuid'; + +uuidv7(); // RESULT +``` + ### uuid.validate(str) Test a string to see if it is a valid UUID diff --git a/bundlewatch.config.json b/bundlewatch.config.json index 1b33eb8a..9c55b0ae 100644 --- a/bundlewatch.config.json +++ b/bundlewatch.config.json @@ -4,10 +4,12 @@ { "path": "./examples/browser-rollup/dist/v3-size.js", "maxSize": "2.1 kB" }, { "path": "./examples/browser-rollup/dist/v4-size.js", "maxSize": "0.7 kB" }, { "path": "./examples/browser-rollup/dist/v5-size.js", "maxSize": "1.5 kB" }, + { "path": "./examples/browser-rollup/dist/v7-size.js", "maxSize": "0.8 kB" }, { "path": "./examples/browser-webpack/dist/v1-size.js", "maxSize": "1.0 kB" }, { "path": "./examples/browser-webpack/dist/v3-size.js", "maxSize": "2.1 kB" }, { "path": "./examples/browser-webpack/dist/v4-size.js", "maxSize": "0.7 kB" }, - { "path": "./examples/browser-webpack/dist/v5-size.js", "maxSize": "1.5 kB" } + { "path": "./examples/browser-webpack/dist/v5-size.js", "maxSize": "1.5 kB" }, + { "path": "./examples/browser-webpack/dist/v7-size.js", "maxSize": "0.8 kB" } ] } diff --git a/examples/benchmark/benchmark.js b/examples/benchmark/benchmark.js index 076f3b96..beda5105 100644 --- a/examples/benchmark/benchmark.js +++ b/examples/benchmark/benchmark.js @@ -57,12 +57,26 @@ export default function benchmark(uuid, Benchmark) { .add('uuid.v4() fill existing array', function () { uuid.v4(null, array, 0); }) + .add('uuid.v4() without native generation', function () { + uuid.v4({}); // passing an object instead of null bypasses native.randomUUID + }) .add('uuid.v3()', function () { uuid.v3('hello.example.com', uuid.v3.DNS); }) .add('uuid.v5()', function () { uuid.v5('hello.example.com', uuid.v5.DNS); }) + .add('uuid.v7()', function () { + uuid.v7(); + }) + .add('uuid.v7() fill existing array', function () { + uuid.v7(null, array, 0); + }) + .add('uuid.v7() with defined time', function () { + uuid.v7({ + msecs: 1645557742000, + }); + }) .on('cycle', function (event) { console.log(event.target.toString()); }) diff --git a/examples/browser-esmodules/example.js b/examples/browser-esmodules/example.js index 7b1ac765..5cbb6e78 100644 --- a/examples/browser-esmodules/example.js +++ b/examples/browser-esmodules/example.js @@ -6,6 +6,7 @@ import { v3 as uuidv3, v4 as uuidv4, v5 as uuidv5, + v7 as uuidv7, validate as uuidValidate, version as uuidVersion, } from './node_modules/uuid/dist/esm-browser/index.js'; @@ -15,6 +16,8 @@ console.log('uuidv1()', uuidv1()); console.log('uuidv4()', uuidv4()); +console.log('uuidv7()', uuidv7()); + // ... using predefined DNS namespace (for domain names) console.log('uuidv3() DNS', uuidv3('hello.example.com', uuidv3.DNS)); @@ -52,6 +55,7 @@ console.log('Same with default export'); console.log('uuid.v1()', uuid.v1()); console.log('uuid.v4()', uuid.v4()); +console.log('uuid.v7()', uuid.v7()); console.log('uuid.v3() DNS', uuid.v3('hello.example.com', uuid.v3.DNS)); console.log('uuid.v3() URL', uuid.v3('http://example.com/hello', uuid.v3.URL)); console.log('uuid.v3() MY_NAMESPACE', uuid.v3('Hello, World!', MY_NAMESPACE)); diff --git a/examples/browser-rollup/README.md b/examples/browser-rollup/README.md index b1a3a578..68214ed4 100644 --- a/examples/browser-rollup/README.md +++ b/examples/browser-rollup/README.md @@ -7,11 +7,12 @@ npm start Then navigate to `example-*.html`. -The `example-v{1,4}.js` demonstrate that treeshaking works as expected: +The `example-v{1,4,7}.js` demonstrate that treeshaking works as expected: ``` $ du -sh dist/* 20K dist/all.js 8.0K dist/v1.js 4.0K dist/v4.js +4.0K dist/v7.js ``` diff --git a/examples/browser-rollup/example-all.js b/examples/browser-rollup/example-all.js index 69997651..07af1908 100644 --- a/examples/browser-rollup/example-all.js +++ b/examples/browser-rollup/example-all.js @@ -6,6 +6,7 @@ import { v3 as uuidv3, v4 as uuidv4, v5 as uuidv5, + v7 as uuidv7, validate as uuidValidate, version as uuidVersion, } from 'uuid'; @@ -20,6 +21,8 @@ testpage(function (addTest, done) { addTest('uuidv4()', uuidv4()); + addTest('uuidv7()', uuidv7()); + // ... using predefined DNS namespace (for domain names) addTest('uuidv3() DNS', uuidv3('hello.example.com', uuidv3.DNS)); @@ -57,6 +60,7 @@ testpage(function (addTest, done) { addTest('uuid.v1()', uuid.v1()); addTest('uuid.v4()', uuid.v4()); + addTest('uuid.v7()', uuid.v7()); addTest('uuid.v3() DNS', uuid.v3('hello.example.com', uuid.v3.DNS)); addTest('uuid.v3() URL', uuid.v3('http://example.com/hello', uuid.v3.URL)); addTest('uuid.v3() MY_NAMESPACE', uuid.v3('Hello, World!', MY_NAMESPACE)); diff --git a/examples/browser-rollup/example-v7.html b/examples/browser-rollup/example-v7.html new file mode 100644 index 00000000..ac90c5b2 --- /dev/null +++ b/examples/browser-rollup/example-v7.html @@ -0,0 +1,12 @@ + + + + + UUID esmodule webpack example + + + + + + + diff --git a/examples/browser-rollup/example-v7.js b/examples/browser-rollup/example-v7.js new file mode 100644 index 00000000..bd807b23 --- /dev/null +++ b/examples/browser-rollup/example-v7.js @@ -0,0 +1,8 @@ +import { v7 as uuidv7 } from 'uuid'; + +import testpage from '../utils/testpage'; + +testpage(function (addTest, done) { + addTest('uuidv7()', uuidv7()); + done(); +}); diff --git a/examples/browser-rollup/example.html b/examples/browser-rollup/example.html index ca74545c..9f99126f 100644 --- a/examples/browser-rollup/example.html +++ b/examples/browser-rollup/example.html @@ -3,4 +3,5 @@

Please open the Developer Console to view output

+ diff --git a/examples/browser-rollup/rollup.config.js b/examples/browser-rollup/rollup.config.js index f6462ee5..8a914aa2 100644 --- a/examples/browser-rollup/rollup.config.js +++ b/examples/browser-rollup/rollup.config.js @@ -27,6 +27,14 @@ module.exports = [ }, plugins, }, + { + input: './example-v7.js', + output: { + file: 'dist/v7.js', + format: 'iife', + }, + plugins, + }, { input: './size-v1.js', @@ -60,4 +68,12 @@ module.exports = [ }, plugins, }, + { + input: './size-v7.js', + output: { + file: 'dist/v7-size.js', + format: 'cjs', + }, + plugins, + }, ]; diff --git a/examples/browser-rollup/size-v7.js b/examples/browser-rollup/size-v7.js new file mode 100644 index 00000000..a7e57335 --- /dev/null +++ b/examples/browser-rollup/size-v7.js @@ -0,0 +1,3 @@ +import { v7 as uuidv7 } from 'uuid'; + +uuidv7(); diff --git a/examples/browser-webpack/example-all-require.js b/examples/browser-webpack/example-all-require.js index 4e9b4feb..13fcc296 100644 --- a/examples/browser-webpack/example-all-require.js +++ b/examples/browser-webpack/example-all-require.js @@ -7,6 +7,7 @@ const { v3: uuidv3, v4: uuidv4, v5: uuidv5, + v7: uuidv7, validate: uuidValidate, version: uuidVersion, } = uuid; @@ -20,6 +21,8 @@ testpage(function (addTest, done) { addTest('uuidv4()', uuidv4()); + addTest('uuidv7()', uuidv7()); + // ... using predefined DNS namespace (for domain names) addTest('uuidv3() DNS', uuidv3('hello.example.com', uuidv3.DNS)); @@ -57,6 +60,7 @@ testpage(function (addTest, done) { addTest('uuid.v1()', uuid.v1()); addTest('uuid.v4()', uuid.v4()); + addTest('uuid.v7()', uuid.v7()); addTest('uuid.v3() DNS', uuid.v3('hello.example.com', uuid.v3.DNS)); addTest('uuid.v3() URL', uuid.v3('http://example.com/hello', uuid.v3.URL)); addTest('uuid.v3() MY_NAMESPACE', uuid.v3('Hello, World!', MY_NAMESPACE)); diff --git a/examples/browser-webpack/example-all.js b/examples/browser-webpack/example-all.js index 69997651..07af1908 100644 --- a/examples/browser-webpack/example-all.js +++ b/examples/browser-webpack/example-all.js @@ -6,6 +6,7 @@ import { v3 as uuidv3, v4 as uuidv4, v5 as uuidv5, + v7 as uuidv7, validate as uuidValidate, version as uuidVersion, } from 'uuid'; @@ -20,6 +21,8 @@ testpage(function (addTest, done) { addTest('uuidv4()', uuidv4()); + addTest('uuidv7()', uuidv7()); + // ... using predefined DNS namespace (for domain names) addTest('uuidv3() DNS', uuidv3('hello.example.com', uuidv3.DNS)); @@ -57,6 +60,7 @@ testpage(function (addTest, done) { addTest('uuid.v1()', uuid.v1()); addTest('uuid.v4()', uuid.v4()); + addTest('uuid.v7()', uuid.v7()); addTest('uuid.v3() DNS', uuid.v3('hello.example.com', uuid.v3.DNS)); addTest('uuid.v3() URL', uuid.v3('http://example.com/hello', uuid.v3.URL)); addTest('uuid.v3() MY_NAMESPACE', uuid.v3('Hello, World!', MY_NAMESPACE)); diff --git a/examples/browser-webpack/example-v7.html b/examples/browser-webpack/example-v7.html new file mode 100644 index 00000000..e985b4e3 --- /dev/null +++ b/examples/browser-webpack/example-v7.html @@ -0,0 +1,12 @@ + + + + + UUID esmodule webpack example + + + + + + + diff --git a/examples/browser-webpack/example-v7.js b/examples/browser-webpack/example-v7.js new file mode 100644 index 00000000..727b450b --- /dev/null +++ b/examples/browser-webpack/example-v7.js @@ -0,0 +1,9 @@ +import { v7 as uuidv7 } from 'uuid'; + +import testpage from '../utils/testpage'; + +testpage(function (addTest, done) { + addTest('uuidv7()', uuidv7()); + + done(); +}); diff --git a/examples/browser-webpack/example.html b/examples/browser-webpack/example.html index ca74545c..9f99126f 100644 --- a/examples/browser-webpack/example.html +++ b/examples/browser-webpack/example.html @@ -3,4 +3,5 @@

Please open the Developer Console to view output

+ diff --git a/examples/browser-webpack/size-v7.js b/examples/browser-webpack/size-v7.js new file mode 100644 index 00000000..a7e57335 --- /dev/null +++ b/examples/browser-webpack/size-v7.js @@ -0,0 +1,3 @@ +import { v7 as uuidv7 } from 'uuid'; + +uuidv7(); diff --git a/examples/browser-webpack/webpack.config.js b/examples/browser-webpack/webpack.config.js index f370bfcb..bb828942 100644 --- a/examples/browser-webpack/webpack.config.js +++ b/examples/browser-webpack/webpack.config.js @@ -7,11 +7,13 @@ module.exports = { allRequire: './example-all-require.js', v1: './example-v1.js', v4: './example-v4.js', + v7: './example-v7.js', 'v1-size': './size-v1.js', 'v3-size': './size-v3.js', 'v4-size': './size-v4.js', 'v5-size': './size-v5.js', + 'v7-size': './size-v7.js', }, // Webpack now produces builds that are incompatible with IE11: // https://webpack.js.org/migrate/5/#turn-off-es2015-syntax-in-runtime-code-if-necessary diff --git a/examples/node-commonjs/example.js b/examples/node-commonjs/example.js index 6a75f5a1..8d773de5 100644 --- a/examples/node-commonjs/example.js +++ b/examples/node-commonjs/example.js @@ -6,6 +6,7 @@ const { v3: uuidv3, v4: uuidv4, v5: uuidv5, + v7: uuidv7, validate: uuidValidate, version: uuidVersion, } = require('uuid'); @@ -16,6 +17,8 @@ console.log('uuidv1()', uuidv1()); console.log('uuidv4()', uuidv4()); +console.log('uuidv7()', uuidv7()); + // ... using predefined DNS namespace (for domain names) console.log('uuidv3() DNS', uuidv3('hello.example.com', uuidv3.DNS)); @@ -53,6 +56,7 @@ console.log('Same with default export'); console.log('uuid.v1()', uuid.v1()); console.log('uuid.v4()', uuid.v4()); +console.log('uuid.v7()', uuid.v7()); console.log('uuid.v3() DNS', uuid.v3('hello.example.com', uuid.v3.DNS)); console.log('uuid.v3() URL', uuid.v3('http://example.com/hello', uuid.v3.URL)); console.log('uuid.v3() MY_NAMESPACE', uuid.v3('Hello, World!', MY_NAMESPACE)); diff --git a/examples/node-esmodules/example.mjs b/examples/node-esmodules/example.mjs index c67927bb..433170d1 100644 --- a/examples/node-esmodules/example.mjs +++ b/examples/node-esmodules/example.mjs @@ -6,6 +6,7 @@ import { v3 as uuidv3, v4 as uuidv4, v5 as uuidv5, + v7 as uuidv7, validate as uuidValidate, version as uuidVersion, } from 'uuid'; @@ -15,6 +16,8 @@ console.log('uuidv1()', uuidv1()); console.log('uuidv4()', uuidv4()); +console.log('uuidv7()', uuidv7()); + // ... using predefined DNS namespace (for domain names) console.log('uuidv3() DNS', uuidv3('hello.example.com', uuidv3.DNS)); @@ -52,6 +55,7 @@ console.log('Same with default export'); console.log('uuid.v1()', uuid.v1()); console.log('uuid.v4()', uuid.v4()); +console.log('uuid.v7()', uuid.v7()); console.log('uuid.v3() DNS', uuid.v3('hello.example.com', uuid.v3.DNS)); console.log('uuid.v3() URL', uuid.v3('http://example.com/hello', uuid.v3.URL)); console.log('uuid.v3() MY_NAMESPACE', uuid.v3('Hello, World!', MY_NAMESPACE)); diff --git a/examples/node-jest/jsdom.test.js b/examples/node-jest/jsdom.test.js index 10cc7750..6773579b 100644 --- a/examples/node-jest/jsdom.test.js +++ b/examples/node-jest/jsdom.test.js @@ -6,3 +6,8 @@ test('uuidv4()', () => { const val = uuid.v4(); expect(uuid.version(val)).toBe(4); }); + +test('uuidv7()', () => { + const val = uuid.v7(); + expect(uuid.version(val)).toBe(7); +}); diff --git a/examples/node-jest/node.test.js b/examples/node-jest/node.test.js index 6626988e..02d93dc7 100644 --- a/examples/node-jest/node.test.js +++ b/examples/node-jest/node.test.js @@ -4,3 +4,8 @@ test('uuidv4()', () => { const val = uuid.v4(); expect(uuid.version(val)).toBe(4); }); + +test('uuidv7()', () => { + const val = uuid.v7(); + expect(uuid.version(val)).toBe(7); +}); diff --git a/examples/node-webpack/example-v7.js b/examples/node-webpack/example-v7.js new file mode 100644 index 00000000..2e9dc07c --- /dev/null +++ b/examples/node-webpack/example-v7.js @@ -0,0 +1,3 @@ +import { v7 as uuidv7 } from 'uuid'; + +console.log('uuidv7()', uuidv7()); diff --git a/examples/node-webpack/package.json b/examples/node-webpack/package.json index 1b45db55..5fbe12b0 100644 --- a/examples/node-webpack/package.json +++ b/examples/node-webpack/package.json @@ -4,7 +4,7 @@ "private": true, "scripts": { "build": "rm -rf dist && webpack", - "test": "npm run build && node dist/v1.js && node dist/v4.js && node dist/all.js" + "test": "npm run build && node dist/v1.js && node dist/v4.js && node dist/v7.js && node dist/all.js" }, "dependencies": { "uuid": "file:../../.local/uuid" diff --git a/examples/node-webpack/webpack.config.js b/examples/node-webpack/webpack.config.js index bc006dff..c0771e43 100644 --- a/examples/node-webpack/webpack.config.js +++ b/examples/node-webpack/webpack.config.js @@ -6,6 +6,7 @@ module.exports = { all: './example-all.js', v1: './example-v1.js', v4: './example-v4.js', + v7: './example-v7.js', }, output: { filename: '[name].js', diff --git a/src/index.js b/src/index.js index 142ce9e4..c982986c 100644 --- a/src/index.js +++ b/src/index.js @@ -2,6 +2,7 @@ export { default as v1 } from './v1.js'; export { default as v3 } from './v3.js'; export { default as v4 } from './v4.js'; export { default as v5 } from './v5.js'; +export { default as v7 } from './v7.js'; export { default as NIL } from './nil.js'; export { default as version } from './version.js'; export { default as validate } from './validate.js'; diff --git a/src/regex.js b/src/regex.js index 92f79a1e..757f9b20 100644 --- a/src/regex.js +++ b/src/regex.js @@ -1 +1 @@ -export default /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000)$/i; +export default /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-5,7][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000)$/i; diff --git a/src/v7.js b/src/v7.js new file mode 100644 index 00000000..356c041d --- /dev/null +++ b/src/v7.js @@ -0,0 +1,54 @@ +import rng from './rng.js'; +import { unsafeStringify } from './stringify.js'; + +/** + * UUID V7 - Unix Epoch time-based UUID + * + * The IETF has introduced a draft to update RFC4122, introducing 3 new + * UUID versions (6,7,8). This implementation of V7 is based on the accepted, + * though not yet approved, revisions. + * + * RFC 4122: https://www.ietf.org/rfc/rfc4122.html + * [DRAFT] Updated RFC 4122: https://github.com/ietf-wg-uuidrev/rfc4122bis + * + * Sample V7 value: https://ietf-wg-uuidrev.github.io/rfc4122bis/draft-00/draft-ietf-uuidrev-rfc4122bis.html#name-example-of-a-uuidv7-value + */ + +function v7(options, buf, offset) { + options = options || {}; + + // milliseconds since unix epoch, 1970-01-01 00:00 + const msecs = options.msecs !== undefined ? options.msecs : Date.now(); + + // rands is Uint8Array(16) filled with random bytes + const rnds = options.random || (options.rng || rng)(); + + // 48 bits of timestamp + rnds[0] = (msecs / 0x10000000000) & 0xff; + rnds[1] = (msecs / 0x100000000) & 0xff; + rnds[2] = (msecs / 0x1000000) & 0xff; + rnds[3] = (msecs / 0x10000) & 0xff; + rnds[4] = (msecs / 0x100) & 0xff; + rnds[5] = msecs & 0xff; + + // set version (7) + rnds[6] = (rnds[6] & 0x0f) | 0x70; + + // Per RFC4122 4.1.1, set the variant + rnds[8] = (rnds[8] & 0x3f) | 0x80; + + // Copy bytes to buffer, if provided + if (buf) { + offset = offset || 0; + + for (let i = 0; i < 16; ++i) { + buf[offset + i] = rnds[i]; + } + + return buf; + } + + return unsafeStringify(rnds); +} + +export default v7; diff --git a/test/unit/version.test.js b/test/unit/version.test.js index c482b4e7..f8edba8e 100644 --- a/test/unit/version.test.js +++ b/test/unit/version.test.js @@ -14,6 +14,8 @@ describe('version', () => { assert.strictEqual(version('90123e1c-7512-523e-bb28-76fab9f2f73d'), 5); + assert.strictEqual(version('017f22e2-79b0-7cc3-98c4-dc0c0c07398f'), 7); + assert.throws(() => version()); assert.throws(() => version('')); diff --git a/wrapper.mjs b/wrapper.mjs index c31e9cef..8e4a3e74 100644 --- a/wrapper.mjs +++ b/wrapper.mjs @@ -3,6 +3,7 @@ export const v1 = uuid.v1; export const v3 = uuid.v3; export const v4 = uuid.v4; export const v5 = uuid.v5; +export const v7 = uuid.v7; export const NIL = uuid.NIL; export const version = uuid.version; export const validate = uuid.validate; From a112083fdcef892fad4053f6d4b2dace306b0e32 Mon Sep 17 00:00:00 2001 From: Patrick McCarren Date: Sat, 21 Jan 2023 19:36:51 -0500 Subject: [PATCH 02/21] fix: add v7.js to .local (#580) note, husky pre-commit hook was bypassed for this commit --- .local/uuid/v7.js | 1 + 1 file changed, 1 insertion(+) create mode 120000 .local/uuid/v7.js diff --git a/.local/uuid/v7.js b/.local/uuid/v7.js new file mode 120000 index 00000000..c54100ca --- /dev/null +++ b/.local/uuid/v7.js @@ -0,0 +1 @@ +../../v7.js \ No newline at end of file From 5a89a924db67c4d34502f0d3bbd18f0515c7695c Mon Sep 17 00:00:00 2001 From: Patrick McCarren Date: Sat, 21 Jan 2023 21:08:44 -0500 Subject: [PATCH 03/21] fix: add v7 to uuid-bin --- src/uuid-bin.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/uuid-bin.js b/src/uuid-bin.js index a411b193..83d388a6 100644 --- a/src/uuid-bin.js +++ b/src/uuid-bin.js @@ -4,6 +4,7 @@ import v1 from './v1.js'; import v3 from './v3.js'; import v4 from './v4.js'; import v5 from './v5.js'; +import v7 from './v7.js'; function usage() { console.log('Usage:'); @@ -12,6 +13,8 @@ function usage() { console.log(' uuid v3 '); console.log(' uuid v4'); console.log(' uuid v5 '); + console.log(' uuid v7'); + console.log(' uuid v7 '); console.log(' uuid --help'); console.log( '\nNote: may be "URL" or "DNS" to use the corresponding UUIDs defined by RFC4122' @@ -74,6 +77,17 @@ switch (version) { break; } + case 'v7': { + const ts = args.shift(); + + console.log( + v7({ + msecs: ts ? parseInt(ts) : undefined, + }) + ); + break; + } + default: usage(); process.exit(1); From 92bc63a2f424131e2e5ad76dfe7ca02fb8911692 Mon Sep 17 00:00:00 2001 From: Patrick McCarren Date: Sat, 21 Jan 2023 21:12:35 -0500 Subject: [PATCH 04/21] chore: fix readme anchor --- README.md | 2 +- README_js.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4809301a..5bbec9ae 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ For timestamp UUIDs, namespace UUIDs, and other options read on ... | [`uuid.v3()`](#uuidv3name-namespace-buffer-offset) | Create a version 3 (namespace w/ MD5) UUID | | | [`uuid.v4()`](#uuidv4options-buffer-offset) | Create a version 4 (random) UUID | | | [`uuid.v5()`](#uuidv5name-namespace-buffer-offset) | Create a version 5 (namespace w/ SHA-1) UUID | | -| [`uuid.v7()`](#uuidv7unix-epoch-time-based) | Create a version 7 (Unix Epoch time-based) UUID | New in `uuid@9.?` | +| [`uuid.v7()`](#uuidv7options-buffer-offset) | Create a version 7 (Unix Epoch time-based) UUID | New in `uuid@9.?` | | [`uuid.validate()`](#uuidvalidatestr) | Test a string to see if it is a valid UUID | New in `uuid@8.3` | | [`uuid.version()`](#uuidversionstr) | Detect RFC version of a UUID | New in `uuid@8.3` | diff --git a/README_js.md b/README_js.md index 3bb4bd6d..97a20a82 100644 --- a/README_js.md +++ b/README_js.md @@ -71,7 +71,7 @@ For timestamp UUIDs, namespace UUIDs, and other options read on ... | [`uuid.v3()`](#uuidv3name-namespace-buffer-offset) | Create a version 3 (namespace w/ MD5) UUID | | | [`uuid.v4()`](#uuidv4options-buffer-offset) | Create a version 4 (random) UUID | | | [`uuid.v5()`](#uuidv5name-namespace-buffer-offset) | Create a version 5 (namespace w/ SHA-1) UUID | | -| [`uuid.v7()`](#uuidv7unix-epoch-time-based) | Create a version 7 (Unix Epoch time-based) UUID | New in `uuid@9.?` | +| [`uuid.v7()`](#uuidv7options-buffer-offset) | Create a version 7 (Unix Epoch time-based) UUID | New in `uuid@9.?` | | [`uuid.validate()`](#uuidvalidatestr) | Test a string to see if it is a valid UUID | New in `uuid@8.3` | | [`uuid.version()`](#uuidversionstr) | Detect RFC version of a UUID | New in `uuid@8.3` | From ba6d9cc4ffa4b4da5d117e7fdd28765d357f6ee5 Mon Sep 17 00:00:00 2001 From: Patrick McCarren Date: Sun, 22 Jan 2023 12:48:03 -0500 Subject: [PATCH 05/21] chore: use generated readme, remove timestamp arg from uuid-bin v7 --- README.md | 3 +-- README_js.md | 1 + src/uuid-bin.js | 12 ++---------- 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 5bbec9ae..4cdad435 100644 --- a/README.md +++ b/README.md @@ -268,7 +268,7 @@ Example: ```javascript import { v7 as uuidv7 } from 'uuid'; -uuidv7(); // ⇨ '017f22e2-79b0-7cc3-98c4-dc0c0c07398f' +uuidv7(); // ⇨ '01695553-c90c-7bad-9bdd-2b0d7b3dcb6d' ``` ### uuid.validate(str) @@ -346,7 +346,6 @@ Usage: uuid v4 uuid v5 uuid v7 - uuid v7 uuid --help Note: may be "URL" or "DNS" to use the corresponding UUIDs diff --git a/README_js.md b/README_js.md index 97a20a82..aaacea8a 100644 --- a/README_js.md +++ b/README_js.md @@ -354,6 +354,7 @@ Usage: uuid v3 uuid v4 uuid v5 + uuid v7 uuid --help Note: may be "URL" or "DNS" to use the corresponding UUIDs diff --git a/src/uuid-bin.js b/src/uuid-bin.js index 83d388a6..164e6458 100644 --- a/src/uuid-bin.js +++ b/src/uuid-bin.js @@ -14,7 +14,6 @@ function usage() { console.log(' uuid v4'); console.log(' uuid v5 '); console.log(' uuid v7'); - console.log(' uuid v7 '); console.log(' uuid --help'); console.log( '\nNote: may be "URL" or "DNS" to use the corresponding UUIDs defined by RFC4122' @@ -77,16 +76,9 @@ switch (version) { break; } - case 'v7': { - const ts = args.shift(); - - console.log( - v7({ - msecs: ts ? parseInt(ts) : undefined, - }) - ); + case 'v7': + console.log(v7()); break; - } default: usage(); From 3a201c36e95ca78f8c8e51ca1b4924b785031937 Mon Sep 17 00:00:00 2001 From: Patrick McCarren Date: Sun, 22 Jan 2023 13:04:39 -0500 Subject: [PATCH 06/21] fix: typo in uuid regex, add negative test cases --- src/regex.js | 2 +- test/unit/validate.test.js | 22 ++++++++++++++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/regex.js b/src/regex.js index 757f9b20..9199fe35 100644 --- a/src/regex.js +++ b/src/regex.js @@ -1 +1 @@ -export default /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-5,7][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000)$/i; +export default /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[13457][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000)$/i; diff --git a/test/unit/validate.test.js b/test/unit/validate.test.js index 7e290ff6..a4de1530 100644 --- a/test/unit/validate.test.js +++ b/test/unit/validate.test.js @@ -6,14 +6,32 @@ describe('validate', () => { test('validate uuid', () => { assert.strictEqual(validate(NIL), true); - assert.strictEqual(validate('d9428888-122b-11e1-b85c-61cd3cbb3210'), true); + // test valid UUID versions - assert.strictEqual(validate('109156be-c4fb-41ea-b1b4-efe1671c5836'), true); + // v1 + assert.strictEqual(validate('d9428888-122b-11e1-b85c-61cd3cbb3210'), true); + // v3 assert.strictEqual(validate('a981a0c2-68b1-35dc-bcfc-296e52ab01ec'), true); + // v4 + assert.strictEqual(validate('109156be-c4fb-41ea-b1b4-efe1671c5836'), true); + + // v5 assert.strictEqual(validate('90123e1c-7512-523e-bb28-76fab9f2f73d'), true); + // v7 + assert.strictEqual(validate('017f22e2-79b0-7cc3-98c4-dc0c0c07398f'), true); + + // test invalid/unsupported UUID versions + [2, 6, 8, 9, 'a', 'b', 'c', 'd', 'e', 'f'].forEach((v) => { + assert.strictEqual( + validate('00000000-0000-' + v + '000-0000-000000000000'), + false, + 'version ' + v + ' should not be valid' + ); + }); + assert.strictEqual(validate(), false); assert.strictEqual(validate(''), false); From b710c419d73351e792be1ddf35b6251bf51e01ef Mon Sep 17 00:00:00 2001 From: Patrick McCarren Date: Sun, 22 Jan 2023 17:34:08 -0500 Subject: [PATCH 07/21] fix: do not mutate provided rnds, add v7 unit tests --- src/v7.js | 41 +++++++++---------- test/unit/v7.test.js | 95 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+), 20 deletions(-) create mode 100644 test/unit/v7.test.js diff --git a/src/v7.js b/src/v7.js index 356c041d..c62ab99c 100644 --- a/src/v7.js +++ b/src/v7.js @@ -20,35 +20,36 @@ function v7(options, buf, offset) { // milliseconds since unix epoch, 1970-01-01 00:00 const msecs = options.msecs !== undefined ? options.msecs : Date.now(); - // rands is Uint8Array(16) filled with random bytes + // rnds is Uint8Array(16) filled with random bytes const rnds = options.random || (options.rng || rng)(); - // 48 bits of timestamp - rnds[0] = (msecs / 0x10000000000) & 0xff; - rnds[1] = (msecs / 0x100000000) & 0xff; - rnds[2] = (msecs / 0x1000000) & 0xff; - rnds[3] = (msecs / 0x10000) & 0xff; - rnds[4] = (msecs / 0x100) & 0xff; - rnds[5] = msecs & 0xff; + // initialize buffer and pointer + let i = (buf && offset) || 0; + const b = buf || new Uint8Array(16); - // set version (7) - rnds[6] = (rnds[6] & 0x0f) | 0x70; + // [bytes 0-5] 48 bits of timestamp + b[i++] = (msecs / 0x10000000000) & 0xff; + b[i++] = (msecs / 0x100000000) & 0xff; + b[i++] = (msecs / 0x1000000) & 0xff; + b[i++] = (msecs / 0x10000) & 0xff; + b[i++] = (msecs / 0x100) & 0xff; + b[i++] = msecs & 0xff; - // Per RFC4122 4.1.1, set the variant - rnds[8] = (rnds[8] & 0x3f) | 0x80; + // [byte 6] - set 4 bits of version (7) + b[i++] = (rnds[6] & 0x0f) | 0x70; - // Copy bytes to buffer, if provided - if (buf) { - offset = offset || 0; + // [byte 7] - set 8 bits of random + b[i++] = rnds[7]; - for (let i = 0; i < 16; ++i) { - buf[offset + i] = rnds[i]; - } + // [byte 8] - Per RFC4122 4.1.1, set the variant (2 bits) + b[i++] = (rnds[8] & 0x3f) | 0x80; - return buf; + // [bytes 9-16] populate with random bytes + for (let n = 0; n < 7; ++n) { + b[i + n] = rnds[n + 9]; } - return unsafeStringify(rnds); + return buf || unsafeStringify(b); } export default v7; diff --git a/test/unit/v7.test.js b/test/unit/v7.test.js new file mode 100644 index 00000000..60325db2 --- /dev/null +++ b/test/unit/v7.test.js @@ -0,0 +1,95 @@ +import assert from 'assert'; +import v7 from '../../src/v7.js'; + +/** + * fixture bit layout: + * ref: https://ietf-wg-uuidrev.github.io/rfc4122bis/draft-00/draft-ietf-uuidrev-rfc4122bis.html#name-example-of-a-uuidv7-value + * + * expectedBytes was calculated using this script: + * https://gist.github.com/d5382ac3a1ce4ba9ba40a90d9da8cbf1 + * + * ------------------------------- + * field bits value + * ------------------------------- + * unix_ts_ms 48 0x17F22E279B0 + * ver 4 0x7 + * rand_a 12 0xCC3 + * var 2 b10 + * rand_b 62 b01, 0x8C4DC0C0C07398F + * ------------------------------- + * total 128 + * ------------------------------- + * final: 017f22e2-79b0-7cc3-98c4-dc0c0c07398f + */ + +describe('v7', () => { + const msecsFixture = 1645557742000; + const randomBytesFixture = [ + 0x10, 0x91, 0x56, 0xbe, 0xc4, 0xfb, 0x0c, 0xc3, 0x18, 0xc4, 0xdc, 0x0c, 0x0c, 0x07, 0x39, 0x8f, + ]; + + const expectedBytes = [1, 127, 34, 226, 121, 176, 124, 195, 152, 196, 220, 12, 12, 7, 57, 143]; + + test('subsequent UUIDs are different', () => { + const id1 = v7(); + const id2 = v7(); + assert(id1 !== id2); + }); + + test('explicit options.random and options.msecs produces expected result', () => { + const id = v7({ + random: randomBytesFixture, + msecs: msecsFixture, + }); + assert.strictEqual(id, '017f22e2-79b0-7cc3-98c4-dc0c0c07398f'); + }); + + test('explicit options.rng produces expected result', () => { + const id = v7({ + rng: () => randomBytesFixture, + msecs: msecsFixture, + }); + assert.strictEqual(id, '017f22e2-79b0-7cc3-98c4-dc0c0c07398f'); + }); + + test('explicit options.msecs produces expected result', () => { + const id = v7({ + msecs: msecsFixture, + }); + assert.strictEqual(id.indexOf('017f22e2'), 0); + }); + + test('fills one UUID into a buffer as expected', () => { + const buffer = []; + const result = v7( + { + random: randomBytesFixture, + msecs: msecsFixture, + }, + buffer + ); + assert.deepEqual(buffer, expectedBytes); + assert.strictEqual(buffer, result); + }); + + test('fills two UUIDs into a buffer as expected', () => { + const buffer = []; + v7( + { + random: randomBytesFixture, + msecs: msecsFixture, + }, + buffer, + 0 + ); + v7( + { + random: randomBytesFixture, + msecs: msecsFixture, + }, + buffer, + 16 + ); + assert.deepEqual(buffer, expectedBytes.concat(expectedBytes)); + }); +}); From 1a6194299aa455f0ac90781e9f6b634ddc94108d Mon Sep 17 00:00:00 2001 From: Patrick McCarren Date: Sun, 22 Jan 2023 17:38:17 -0500 Subject: [PATCH 08/21] fix: validation test should not pass version 0 --- test/unit/validate.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/unit/validate.test.js b/test/unit/validate.test.js index a4de1530..3357d28e 100644 --- a/test/unit/validate.test.js +++ b/test/unit/validate.test.js @@ -24,9 +24,9 @@ describe('validate', () => { assert.strictEqual(validate('017f22e2-79b0-7cc3-98c4-dc0c0c07398f'), true); // test invalid/unsupported UUID versions - [2, 6, 8, 9, 'a', 'b', 'c', 'd', 'e', 'f'].forEach((v) => { + [0, 2, 6, 8, 9, 'a', 'b', 'c', 'd', 'e', 'f'].forEach((v) => { assert.strictEqual( - validate('00000000-0000-' + v + '000-0000-000000000000'), + validate('12300000-0000-' + v + '000-0000-000000000000'), false, 'version ' + v + ' should not be valid' ); From bf112122b9927a599caf434cad905c394b397d16 Mon Sep 17 00:00:00 2001 From: Patrick McCarren Date: Sun, 22 Jan 2023 17:38:48 -0500 Subject: [PATCH 09/21] chore: update package.json description --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 809d4618..c0e47839 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "uuid", "version": "9.0.0", - "description": "RFC4122 (v1, v4, and v5) UUIDs", + "description": "RFC4122 (v1, v3, v4, v5, and v7) UUIDs", "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" From a17479a15cd63a458b086db089e73cafa7add9d2 Mon Sep 17 00:00:00 2001 From: Patrick McCarren Date: Fri, 3 Feb 2023 18:59:56 -0800 Subject: [PATCH 10/21] Update src/v7.js MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Linus Unnebäck --- src/v7.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/v7.js b/src/v7.js index c62ab99c..2d4d8b45 100644 --- a/src/v7.js +++ b/src/v7.js @@ -45,9 +45,13 @@ function v7(options, buf, offset) { b[i++] = (rnds[8] & 0x3f) | 0x80; // [bytes 9-16] populate with random bytes - for (let n = 0; n < 7; ++n) { - b[i + n] = rnds[n + 9]; - } + b[i++] = rnds[9]; + b[i++] = rnds[10]; + b[i++] = rnds[11]; + b[i++] = rnds[12]; + b[i++] = rnds[13]; + b[i++] = rnds[14]; + b[i++] = rnds[15]; return buf || unsafeStringify(b); } From ced5fdc144895b6447976ccbcd5edd15a5f6b42a Mon Sep 17 00:00:00 2001 From: Patrick McCarren Date: Sat, 4 Feb 2023 10:49:53 -0800 Subject: [PATCH 11/21] include uuid v6 and v8 in validation regex --- src/regex.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/regex.js b/src/regex.js index 9199fe35..0e7a4590 100644 --- a/src/regex.js +++ b/src/regex.js @@ -1 +1 @@ -export default /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[13457][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000)$/i; +export default /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000)$/i; From 7264c2cd9c5f8d683966952ed90b632d66261e65 Mon Sep 17 00:00:00 2001 From: Patrick McCarren Date: Sat, 4 Feb 2023 13:25:13 -0800 Subject: [PATCH 12/21] chore: add test:matching script to package.json --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index c0e47839..61c056fc 100644 --- a/package.json +++ b/package.json @@ -98,6 +98,7 @@ "eslint:fix": "eslint --fix src/ test/ examples/ *.js", "pretest": "[ -n $CI ] || npm run build", "test": "BABEL_ENV=commonjsNode node --throw-deprecation node_modules/.bin/jest test/unit/", + "test:matching": "BABEL_ENV=commonjsNode node --throw-deprecation node_modules/.bin/jest test/unit/ -t", "pretest:browser": "optional-dev-dependency && npm run build && npm-run-all --parallel examples:browser:**", "test:browser": "wdio run ./wdio.conf.js", "pretest:node": "npm run build", From 2263096a0a04d6193971ef9ce737e5f77ec21e1c Mon Sep 17 00:00:00 2001 From: Patrick McCarren Date: Sun, 5 Feb 2023 01:05:56 -0500 Subject: [PATCH 13/21] fix: v7 monotonicity and lexicographical sorting --- src/v7.js | 129 ++++++++++++++++++++++++++++++++++++------- test/unit/v7.test.js | 77 ++++++++++++++++++++++++++ 2 files changed, 187 insertions(+), 19 deletions(-) diff --git a/src/v7.js b/src/v7.js index 2d4d8b45..52db17f3 100644 --- a/src/v7.js +++ b/src/v7.js @@ -12,41 +12,132 @@ import { unsafeStringify } from './stringify.js'; * [DRAFT] Updated RFC 4122: https://github.com/ietf-wg-uuidrev/rfc4122bis * * Sample V7 value: https://ietf-wg-uuidrev.github.io/rfc4122bis/draft-00/draft-ietf-uuidrev-rfc4122bis.html#name-example-of-a-uuidv7-value + * + * Monotonic Bit Layout: + * RFC 4122.6.2 Method 1, Dedicated Counter Bits + * ref: https://ietf-wg-uuidrev.github.io/rfc4122bis/draft-00/draft-ietf-uuidrev-rfc4122bis.html#section-6.2-5.2.1 + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | unix_ts_ms | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | unix_ts_ms | ver | seq_hi | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |var| seq_low | rand | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | rand | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * seq is a 31 bit serialized counter; comprised of 12 bit seq_hi and 19 bit seq_low, + * and randomly initialized upon timestamp change. 31 bit counter size was selected + * as any bitwise operations in node are done as _signed_ 32 bit ints. we exclude the sign bit. */ +let _seqLow = null; +let _seqHigh = null; +let _msecs = 0; + function v7(options, buf, offset) { options = options || {}; + // initialize buffer and pointer + let i = (buf && offset) || 0; + const b = buf || new Uint8Array(16); + + let seqHigh = _seqHigh; + let seqLow = _seqLow; + // milliseconds since unix epoch, 1970-01-01 00:00 const msecs = options.msecs !== undefined ? options.msecs : Date.now(); // rnds is Uint8Array(16) filled with random bytes const rnds = options.random || (options.rng || rng)(); - // initialize buffer and pointer - let i = (buf && offset) || 0; - const b = buf || new Uint8Array(16); + // seq is user provided seq + let seq = options.seq !== undefined ? options.seq : null; + + // if we have a user provided seq + if (seq !== null) { + // trim provided seq to 31 bits of value, avoiding overflow + if (seq > 0x7fffffff) { + seq = 0x7fffffff; + } + + // split provided seq into high/low parts + seqHigh = (seq >>> 19) & 0xfff; + seqLow = seq & 0x7ffff; + } + // else check if clock has advanced + else if (msecs !== _msecs) { + // reinitialize seq + seqHigh = null; + seqLow = null; + } + + // randomly initialize seq if empty + if (seqHigh === null) { + seqHigh = rnds[6] & 0x7f; + seqHigh = (seqHigh << 8) | rnds[7]; + } + + if (seqLow === null) { + seqLow = rnds[8] & 0x3f; // pad for var + seqLow = (seqLow << 8) | rnds[9]; + seqLow = (seqLow << 5) | (rnds[10] >>> 3); + } + + // check if clock has advanced or user provided seq + if (msecs > _msecs || seq !== null) { + _msecs = msecs; + } + // increment seq if within msecs window + else if (msecs + 10000 > _msecs) { + seqLow += 1; // increment our seq + if (seqLow > 0x7ffff) { + seqLow = 0; + + if (++seqHigh > 0xfff) { + seqHigh = 0; + + // increment internal _msecs. this allows us to continue incrementing + // while staying monotonic. Note, once we hit 10k milliseconds beyond system + // clock, we will reset breaking monotonicity (after (2^31)*10000 generations) + _msecs++; + } + } + } else { + // resetting; we have advanced more than + // 10k milliseconds beyond system clock + _msecs = msecs; + } + + _seqHigh = seqHigh; + _seqLow = seqLow; + + // [bytes 0-5] 48 bits of local timestamp + b[i++] = (_msecs / 0x10000000000) & 0xff; + b[i++] = (_msecs / 0x100000000) & 0xff; + b[i++] = (_msecs / 0x1000000) & 0xff; + b[i++] = (_msecs / 0x10000) & 0xff; + b[i++] = (_msecs / 0x100) & 0xff; + b[i++] = _msecs & 0xff; + + // [byte 6] - set 4 bits of version (7) with first 4 bits seq_hi + b[i++] = ((seqHigh >>> 4) & 0x0f) | 0x70; - // [bytes 0-5] 48 bits of timestamp - b[i++] = (msecs / 0x10000000000) & 0xff; - b[i++] = (msecs / 0x100000000) & 0xff; - b[i++] = (msecs / 0x1000000) & 0xff; - b[i++] = (msecs / 0x10000) & 0xff; - b[i++] = (msecs / 0x100) & 0xff; - b[i++] = msecs & 0xff; + // [byte 7] remaining 8 bits of seq_hi + b[i++] = seqHigh & 0xff; - // [byte 6] - set 4 bits of version (7) - b[i++] = (rnds[6] & 0x0f) | 0x70; + // [byte 8] - variant (2 bits), first 6 bits seq_low + b[i++] = ((seqLow >>> 13) & 0x3f) | 0x80; - // [byte 7] - set 8 bits of random - b[i++] = rnds[7]; + // [byte 9] 8 bits seq_low + b[i++] = (seqLow >>> 5) & 0xff; - // [byte 8] - Per RFC4122 4.1.1, set the variant (2 bits) - b[i++] = (rnds[8] & 0x3f) | 0x80; + // [byte 10] remaining 5 bits seq_low, 3 bits random + b[i++] = ((seqLow << 3) & 0xff) | (rnds[10] & 0x07); - // [bytes 9-16] populate with random bytes - b[i++] = rnds[9]; - b[i++] = rnds[10]; + // [bytes 11-15] always random b[i++] = rnds[11]; b[i++] = rnds[12]; b[i++] = rnds[13]; diff --git a/test/unit/v7.test.js b/test/unit/v7.test.js index 60325db2..8bb1f6c5 100644 --- a/test/unit/v7.test.js +++ b/test/unit/v7.test.js @@ -24,6 +24,8 @@ import v7 from '../../src/v7.js'; describe('v7', () => { const msecsFixture = 1645557742000; + const seqFixture = 0x661b189b; + const randomBytesFixture = [ 0x10, 0x91, 0x56, 0xbe, 0xc4, 0xfb, 0x0c, 0xc3, 0x18, 0xc4, 0xdc, 0x0c, 0x0c, 0x07, 0x39, 0x8f, ]; @@ -40,6 +42,7 @@ describe('v7', () => { const id = v7({ random: randomBytesFixture, msecs: msecsFixture, + seq: seqFixture, }); assert.strictEqual(id, '017f22e2-79b0-7cc3-98c4-dc0c0c07398f'); }); @@ -48,6 +51,7 @@ describe('v7', () => { const id = v7({ rng: () => randomBytesFixture, msecs: msecsFixture, + seq: seqFixture, }); assert.strictEqual(id, '017f22e2-79b0-7cc3-98c4-dc0c0c07398f'); }); @@ -65,6 +69,7 @@ describe('v7', () => { { random: randomBytesFixture, msecs: msecsFixture, + seq: seqFixture, }, buffer ); @@ -78,6 +83,7 @@ describe('v7', () => { { random: randomBytesFixture, msecs: msecsFixture, + seq: seqFixture, }, buffer, 0 @@ -86,10 +92,81 @@ describe('v7', () => { { random: randomBytesFixture, msecs: msecsFixture, + seq: seqFixture, }, buffer, 16 ); assert.deepEqual(buffer, expectedBytes.concat(expectedBytes)); }); + + // + // monotonic and lexicographical sorting tests + // + + test('lexicographical sorting is preserved', () => { + let id; + let prior; + let msecs = msecsFixture; + for (let i = 0; i < 20000; ++i) { + if (i % 1500 === 0) { + // every 1500 runs increment msecs so seq is + // reinitialized, simulating passage of time + msecs += 1; + } + + id = v7({ msecs }); + + if (i > 0) { + assert(prior < id, `${prior} < ${id}`); + } + + prior = id; + } + }); + + test('handles seq rollover', () => { + const msecs = msecsFixture; + const a = v7({ + msecs, + seq: 0x7fffffff, + }); + + v7({ msecs }); + + const c = v7({ msecs }); + + assert(a < c, `${a} < ${c}`); + }); + + test('can supply seq', () => { + let seq = 0x12345; + let uuid = v7({ + msecs: msecsFixture, + seq, + }); + + assert.strictEqual(uuid.substr(0, 25), '017f22e2-79b0-7000-891a-2'); + + seq = 0x6fffffff; + uuid = v7({ + msecs: msecsFixture, + seq, + }); + + assert.strictEqual(uuid.substr(0, 25), '017f22e2-79b0-7fff-bfff-f'); + }); + + test('internal seq is reset upon timestamp change', () => { + v7({ + msecs: msecsFixture, + seq: 0x6fffffff, + }); + + const uuid = v7({ + msecs: msecsFixture + 1, + }); + + assert(uuid.indexOf('fff') !== 15); + }); }); From 4022ec24a5f7206b0e2289822fbede3d6907f4da Mon Sep 17 00:00:00 2001 From: Patrick McCarren Date: Sun, 5 Feb 2023 23:44:27 -0500 Subject: [PATCH 14/21] refactor: v7 seq reinitialization --- src/v7.js | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/v7.js b/src/v7.js index 52db17f3..0bcf7bcb 100644 --- a/src/v7.js +++ b/src/v7.js @@ -16,6 +16,7 @@ import { unsafeStringify } from './stringify.js'; * Monotonic Bit Layout: * RFC 4122.6.2 Method 1, Dedicated Counter Bits * ref: https://ietf-wg-uuidrev.github.io/rfc4122bis/draft-00/draft-ietf-uuidrev-rfc4122bis.html#section-6.2-5.2.1 + * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ @@ -44,18 +45,30 @@ function v7(options, buf, offset) { let i = (buf && offset) || 0; const b = buf || new Uint8Array(16); - let seqHigh = _seqHigh; - let seqLow = _seqLow; + // rnds is Uint8Array(16) filled with random bytes + const rnds = options.random || (options.rng || rng)(); // milliseconds since unix epoch, 1970-01-01 00:00 const msecs = options.msecs !== undefined ? options.msecs : Date.now(); - // rnds is Uint8Array(16) filled with random bytes - const rnds = options.random || (options.rng || rng)(); - - // seq is user provided seq + // seq is user provided 31 bit counter let seq = options.seq !== undefined ? options.seq : null; + // initialize local seq high/low parts + let seqHigh = _seqHigh; + let seqLow = _seqLow; + + // check if clock has advanced and user has not provided msecs + if (msecs > _msecs && options.msecs === undefined) { + _msecs = msecs; + + // unless user provided seq, reset seq parts + if (seq !== null) { + seqHigh = null; + seqLow = null; + } + } + // if we have a user provided seq if (seq !== null) { // trim provided seq to 31 bits of value, avoiding overflow @@ -67,33 +80,20 @@ function v7(options, buf, offset) { seqHigh = (seq >>> 19) & 0xfff; seqLow = seq & 0x7ffff; } - // else check if clock has advanced - else if (msecs !== _msecs) { - // reinitialize seq - seqHigh = null; - seqLow = null; - } - // randomly initialize seq if empty - if (seqHigh === null) { + // randomly initialize seq + if (seqHigh === null || seqLow === null) { seqHigh = rnds[6] & 0x7f; seqHigh = (seqHigh << 8) | rnds[7]; - } - if (seqLow === null) { seqLow = rnds[8] & 0x3f; // pad for var seqLow = (seqLow << 8) | rnds[9]; seqLow = (seqLow << 5) | (rnds[10] >>> 3); } - // check if clock has advanced or user provided seq - if (msecs > _msecs || seq !== null) { - _msecs = msecs; - } // increment seq if within msecs window - else if (msecs + 10000 > _msecs) { - seqLow += 1; // increment our seq - if (seqLow > 0x7ffff) { + if (msecs + 10000 > _msecs && seq === null) { + if (++seqLow > 0x7ffff) { seqLow = 0; if (++seqHigh > 0xfff) { From 1d5c88aaf5064bfb9d8b8683af60eb7a979d8ca4 Mon Sep 17 00:00:00 2001 From: Patrick McCarren Date: Sun, 5 Feb 2023 23:51:09 -0500 Subject: [PATCH 15/21] chore: update v7 README --- README.md | 5 +++-- README_js.md | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4cdad435..8f126d08 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ For timestamp UUIDs, namespace UUIDs, and other options read on ... | [`uuid.v3()`](#uuidv3name-namespace-buffer-offset) | Create a version 3 (namespace w/ MD5) UUID | | | [`uuid.v4()`](#uuidv4options-buffer-offset) | Create a version 4 (random) UUID | | | [`uuid.v5()`](#uuidv5name-namespace-buffer-offset) | Create a version 5 (namespace w/ SHA-1) UUID | | -| [`uuid.v7()`](#uuidv7options-buffer-offset) | Create a version 7 (Unix Epoch time-based) UUID | New in `uuid@9.?` | +| [`uuid.v7()`](#uuidv7options-buffer-offset) | Create a version 7 (Unix Epoch time-based) UUID | `experimental support` | | [`uuid.validate()`](#uuidvalidatestr) | Test a string to see if it is a valid UUID | New in `uuid@8.3` | | [`uuid.version()`](#uuidversionstr) | Detect RFC version of a UUID | New in `uuid@8.3` | @@ -259,6 +259,7 @@ Create an RFC version 7 (random) UUID | [`options.msecs`] | RFC "timestamp" field (`Number` of milliseconds, unix epoch) | | [`options.random`] | `Array` of 16 random bytes (0-255) | | [`options.rng`] | Alternative to `options.random`, a `Function` that returns an `Array` of 16 random bytes (0-255) | +| [`options.seq`] | 31 bit monotonic sequence counter as `Number` between 0 - 0x7fffffff | | [`buffer`] | `Array \| Buffer` If specified, uuid will be written here in byte-form, starting at `offset` | | [`offset` = 0] | `Number` Index to start writing UUID bytes in `buffer` | | _returns_ | UUID `String` if no `buffer` is specified, otherwise returns `buffer` | @@ -268,7 +269,7 @@ Example: ```javascript import { v7 as uuidv7 } from 'uuid'; -uuidv7(); // ⇨ '01695553-c90c-7bad-9bdd-2b0d7b3dcb6d' +uuidv7(); // ⇨ '01695553-c90c-7aad-9bdd-330d7b3dcb6d' ``` ### uuid.validate(str) diff --git a/README_js.md b/README_js.md index aaacea8a..a6d50c63 100644 --- a/README_js.md +++ b/README_js.md @@ -71,7 +71,7 @@ For timestamp UUIDs, namespace UUIDs, and other options read on ... | [`uuid.v3()`](#uuidv3name-namespace-buffer-offset) | Create a version 3 (namespace w/ MD5) UUID | | | [`uuid.v4()`](#uuidv4options-buffer-offset) | Create a version 4 (random) UUID | | | [`uuid.v5()`](#uuidv5name-namespace-buffer-offset) | Create a version 5 (namespace w/ SHA-1) UUID | | -| [`uuid.v7()`](#uuidv7options-buffer-offset) | Create a version 7 (Unix Epoch time-based) UUID | New in `uuid@9.?` | +| [`uuid.v7()`](#uuidv7options-buffer-offset) | Create a version 7 (Unix Epoch time-based) UUID | `experimental support` | | [`uuid.validate()`](#uuidvalidatestr) | Test a string to see if it is a valid UUID | New in `uuid@8.3` | | [`uuid.version()`](#uuidversionstr) | Detect RFC version of a UUID | New in `uuid@8.3` | @@ -268,6 +268,7 @@ Create an RFC version 7 (random) UUID | [`options.msecs`] | RFC "timestamp" field (`Number` of milliseconds, unix epoch) | | [`options.random`] | `Array` of 16 random bytes (0-255) | | [`options.rng`] | Alternative to `options.random`, a `Function` that returns an `Array` of 16 random bytes (0-255) | +| [`options.seq`] | 31 bit monotonic sequence counter as `Number` between 0 - 0x7fffffff | | [`buffer`] | `Array \| Buffer` If specified, uuid will be written here in byte-form, starting at `offset` | | [`offset` = 0] | `Number` Index to start writing UUID bytes in `buffer` | | _returns_ | UUID `String` if no `buffer` is specified, otherwise returns `buffer` | From 3cb9acd6ebb1a8daaa448107188a973fe577a744 Mon Sep 17 00:00:00 2001 From: Patrick McCarren Date: Thu, 16 Feb 2023 13:24:53 -0500 Subject: [PATCH 16/21] chore: fix README_js.md prettier --- README_js.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/README_js.md b/README_js.md index 8e507b2e..d07ad17f 100644 --- a/README_js.md +++ b/README_js.md @@ -32,11 +32,9 @@ For the creation of [RFC4122](https://www.ietf.org/rfc/rfc4122.txt) UUIDs - **Small** - Zero-dependency, small footprint, plays nice with "tree shaking" packagers - **CLI** - Includes the [`uuid` command line](#command-line) utility -> **Note** -> Upgrading from `uuid@3`? Your code is probably okay, but check out [Upgrading From `uuid@3`](#upgrading-from-uuid3) for details. +> **Note** Upgrading from `uuid@3`? Your code is probably okay, but check out [Upgrading From `uuid@3`](#upgrading-from-uuid3) for details. -> **Note** -> Only interested in creating a version 4 UUID? You might be able to use [`cypto.randomUUID()`](https://developer.mozilla.org/en-US/docs/Web/API/Crypto/randomUUID), eliminating the need to install this library. +> **Note** Only interested in creating a version 4 UUID? You might be able to use [`cypto.randomUUID()`](https://developer.mozilla.org/en-US/docs/Web/API/Crypto/randomUUID), eliminating the need to install this library. ## Quickstart From 7f1ceafced500f1edeeabea7389a25a6ff9dde56 Mon Sep 17 00:00:00 2001 From: Patrick McCarren Date: Thu, 16 Feb 2023 13:27:45 -0500 Subject: [PATCH 17/21] chore: render README.md --- README.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 17bd0644..999218ce 100644 --- a/README.md +++ b/README.md @@ -17,11 +17,9 @@ For the creation of [RFC4122](https://www.ietf.org/rfc/rfc4122.txt) UUIDs - **Small** - Zero-dependency, small footprint, plays nice with "tree shaking" packagers - **CLI** - Includes the [`uuid` command line](#command-line) utility -> **Note** -> Upgrading from `uuid@3`? Your code is probably okay, but check out [Upgrading From `uuid@3`](#upgrading-from-uuid3) for details. +> **Note** Upgrading from `uuid@3`? Your code is probably okay, but check out [Upgrading From `uuid@3`](#upgrading-from-uuid3) for details. -> **Note** -> Only interested in creating a version 4 UUID? You might be able to use [`cypto.randomUUID()`](https://developer.mozilla.org/en-US/docs/Web/API/Crypto/randomUUID), eliminating the need to install this library. +> **Note** Only interested in creating a version 4 UUID? You might be able to use [`cypto.randomUUID()`](https://developer.mozilla.org/en-US/docs/Web/API/Crypto/randomUUID), eliminating the need to install this library. ## Quickstart From a8dd224d9b12ad9a66d6b6db90132ffdb862c5ca Mon Sep 17 00:00:00 2001 From: Robert Kieffer Date: Sun, 2 Jun 2024 16:50:26 -0700 Subject: [PATCH 18/21] chore: forgot to add updated prettier ignore file --- .prettierignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.prettierignore b/.prettierignore index 49780785..5538ca13 100644 --- a/.prettierignore +++ b/.prettierignore @@ -2,3 +2,4 @@ dist/ node_modules/ README.md +*.sh \ No newline at end of file From b9e6efa1a7eaccb9eed0899d6461b0d547ecdf0e Mon Sep 17 00:00:00 2001 From: Robert Kieffer Date: Sun, 2 Jun 2024 17:41:03 -0700 Subject: [PATCH 19/21] docs: update rfc links --- examples/benchmark/package-lock.json | 26 ++++++++++++++---------- src/v7.js | 30 +++++++++++++++------------- 2 files changed, 31 insertions(+), 25 deletions(-) diff --git a/examples/benchmark/package-lock.json b/examples/benchmark/package-lock.json index b061b75d..c0cda780 100644 --- a/examples/benchmark/package-lock.json +++ b/examples/benchmark/package-lock.json @@ -15,20 +15,24 @@ } }, "../../.local/uuid": { - "version": "8.3.2", + "version": "9.0.1", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], "license": "MIT", "bin": { "uuid": "dist/bin/uuid" }, "devDependencies": { - "@babel/cli": "7.18.9", - "@babel/core": "7.18.9", + "@babel/cli": "7.18.10", + "@babel/core": "7.18.10", "@babel/eslint-parser": "7.18.9", - "@babel/preset-env": "7.18.9", + "@babel/preset-env": "7.18.10", "@commitlint/cli": "17.0.3", "@commitlint/config-conventional": "17.0.3", "bundlewatch": "0.3.3", - "eslint": "8.20.0", + "eslint": "8.21.0", "eslint-config-prettier": "8.5.0", "eslint-config-standard": "17.0.0", "eslint-plugin-import": "2.26.0", @@ -42,7 +46,7 @@ "optional-dev-dependency": "2.0.1", "prettier": "2.7.1", "random-seed": "0.3.0", - "runmd": "1.3.6", + "runmd": "1.3.9", "standard-version": "9.5.0" } }, @@ -99,14 +103,14 @@ "uuid": { "version": "file:../../.local/uuid", "requires": { - "@babel/cli": "7.18.9", - "@babel/core": "7.18.9", + "@babel/cli": "7.18.10", + "@babel/core": "7.18.10", "@babel/eslint-parser": "7.18.9", - "@babel/preset-env": "7.18.9", + "@babel/preset-env": "7.18.10", "@commitlint/cli": "17.0.3", "@commitlint/config-conventional": "17.0.3", "bundlewatch": "0.3.3", - "eslint": "8.20.0", + "eslint": "8.21.0", "eslint-config-prettier": "8.5.0", "eslint-config-standard": "17.0.0", "eslint-plugin-import": "2.26.0", @@ -120,7 +124,7 @@ "optional-dev-dependency": "2.0.1", "prettier": "2.7.1", "random-seed": "0.3.0", - "runmd": "1.3.6", + "runmd": "1.3.9", "standard-version": "9.5.0" } } diff --git a/src/v7.js b/src/v7.js index 0bcf7bcb..74646ea8 100644 --- a/src/v7.js +++ b/src/v7.js @@ -4,21 +4,22 @@ import { unsafeStringify } from './stringify.js'; /** * UUID V7 - Unix Epoch time-based UUID * - * The IETF has introduced a draft to update RFC4122, introducing 3 new - * UUID versions (6,7,8). This implementation of V7 is based on the accepted, - * though not yet approved, revisions. + * The IETF has published RFC9562, introducing 3 new UUID versions (6,7,8). This + * implementation of V7 is based on the accepted, though not yet approved, + * revisions. * - * RFC 4122: https://www.ietf.org/rfc/rfc4122.html - * [DRAFT] Updated RFC 4122: https://github.com/ietf-wg-uuidrev/rfc4122bis + * RFC 4122:https://www.rfc-editor.org/rfc/rfc9562.html Universally Unique + * IDentifiers (UUIDs) + * - * Sample V7 value: https://ietf-wg-uuidrev.github.io/rfc4122bis/draft-00/draft-ietf-uuidrev-rfc4122bis.html#name-example-of-a-uuidv7-value + * Sample V7 value: + * https://www.rfc-editor.org/rfc/rfc9562.html#name-example-of-a-uuidv7-value * - * Monotonic Bit Layout: - * RFC 4122.6.2 Method 1, Dedicated Counter Bits - * ref: https://ietf-wg-uuidrev.github.io/rfc4122bis/draft-00/draft-ietf-uuidrev-rfc4122bis.html#section-6.2-5.2.1 + * Monotonic Bit Layout: RFC rfc9562.6.2 Method 1, Dedicated Counter Bits ref: + * https://www.rfc-editor.org/rfc/rfc9562.html#section-6.2-5.1 * - * 0 1 2 3 - * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * 0 1 2 3 0 1 2 3 4 5 6 + * 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | unix_ts_ms | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ @@ -29,9 +30,10 @@ import { unsafeStringify } from './stringify.js'; * | rand | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * - * seq is a 31 bit serialized counter; comprised of 12 bit seq_hi and 19 bit seq_low, - * and randomly initialized upon timestamp change. 31 bit counter size was selected - * as any bitwise operations in node are done as _signed_ 32 bit ints. we exclude the sign bit. + * seq is a 31 bit serialized counter; comprised of 12 bit seq_hi and 19 bit + * seq_low, and randomly initialized upon timestamp change. 31 bit counter size + * was selected as any bitwise operations in node are done as _signed_ 32 bit + * ints. we exclude the sign bit. */ let _seqLow = null; From 874af5c8298d0c0ee241d2a808a598f115ae0cd3 Mon Sep 17 00:00:00 2001 From: Robert Kieffer Date: Sun, 2 Jun 2024 17:55:53 -0700 Subject: [PATCH 20/21] chore: regenerate README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f1619e0f..816ece0f 100644 --- a/README.md +++ b/README.md @@ -98,7 +98,7 @@ import { parse as uuidParse } from 'uuid'; const bytes = uuidParse('6ec0bd7f-11c0-43da-975e-2a8ad9ebae0b'); // Convert to hex strings to show byte order (for documentation purposes) -[...bytes].map((v) => v.toString(16).padStart(2, '0')); // ⇨ +[...bytes].map((v) => v.toString(16).padStart(2, '0')); // ⇨ // [ // '6e', 'c0', 'bd', '7f', // '11', 'c0', '43', 'da', From d587e5b4a5206ffebbc4d6e41b1da77c84c51ff0 Mon Sep 17 00:00:00 2001 From: Robert Kieffer Date: Sun, 2 Jun 2024 18:03:57 -0700 Subject: [PATCH 21/21] docs: update links --- src/v7.js | 2 +- test/unit/v7.test.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/v7.js b/src/v7.js index 74646ea8..515cbc7a 100644 --- a/src/v7.js +++ b/src/v7.js @@ -8,7 +8,7 @@ import { unsafeStringify } from './stringify.js'; * implementation of V7 is based on the accepted, though not yet approved, * revisions. * - * RFC 4122:https://www.rfc-editor.org/rfc/rfc9562.html Universally Unique + * RFC 9562:https://www.rfc-editor.org/rfc/rfc9562.html Universally Unique * IDentifiers (UUIDs) * diff --git a/test/unit/v7.test.js b/test/unit/v7.test.js index 8bb1f6c5..aa2b5430 100644 --- a/test/unit/v7.test.js +++ b/test/unit/v7.test.js @@ -3,7 +3,7 @@ import v7 from '../../src/v7.js'; /** * fixture bit layout: - * ref: https://ietf-wg-uuidrev.github.io/rfc4122bis/draft-00/draft-ietf-uuidrev-rfc4122bis.html#name-example-of-a-uuidv7-value + * ref: https://www.rfc-editor.org/rfc/rfc9562.html#name-example-of-a-uuidv7-value * * expectedBytes was calculated using this script: * https://gist.github.com/d5382ac3a1ce4ba9ba40a90d9da8cbf1