From 0b8f680a2632ad28b08ec6f56ad288e874d41821 Mon Sep 17 00:00:00 2001 From: Patrick McCarren Date: Sat, 21 Jan 2023 19:36:32 -0500 Subject: [PATCH] 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;