This commit is contained in:
Patrick McCarren
2024-06-02 15:56:09 -10:00
committed by GitHub
parent f8e0b92363
commit cf68e024ba
39 changed files with 549 additions and 20 deletions
+1
View File
@@ -0,0 +1 @@
../../v7.js
+1
View File
@@ -2,3 +2,4 @@
dist/
node_modules/
README.md
*.sh
+1
View File
@@ -3,3 +3,4 @@ Christoph Tavan <[email protected]>
AJ ONeal <[email protected]>
Vincent Voyer <[email protected]>
Roman Shtylman <[email protected]>
Patrick McCarren <[email protected]>
+26 -1
View File
@@ -7,7 +7,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)
- NodeJS 12+ ([LTS releases](https://github.com/nodejs/Release))
@@ -59,6 +59,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 | `experimental support` |
| [`uuid.validate()`](#uuidvalidatestr) | Test a string to see if it is a valid UUID | New in `[email protected]` |
| [`uuid.version()`](#uuidversionstr) | Detect RFC version of a UUID | New in `[email protected]` |
@@ -251,6 +252,29 @@ 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) |
| [`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` |
Example:
```javascript
import { v7 as uuidv7 } from 'uuid';
uuidv7(); // ⇨ '01695553-c90c-7aad-9bdd-330d7b3dcb6d'
```
### uuid.validate(str)
Test a string to see if it is a valid UUID
@@ -325,6 +349,7 @@ Usage:
uuid v3 <name> <namespace uuid>
uuid v4
uuid v5 <name> <namespace uuid>
uuid v7
uuid --help
Note: <namespace uuid> may be "URL" or "DNS" to use the corresponding UUIDs
+26 -1
View File
@@ -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)
- NodeJS 12+ ([LTS releases](https://github.com/nodejs/Release))
@@ -73,6 +73,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 | `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 +260,29 @@ 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) |
| [`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` |
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
@@ -333,6 +357,7 @@ Usage:
uuid v3 <name> <namespace uuid>
uuid v4
uuid v5 <name> <namespace uuid>
uuid v7
uuid --help
Note: <namespace uuid> may be "URL" or "DNS" to use the corresponding UUIDs
+4 -1
View File
@@ -20,6 +20,8 @@
"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"
@@ -35,6 +37,7 @@
{
"path": "./examples/browser-webpack/dist/v5-size.js",
"maxSize": "1.5 kB"
}
},
{ "path": "./examples/browser-webpack/dist/v7-size.js", "maxSize": "0.8 kB" }
]
}
+14
View File
@@ -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());
})
+15 -11
View File
@@ -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"
}
}
+4
View File
@@ -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));
+2 -1
View File
@@ -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
```
+4
View File
@@ -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));
+10
View File
@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<title>UUID esmodule webpack example</title>
</head>
<body>
<script type="text/javascript" src="./dist/v7.js"></script>
</body>
</html>
+8
View File
@@ -0,0 +1,8 @@
import { v7 as uuidv7 } from 'uuid';
import testpage from '../utils/testpage';
testpage(function (addTest, done) {
addTest('uuidv7()', uuidv7());
done();
});
+1
View File
@@ -3,4 +3,5 @@
<p>Please open the Developer Console to view output</p>
<script type="text/javascript" src="./dist/v1.js"></script>
<script type="text/javascript" src="./dist/v4.js"></script>
<script type="text/javascript" src="./dist/v7.js"></script>
<script type="text/javascript" src="./dist/all.js"></script>
+16
View File
@@ -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,
},
];
+3
View File
@@ -0,0 +1,3 @@
import { v7 as uuidv7 } from 'uuid';
uuidv7();
@@ -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));
+4
View File
@@ -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));
+10
View File
@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<title>UUID esmodule webpack example</title>
</head>
<body>
<script type="text/javascript" src="./dist/v7.js"></script>
</body>
</html>
+9
View File
@@ -0,0 +1,9 @@
import { v7 as uuidv7 } from 'uuid';
import testpage from '../utils/testpage';
testpage(function (addTest, done) {
addTest('uuidv7()', uuidv7());
done();
});
+1
View File
@@ -3,4 +3,5 @@
<p>Please open the Developer Console to view output</p>
<script type="text/javascript" src="./dist/v1.js"></script>
<script type="text/javascript" src="./dist/v4.js"></script>
<script type="text/javascript" src="./dist/v7.js"></script>
<script type="text/javascript" src="./dist/all.js"></script>
+3
View File
@@ -0,0 +1,3 @@
import { v7 as uuidv7 } from 'uuid';
uuidv7();
@@ -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
+4
View File
@@ -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));
+4
View File
@@ -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));
+5
View File
@@ -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);
});
+5
View File
@@ -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);
});
+3
View File
@@ -0,0 +1,3 @@
import { v7 as uuidv7 } from 'uuid';
console.log('uuidv7()', uuidv7());
+1 -1
View File
@@ -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"
+1
View File
@@ -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',
+2 -1
View File
@@ -1,7 +1,7 @@
{
"name": "uuid",
"version": "9.0.1",
"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"
@@ -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",
+1
View File
@@ -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';
+1 -1
View File
@@ -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-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000)$/i;
+6
View File
@@ -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,7 @@ function usage() {
console.log(' uuid v3 <name> <namespace uuid>');
console.log(' uuid v4');
console.log(' uuid v5 <name> <namespace uuid>');
console.log(' uuid v7');
console.log(' uuid --help');
console.log(
'\nNote: <namespace uuid> may be "URL" or "DNS" to use the corresponding UUIDs defined by RFC4122'
@@ -74,6 +76,10 @@ switch (version) {
break;
}
case 'v7':
console.log(v7());
break;
default:
usage();
process.exit(1);
+152
View File
@@ -0,0 +1,152 @@
import rng from './rng.js';
import { unsafeStringify } from './stringify.js';
/**
* UUID V7 - Unix Epoch time-based UUID
*
* 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 9562:https://www.rfc-editor.org/rfc/rfc9562.html Universally Unique
* IDentifiers (UUIDs)
*
* Sample V7 value:
* https://www.rfc-editor.org/rfc/rfc9562.html#name-example-of-a-uuidv7-value
*
* 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
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | 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);
// 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();
// 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
if (seq > 0x7fffffff) {
seq = 0x7fffffff;
}
// split provided seq into high/low parts
seqHigh = (seq >>> 19) & 0xfff;
seqLow = seq & 0x7ffff;
}
// randomly initialize seq
if (seqHigh === null || seqLow === null) {
seqHigh = rnds[6] & 0x7f;
seqHigh = (seqHigh << 8) | rnds[7];
seqLow = rnds[8] & 0x3f; // pad for var
seqLow = (seqLow << 8) | rnds[9];
seqLow = (seqLow << 5) | (rnds[10] >>> 3);
}
// increment seq if within msecs window
if (msecs + 10000 > _msecs && seq === null) {
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;
// [byte 7] remaining 8 bits of seq_hi
b[i++] = seqHigh & 0xff;
// [byte 8] - variant (2 bits), first 6 bits seq_low
b[i++] = ((seqLow >>> 13) & 0x3f) | 0x80;
// [byte 9] 8 bits seq_low
b[i++] = (seqLow >>> 5) & 0xff;
// [byte 10] remaining 5 bits seq_low, 3 bits random
b[i++] = ((seqLow << 3) & 0xff) | (rnds[10] & 0x07);
// [bytes 11-15] always random
b[i++] = rnds[11];
b[i++] = rnds[12];
b[i++] = rnds[13];
b[i++] = rnds[14];
b[i++] = rnds[15];
return buf || unsafeStringify(b);
}
export default v7;
+172
View File
@@ -0,0 +1,172 @@
import assert from 'assert';
import v7 from '../../src/v7.js';
/**
* fixture bit layout:
* 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
*
* -------------------------------
* 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 seqFixture = 0x661b189b;
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,
seq: seqFixture,
});
assert.strictEqual(id, '017f22e2-79b0-7cc3-98c4-dc0c0c07398f');
});
test('explicit options.rng produces expected result', () => {
const id = v7({
rng: () => randomBytesFixture,
msecs: msecsFixture,
seq: seqFixture,
});
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,
seq: seqFixture,
},
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,
seq: seqFixture,
},
buffer,
0
);
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);
});
});
+20 -2
View File
@@ -6,14 +6,32 @@ describe('validate', () => {
test('validate uuid', () => {
assert.strictEqual(validate(NIL), true);
// test valid UUID versions
// v1
assert.strictEqual(validate('d9428888-122b-11e1-b85c-61cd3cbb3210'), true);
assert.strictEqual(validate('109156be-c4fb-41ea-b1b4-efe1671c5836'), 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
[0, 2, 6, 8, 9, 'a', 'b', 'c', 'd', 'e', 'f'].forEach((v) => {
assert.strictEqual(
validate('12300000-0000-' + v + '000-0000-000000000000'),
false,
'version ' + v + ' should not be valid'
);
});
assert.strictEqual(validate(), false);
assert.strictEqual(validate(''), false);
+2
View File
@@ -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(''));
+1
View File
@@ -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;