diff --git a/README.md b/README.md index c47313b0..43cee5f9 100644 --- a/README.md +++ b/README.md @@ -17,97 +17,185 @@ For the creation of [RFC4122](http://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 -**Upgrading from uuid\@3?** Your code is probably okay, but check out [Upgrading From uuid\@3](#upgrading-from-uuid3) for details. +**Upgrading from `uuid@3.x`?** Your code is probably okay, but check out [Upgrading From `uuid@3.x`](#upgrading-from-uuid3x) for details. ## Quickstart +To create a random UUID... + +**1. Install** + ```shell npm install uuid ``` -Once installed, decide which type of UUID you need. RFC4122 provides for four versions, all of which are supported here. In order of popularity, they are: - -- Version 4 (random) - Created from cryptographically-strong random values -- Version 1 (timestamp) - Created from the system clock (plus random values) -- Version 5 (namespace, SHA-1) - Created from user-supplied name and namespace strings -- Version 3 (namespace, MD5) - Like version 5, above, but with a poorer hash algorithm - -**Unsure which one to use?** Use version 4 (random) unless you have a specific need for one of the other versions. See also [this FAQ](https://github.com/tc39/proposal-uuid#faq). - -### Create Version 4 (Random) UUIDs - -ECMAScript Module syntax: +**2. Create a UUID** (ES6 module syntax) ```javascript import { v4 as uuidv4 } from 'uuid'; uuidv4(); // ⇨ '9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d' ``` -CommonJS syntax: +... or using CommonJS syntax: ```javascript const { v4: uuidv4 } = require('uuid'); uuidv4(); // ⇨ '1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed' ``` -### Create Version 1 (Timestamp) UUIDs +For timestamp UUIDs, namespace UUIDs, and other options read on ... + +## API Summary + +| | | | +| --- | --- | --- | +| [`uuid.NIL`](#uuidnil) | The nil UUID string (all zeros) | New in `uuid@8.2` | +| [`uuid.parse()`](#uuidparsestr) | Convert UUID string to array of bytes | New in `uuid@8.2` | +| [`uuid.stringify()`](#uuidstringifyarr-offset) | Convert array of bytes to UUID string | New in `uuid@8.2` | +| [`uuid.v1()`](#uuidv1options-buffer-offset) | Create a version 1 (timestamp) UUID | | +| [`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.validate()`](#uuidvalidatestr) | Test a string to see if it is a valid UUID | New in `uuid@8.2` | +| [`uuid.version()`](#uuidversionstr) | Detect RFC version of a UUID | New in `uuid@8.2` | + +## API + +### uuid.NIL + +The nil UUID string (all zeros). + +Example: ```javascript -import { v1 as uuidv1 } from 'uuid'; -uuidv1(); // ⇨ '2c5ea4c0-4067-11e9-8bad-9b1deb4d3b7d' +import { NIL as NIL_UUID } from 'uuid'; + +NIL_UUID; // ⇨ '00000000-0000-0000-0000-000000000000' ``` -### Create Version 3 or Version 5 (Namespace) UUIDs +### uuid.parse(str) -⚠️ Version 3 and Version 5 UUIDs are basically the same, differing only in the underlying hash algorithm. Note that per the RFC, "_If backward compatibility is not an issue, SHA-1 [Version 5] is preferred_." +Convert UUID string to array of bytes -⚠️ If using a custom namespace **be sure to generate your own namespace UUID**. You can grab one [here](https://www.uuidgenerator.net/). +| | | +| --------- | ---------------------------------------- | +| `str` | A valid UUID `String` | +| _returns_ | `Uint8Array[16]` | +| _throws_ | `TypeError` if `str` is not a valid UUID | + +Example: ```javascript -import { v5 as uuidv5 } from 'uuid'; // For version 5 -import { v3 as uuidv3 } from 'uuid'; // For version 3 +import { parse as uuidParse } from 'uuid'; + +uuidParse('6ec0bd7f-11c0-43da-975e-2a8ad9ebae0b'); // ⇨ + // Uint8Array(16) [ + // 110, 192, 189, 127, 17, + // 192, 67, 218, 151, 94, + // 42, 138, 217, 235, 174, + // 11 + // ] +``` -// Using predefined DNS namespace (for domain names) -uuidv5('hello.example.com', uuidv5.DNS); // ⇨ 'fdda765f-fc57-5604-a269-52a7df8164ec' -uuidv3('hello.example.com', uuidv3.DNS); // ⇨ '9125a8dc-52ee-365b-a5aa-81b0b3681cf6' +### uuid.stringify(arr[, offset]) -// Using predefined URL namespace (for URLs) -uuidv5('http://example.com/hello', uuidv5.URL); // ⇨ '3bbcee75-cecc-5b56-8031-b6641c1ed1f1' -uuidv3('http://example.com/hello', uuidv3.URL); // ⇨ 'c6235813-3ba4-3801-ae84-e0a6ebb7d138' +Convert array of bytes to UUID string -// Using a custom namespace (See note, above, about generating your own -// namespace UUID) -const MY_NAMESPACE = '1b671a64-40d5-491e-99b0-da01ff1f3341'; -uuidv5('Hello, World!', MY_NAMESPACE); // ⇨ '630eb68f-e0fa-5ecc-887a-7c7a62614681' -uuidv3('Hello, World!', MY_NAMESPACE); // ⇨ 'e8b5a51d-11c8-3310-a6ab-367563f20686' +| | | +| -------------- | --------------------------------------------------------------------------- | +| `arr` | `Array`-like collection of 16 values (starting from `offset`) between 0-255 | +| [`offset` = 0] | `Number` Starting index in the Array | +| _returns_ | `String` | +| _throws_ | `TypeError` if a valid UUID string cannot be generated | + +Example: + +```javascript +import { stringify as uuidStringify } from 'uuid'; + +const uuidBytes = [110, 192, 189, 127, 17, 192, 67, 218, 151, 94, 42, 138, 217, 235, 174, 11]; + +uuidStringify(uuidBytes); // ⇨ '6ec0bd7f-11c0-43da-975e-2a8ad9ebae0b' ``` -## API +### uuid.v1([options[, buffer[, offset]]]) -### Version 4 (Random) +Create an RFC version 1 (timestamp) UUID + +| | | +| --- | --- | +| [`options`] | `Object` with one or more of the following properties: | +| [`options.node` ] | RFC "node" field as an `Array[6]` of byte values (per 4.1.6) | +| [`options.clockseq`] | RFC "clock sequence" as a `Number` between 0 - 0x3fff | +| [`options.msecs`] | RFC "timestamp" field (`Number` of milliseconds, unix epoch) | +| [`options.nsecs`] | RFC "timestamp" field (`Number` of nanseconds to add to `msecs`, should be 0-10,000) | +| [`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` | +| _throws_ | `Error` if more than 10M UUIDs/sec are requested | + +Note: The default [node id](https://tools.ietf.org/html/rfc4122#section-4.1.6) (the last 12 digits in the UUID) is generated once, randomly, on process startup, and then remains unchanged for the duration of the process. + +Note: `options.random` and `options.rng` are only meaningful on the very first call to `v1()`, where they may be passed to initialize the internal `node` and `clockseq` fields. + +Example: ```javascript -import { v4 as uuidv4 } from 'uuid'; +import { v1 as uuidv1 } from 'uuid'; -// Incantations -uuidv4(); -uuidv4(options); -uuidv4(options, buffer, offset); +uuidv1(); // ⇨ '2c5ea4c0-4067-11e9-8bad-9b1deb4d3b7d' +``` + +Example using `options`: + +```javascript +import { v1 as uuidv1 } from 'uuid'; + +const v1options = { + node: [0x01, 0x23, 0x45, 0x67, 0x89, 0xab], + clockseq: 0x1234, + msecs: new Date('2011-11-01').getTime(), + nsecs: 5678, +}; +uuidv1(v1options); // ⇨ '710b962e-041c-11e1-9234-0123456789ab' ``` -Generate and return a RFC4122 version 4 UUID. +### uuid.v3(name, namespace[, buffer[, offset]]) + +Create an RFC version 3 (namespace w/ MD5) UUID + +API is identical to `v5()`, but uses "v3" instead. + +⚠️ Note: Per the RFC, "_If backward compatibility is not an issue, SHA-1 [Version 5] is preferred_." + +### uuid.v4([options[, buffer[, offset]]]) -- `options` - (Object) Optional uuid state to apply. Properties may include: - - `random` - (Number[16]) Array of 16 numbers (0-255) to use in place of randomly generated values. Takes precedence over `options.rng`. - - `rng` - (Function) Random # generator function that returns an Array[16] of byte values (0-255). Alternative to `options.random`. -- `buffer` - (Array | Buffer) Array or buffer where UUID bytes are to be written. -- `offset` - (Number) Starting index in `buffer` at which to begin writing. +Create an RFC version 4 (random) UUID + +| | | +| --- | --- | +| [`options`] | `Object` with one or more of the following properties: | +| [`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 { v4 as uuidv4 } from 'uuid'; -Returns `buffer`, if specified, otherwise the string form of the UUID +uuidv4(); // ⇨ '1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed' +``` -Example: Generate string UUID with predefined `random` values +Example using predefined `random` values: ```javascript +import { v4 as uuidv4 } from 'uuid'; + const v4options = { random: [ 0x10, @@ -131,137 +219,75 @@ const v4options = { uuidv4(v4options); // ⇨ '109156be-c4fb-41ea-b1b4-efe1671c5836' ``` -Example: Generate two IDs in a single buffer +### uuid.v5(name, namespace[, buffer[, offset]]) -```javascript -const buffer = new Array(); -uuidv4(null, buffer, 0); // ⇨ - // [ - // 27, 157, 107, 205, 187, - // 253, 75, 45, 155, 93, - // 171, 141, 251, 189, 75, - // 237 - // ] -uuidv4(null, buffer, 16); // ⇨ - // [ - // 27, 157, 107, 205, 187, 253, 75, 45, - // 155, 93, 171, 141, 251, 189, 75, 237, - // 155, 29, 235, 77, 59, 125, 75, 173, - // 155, 221, 43, 13, 123, 61, 203, 109 - // ] -``` - -### Version 1 (Timestamp) - -```javascript -import { v1 as uuidv1 } from 'uuid'; +Createa an RFC version 5 (namespace w/ SHA-1) UUID -// Incantations -uuidv1(); -uuidv1(options); -uuidv1(options, buffer, offset); -``` +| | | +| --- | --- | +| `name` | `String \| Array` | +| `namespace` | `String \| Array[16]` Namespace UUID | +| [`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` | -Generate and return a RFC4122 version 1 (timestamp) UUID. +Note: The RFC `DNS` and `URL` namespaces are available as `v5.DNS` and `v5.URL`. -- `options` - (Object) Optional uuid state to apply. Properties may include: - - `node` - (Array) Node id as Array of 6 bytes (per 4.1.6). Default: Randomly generated ID. See note 1. - - `clockseq` - (Number between 0 - 0x3fff) RFC clock sequence. Default: An internally maintained clockseq is used. - - `msecs` - (Number) Time in milliseconds since unix Epoch. Default: The current time is used. - - `nsecs` - (Number between 0-9999) additional time, in 100-nanosecond units. Ignored if `msecs` is unspecified. Default: internal uuid counter is used, as per 4.2.1.2. - - `random` - (Number[16]) Array of 16 numbers (0-255) to use for initialization of `node` and `clockseq` as described above. Takes precedence over `options.rng`. - - `rng` - (Function) Random # generator function that returns an Array[16] of byte values (0-255). Alternative to `options.random`. -- `buffer` - (Array | Buffer) Array or buffer where UUID bytes are to be written. -- `offset` - (Number) Starting index in `buffer` at which to begin writing. - -Returns `buffer`, if specified, otherwise the string form of the UUID - -Note: The default [node id](https://tools.ietf.org/html/rfc4122#section-4.1.6) (the last 12 digits in the UUID) is generated once, randomly, on process startup, and then remains unchanged for the duration of the process. - -Example: Generate string UUID with fully-specified options +Example with custom namespace: ```javascript -const v1options = { - node: [0x01, 0x23, 0x45, 0x67, 0x89, 0xab], - clockseq: 0x1234, - msecs: new Date('2011-11-01').getTime(), - nsecs: 5678, -}; -uuidv1(v1options); // ⇨ '710b962e-041c-11e1-9234-0123456789ab' -``` +import { v5 as uuidv5 } from 'uuid'; -Example: In-place generation of two binary IDs +// Define a custom namespace. Readers, create your own using something like +// https://www.uuidgenerator.net/ +const MY_NAMESPACE = '1b671a64-40d5-491e-99b0-da01ff1f3341'; -```javascript -// Generate two ids in an array -const arr = new Array(); -uuidv1(null, arr, 0); // ⇨ - // [ - // 44, 94, 164, 192, 64, 103, - // 17, 233, 146, 52, 155, 29, - // 235, 77, 59, 125 - // ] -uuidv1(null, arr, 16); // ⇨ - // [ - // 44, 94, 164, 192, 64, 103, 17, 233, - // 146, 52, 155, 29, 235, 77, 59, 125, - // 44, 94, 164, 193, 64, 103, 17, 233, - // 146, 52, 155, 29, 235, 77, 59, 125 - // ] +uuidv5('Hello, World!', MY_NAMESPACE); // ⇨ '630eb68f-e0fa-5ecc-887a-7c7a62614681' ``` -### Version 5 (Namespace) +Example with RFC `URL` namespace: ```javascript import { v5 as uuidv5 } from 'uuid'; -// Incantations -uuidv5(name, namespace); -uuidv5(name, namespace, buffer); -uuidv5(name, namespace, buffer, offset); +uuidv5('https://www.w3.org/', uuidv5.URL); // ⇨ 'c106a26a-21bb-5538-8bf2-57095d1976c1' ``` -Generate and return a RFC4122 version 5 UUID. +### uuid.validate(str) -- `name` - (String | Array[]) "name" to create UUID with -- `namespace` - (String | Array[]) "namespace" UUID either as a String or Array[16] of byte values -- `buffer` - (Array | Buffer) Array or buffer where UUID bytes are to be written. -- `offset` - (Number) Starting index in `buffer` at which to begin writing. Default = 0 +Test a string to see if it is a valid UUID -Returns `buffer`, if specified, otherwise the string form of the UUID +| | | +| --------- | --------------------------------------------------- | +| `str` | `String` to validate | +| _returns_ | `true` if string is a valid UUID, `false` otherwise | Example: ```javascript -uuidv5('hello world', MY_NAMESPACE); // ⇨ '9f282611-e0fd-5650-8953-89c8e342da0b' -``` - -### Version 3 (Namespace) - -⚠️ Note: Per the RFC, "_If backward compatibility is not an issue, SHA-1 [Version 5] is preferred_." - -```javascript -import { v3 as uuidv3 } from 'uuid'; +import { validate as uuidValidate } from 'uuid'; -// Incantations -uuidv3(name, namespace); -uuidv3(name, namespace, buffer); -uuidv3(name, namespace, buffer, offset); +uuidValidate('not a UUID'); // ⇨ false +uuidValidate('6ec0bd7f-11c0-43da-975e-2a8ad9ebae0b'); // ⇨ true ``` -Generate and return a RFC4122 version 3 UUID. +### uuid.version(str) -- `name` - (String | Array[]) "name" to create UUID with -- `namespace` - (String | Array[]) "namespace" UUID either as a String or Array[16] of byte values -- `buffer` - (Array | Buffer) Array or buffer where UUID bytes are to be written. -- `offset` - (Number) Starting index in `buffer` at which to begin writing. Default = 0 +Detect RFC version of a UUID -Returns `buffer`, if specified, otherwise the string form of the UUID +| | | +| --------- | ---------------------------------------- | +| `str` | A valid UUID `String` | +| _returns_ | `Number` The RFC version of the UUID | +| _throws_ | `TypeError` if `str` is not a valid UUID | Example: ```javascript -uuidv3('hello world', MY_NAMESPACE); // ⇨ '042ffd34-d989-321c-ad06-f60826172424' +import { version as uuidVersion } from 'uuid'; + +uuidVersion('45637ec4-c85f-11ea-87d0-0242ac130003'); // ⇨ 1 +uuidVersion('6ec0bd7f-11c0-43da-975e-2a8ad9ebae0b'); // ⇨ 4 ``` ## Command Line @@ -340,7 +366,7 @@ To load this module directly into older browsers you can use the [UMD (Universal ``` -These CDNs all provide the same [`uuidv4()`](#version-4-random) method: +These CDNs all provide the same [`uuidv4()`](#uuidv4options-buffer-offset) method: ```html ``` -Methods for the other algorithms ([`uuidv1()`](#version-1-timestamp), [`uuidv3()`](#version-3-namespace) and [`uuidv5()`](#version-5-namespace)) are available from the files `uuidv1.min.js`, `uuidv3.min.js` and `uuidv5.min.js` respectively. +Methods for the other algorithms ([`uuidv1()`](#uuidv1options-buffer-offset), [`uuidv3()`](#uuidv3name-namespace-buffer-offset) and [`uuidv5()`](#uuidv5name-namespace-buffer-offset)) are available from the files `uuidv1.min.js`, `uuidv3.min.js` and `uuidv5.min.js` respectively. ## "getRandomValues() not supported" @@ -368,11 +394,11 @@ import { v4 as uuidv4 } from 'uuid'; [In Edge <= 18, Web Crypto is not supported in Web Workers or Service Workers](https://caniuse.com/#feat=cryptography) and we are not aware of a polyfill (let us know if you find one, please). -## Upgrading From uuid\@7 +## Upgrading From `uuid@7.x` ### Only Named Exports Supported When Using with Node.js ESM -uuid\@7 did not come with native ECMAScript Module (ESM) support for Node.js. Importing it in Node.js ESM consequently imported the CommonJS source with a default export. This library now comes with true Node.js ESM support and only provides named exports. +`uuid@7.x` did not come with native ECMAScript Module (ESM) support for Node.js. Importing it in Node.js ESM consequently imported the CommonJS source with a default export. This library now comes with true Node.js ESM support and only provides named exports. Instead of doing: @@ -390,24 +416,24 @@ uuidv4(); ### Deep Requires No Longer Supported -Deep requires like `require('uuid/v4')` [which have been deprecated in uuid\@7](#deep-requires-now-deprecated) are no longer supported. +Deep requires like `require('uuid/v4')` [which have been deprecated in `uuid@7.x`](#deep-requires-now-deprecated) are no longer supported. -## Upgrading From uuid\@3 +## Upgrading From `uuid@3.x` -"_Wait... what happened to uuid\@4 - uuid\@6?!?_" +"_Wait... what happened to `uuid@4.x` - `uuid@6.x`?!?_" -In order to avoid confusion with RFC [version 4](#version-4-random) and [version 5](#version-5-namespace) UUIDs, and a possible [version 6](http://gh.peabody.io/uuidv6/), releases 4 thru 6 of this module have been skipped. Hence, how we're now at uuid\@7. +In order to avoid confusion with RFC [version 4](#uuidv4options-buffer-offset) and [version 5](#uuidv5name-namespace-buffer-offset) UUIDs, and a possible [version 6](http://gh.peabody.io/uuidv6/), releases 4 thru 6 of this module have been skipped. ### Deep Requires Now Deprecated -uuid\@3 encouraged the use of deep requires to minimize the bundle size of browser builds: +`uuid@3.x` encouraged the use of deep requires to minimize the bundle size of browser builds: ```javascript const uuidv4 = require('uuid/v4'); // <== NOW DEPRECATED! uuidv4(); ``` -As of uuid\@7 this library now provides ECMAScript modules builds, which allow packagers like Webpack and Rollup to do "tree-shaking" to remove dead code. Instead, use the `import` syntax: +As of `uuid@7.x` this library now provides ECMAScript modules builds, which allow packagers like Webpack and Rollup to do "tree-shaking" to remove dead code. Instead, use the `import` syntax: ```javascript import { v4 as uuidv4 } from 'uuid'; @@ -423,13 +449,13 @@ uuidv4(); ### Default Export Removed -uuid\@3 was exporting the Version 4 UUID method as a default export: +`uuid@3.x` was exporting the Version 4 UUID method as a default export: ```javascript const uuid = require('uuid'); // <== REMOVED! ``` -This usage pattern was already discouraged in uuid\@3 and has been removed in uuid\@7. +This usage pattern was already discouraged in `uuid@3.x` and has been removed in `uuid@7.x`. ---- Markdown generated from [README_js.md](README_js.md) by [![RunMD Logo](http://i.imgur.com/h0FVyzU.png)](https://github.com/broofa/runmd) \ No newline at end of file diff --git a/README_js.md b/README_js.md index 581cc51f..0327e1fc 100644 --- a/README_js.md +++ b/README_js.md @@ -29,97 +29,179 @@ For the creation of [RFC4122](http://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 -**Upgrading from uuid\@3?** Your code is probably okay, but check out [Upgrading From uuid\@3](#upgrading-from-uuid3) for details. +**Upgrading from `uuid@3.x`?** Your code is probably okay, but check out [Upgrading From `uuid@3.x`](#upgrading-from-uuid3x) for details. ## Quickstart +To create a random UUID... + +**1. Install** + ```shell npm install uuid ``` -Once installed, decide which type of UUID you need. RFC4122 provides for four versions, all of which are supported here. In order of popularity, they are: - -- Version 4 (random) - Created from cryptographically-strong random values -- Version 1 (timestamp) - Created from the system clock (plus random values) -- Version 5 (namespace, SHA-1) - Created from user-supplied name and namespace strings -- Version 3 (namespace, MD5) - Like version 5, above, but with a poorer hash algorithm - -**Unsure which one to use?** Use version 4 (random) unless you have a specific need for one of the other versions. See also [this FAQ](https://github.com/tc39/proposal-uuid#faq). - -### Create Version 4 (Random) UUIDs - -ECMAScript Module syntax: +**2. Create a UUID** (ES6 module syntax) -```javascript --run v4 +```javascript --run import { v4 as uuidv4 } from 'uuid'; uuidv4(); // RESULT ``` -CommonJS syntax: +... or using CommonJS syntax: -```javascript --run v4cjs +```javascript --run const { v4: uuidv4 } = require('uuid'); uuidv4(); // RESULT ``` -### Create Version 1 (Timestamp) UUIDs +For timestamp UUIDs, namespace UUIDs, and other options read on ... -```javascript --run v1 -import { v1 as uuidv1 } from 'uuid'; -uuidv1(); // RESULT +## API Summary + +| | | | +| --- | --- | --- | +| [`uuid.NIL`](#uuidnil) | The nil UUID string (all zeros) | New in `uuid@8.2` | +| [`uuid.parse()`](#uuidparsestr) | Convert UUID string to array of bytes | New in `uuid@8.2` | +| [`uuid.stringify()`](#uuidstringifyarr-offset) | Convert array of bytes to UUID string | New in `uuid@8.2` | +| [`uuid.v1()`](#uuidv1options-buffer-offset) | Create a version 1 (timestamp) UUID | | +| [`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.validate()`](#uuidvalidatestr) | Test a string to see if it is a valid UUID | New in `uuid@8.2` | +| [`uuid.version()`](#uuidversionstr) | Detect RFC version of a UUID | New in `uuid@8.2` | + +## API + +### uuid.NIL + +The nil UUID string (all zeros). + +Example: + +```javascript --run +import { NIL as NIL_UUID } from 'uuid'; + +NIL_UUID; // RESULT ``` -### Create Version 3 or Version 5 (Namespace) UUIDs +### uuid.parse(str) + +Convert UUID string to array of bytes -⚠️ Version 3 and Version 5 UUIDs are basically the same, differing only in the underlying hash algorithm. Note that per the RFC, "_If backward compatibility is not an issue, SHA-1 [Version 5] is preferred_." +| | | +| --------- | ---------------------------------------- | +| `str` | A valid UUID `String` | +| _returns_ | `Uint8Array[16]` | +| _throws_ | `TypeError` if `str` is not a valid UUID | -⚠️ If using a custom namespace **be sure to generate your own namespace UUID**. You can grab one [here](https://www.uuidgenerator.net/). +Example: -```javascript --run v35 -import { v5 as uuidv5 } from 'uuid'; // For version 5 -import { v3 as uuidv3 } from 'uuid'; // For version 3 +```javascript --run +import { parse as uuidParse } from 'uuid'; -// Using predefined DNS namespace (for domain names) -uuidv5('hello.example.com', uuidv5.DNS); // RESULT -uuidv3('hello.example.com', uuidv3.DNS); // RESULT +uuidParse('6ec0bd7f-11c0-43da-975e-2a8ad9ebae0b'); // RESULT +``` -// Using predefined URL namespace (for URLs) -uuidv5('http://example.com/hello', uuidv5.URL); // RESULT -uuidv3('http://example.com/hello', uuidv3.URL); // RESULT +### uuid.stringify(arr[, offset]) -// Using a custom namespace (See note, above, about generating your own -// namespace UUID) -const MY_NAMESPACE = '1b671a64-40d5-491e-99b0-da01ff1f3341'; -uuidv5('Hello, World!', MY_NAMESPACE); // RESULT -uuidv3('Hello, World!', MY_NAMESPACE); // RESULT +Convert array of bytes to UUID string + +| | | +| -------------- | --------------------------------------------------------------------------- | +| `arr` | `Array`-like collection of 16 values (starting from `offset`) between 0-255 | +| [`offset` = 0] | `Number` Starting index in the Array | +| _returns_ | `String` | +| _throws_ | `TypeError` if a valid UUID string cannot be generated | + +Example: + +```javascript --run +import { stringify as uuidStringify } from 'uuid'; + +const uuidBytes = [110, 192, 189, 127, 17, 192, 67, 218, 151, 94, 42, 138, 217, 235, 174, 11]; + +uuidStringify(uuidBytes); // RESULT ``` -## API +### uuid.v1([options[, buffer[, offset]]]) -### Version 4 (Random) +Create an RFC version 1 (timestamp) UUID -```javascript -import { v4 as uuidv4 } from 'uuid'; +| | | +| --- | --- | +| [`options`] | `Object` with one or more of the following properties: | +| [`options.node` ] | RFC "node" field as an `Array[6]` of byte values (per 4.1.6) | +| [`options.clockseq`] | RFC "clock sequence" as a `Number` between 0 - 0x3fff | +| [`options.msecs`] | RFC "timestamp" field (`Number` of milliseconds, unix epoch) | +| [`options.nsecs`] | RFC "timestamp" field (`Number` of nanseconds to add to `msecs`, should be 0-10,000) | +| [`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` | +| _throws_ | `Error` if more than 10M UUIDs/sec are requested | -// Incantations -uuidv4(); -uuidv4(options); -uuidv4(options, buffer, offset); +Note: The default [node id](https://tools.ietf.org/html/rfc4122#section-4.1.6) (the last 12 digits in the UUID) is generated once, randomly, on process startup, and then remains unchanged for the duration of the process. + +Note: `options.random` and `options.rng` are only meaningful on the very first call to `v1()`, where they may be passed to initialize the internal `node` and `clockseq` fields. + +Example: + +```javascript --run +import { v1 as uuidv1 } from 'uuid'; + +uuidv1(); // RESULT ``` -Generate and return a RFC4122 version 4 UUID. +Example using `options`: -- `options` - (Object) Optional uuid state to apply. Properties may include: - - `random` - (Number[16]) Array of 16 numbers (0-255) to use in place of randomly generated values. Takes precedence over `options.rng`. - - `rng` - (Function) Random # generator function that returns an Array[16] of byte values (0-255). Alternative to `options.random`. -- `buffer` - (Array | Buffer) Array or buffer where UUID bytes are to be written. -- `offset` - (Number) Starting index in `buffer` at which to begin writing. +```javascript --run +import { v1 as uuidv1 } from 'uuid'; -Returns `buffer`, if specified, otherwise the string form of the UUID +const v1options = { + node: [0x01, 0x23, 0x45, 0x67, 0x89, 0xab], + clockseq: 0x1234, + msecs: new Date('2011-11-01').getTime(), + nsecs: 5678, +}; +uuidv1(v1options); // RESULT +``` + +### uuid.v3(name, namespace[, buffer[, offset]]) + +Create an RFC version 3 (namespace w/ MD5) UUID + +API is identical to `v5()`, but uses "v3" instead. + +⚠️ Note: Per the RFC, "_If backward compatibility is not an issue, SHA-1 [Version 5] is preferred_." + +### uuid.v4([options[, buffer[, offset]]]) + +Create an RFC version 4 (random) UUID + +| | | +| --- | --- | +| [`options`] | `Object` with one or more of the following properties: | +| [`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 { v4 as uuidv4 } from 'uuid'; + +uuidv4(); // RESULT +``` + +Example using predefined `random` values: -Example: Generate string UUID with predefined `random` values +```javascript --run +import { v4 as uuidv4 } from 'uuid'; -```javascript --run v4 const v4options = { random: [ 0x10, @@ -143,114 +225,75 @@ const v4options = { uuidv4(v4options); // RESULT ``` -Example: Generate two IDs in a single buffer +### uuid.v5(name, namespace[, buffer[, offset]]) -```javascript --run v4 -const buffer = new Array(); -uuidv4(null, buffer, 0); // RESULT -uuidv4(null, buffer, 16); // RESULT -``` - -### Version 1 (Timestamp) - -```javascript -import { v1 as uuidv1 } from 'uuid'; +Createa an RFC version 5 (namespace w/ SHA-1) UUID -// Incantations -uuidv1(); -uuidv1(options); -uuidv1(options, buffer, offset); -``` +| | | +| --- | --- | +| `name` | `String \| Array` | +| `namespace` | `String \| Array[16]` Namespace UUID | +| [`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` | -Generate and return a RFC4122 version 1 (timestamp) UUID. +Note: The RFC `DNS` and `URL` namespaces are available as `v5.DNS` and `v5.URL`. -- `options` - (Object) Optional uuid state to apply. Properties may include: - - `node` - (Array) Node id as Array of 6 bytes (per 4.1.6). Default: Randomly generated ID. See note 1. - - `clockseq` - (Number between 0 - 0x3fff) RFC clock sequence. Default: An internally maintained clockseq is used. - - `msecs` - (Number) Time in milliseconds since unix Epoch. Default: The current time is used. - - `nsecs` - (Number between 0-9999) additional time, in 100-nanosecond units. Ignored if `msecs` is unspecified. Default: internal uuid counter is used, as per 4.2.1.2. - - `random` - (Number[16]) Array of 16 numbers (0-255) to use for initialization of `node` and `clockseq` as described above. Takes precedence over `options.rng`. - - `rng` - (Function) Random # generator function that returns an Array[16] of byte values (0-255). Alternative to `options.random`. -- `buffer` - (Array | Buffer) Array or buffer where UUID bytes are to be written. -- `offset` - (Number) Starting index in `buffer` at which to begin writing. +Example with custom namespace: -Returns `buffer`, if specified, otherwise the string form of the UUID - -Note: The default [node id](https://tools.ietf.org/html/rfc4122#section-4.1.6) (the last 12 digits in the UUID) is generated once, randomly, on process startup, and then remains unchanged for the duration of the process. - -Example: Generate string UUID with fully-specified options - -```javascript --run v1 -const v1options = { - node: [0x01, 0x23, 0x45, 0x67, 0x89, 0xab], - clockseq: 0x1234, - msecs: new Date('2011-11-01').getTime(), - nsecs: 5678, -}; -uuidv1(v1options); // RESULT -``` +```javascript --run +import { v5 as uuidv5 } from 'uuid'; -Example: In-place generation of two binary IDs +// Define a custom namespace. Readers, create your own using something like +// https://www.uuidgenerator.net/ +const MY_NAMESPACE = '1b671a64-40d5-491e-99b0-da01ff1f3341'; -```javascript --run v1 -// Generate two ids in an array -const arr = new Array(); -uuidv1(null, arr, 0); // RESULT -uuidv1(null, arr, 16); // RESULT +uuidv5('Hello, World!', MY_NAMESPACE); // RESULT ``` -### Version 5 (Namespace) +Example with RFC `URL` namespace: -```javascript +```javascript --run import { v5 as uuidv5 } from 'uuid'; -// Incantations -uuidv5(name, namespace); -uuidv5(name, namespace, buffer); -uuidv5(name, namespace, buffer, offset); +uuidv5('https://www.w3.org/', uuidv5.URL); // RESULT ``` -Generate and return a RFC4122 version 5 UUID. +### uuid.validate(str) -- `name` - (String | Array[]) "name" to create UUID with -- `namespace` - (String | Array[]) "namespace" UUID either as a String or Array[16] of byte values -- `buffer` - (Array | Buffer) Array or buffer where UUID bytes are to be written. -- `offset` - (Number) Starting index in `buffer` at which to begin writing. Default = 0 +Test a string to see if it is a valid UUID -Returns `buffer`, if specified, otherwise the string form of the UUID +| | | +| --------- | --------------------------------------------------- | +| `str` | `String` to validate | +| _returns_ | `true` if string is a valid UUID, `false` otherwise | Example: -```javascript --run v35 -uuidv5('hello world', MY_NAMESPACE); // RESULT -``` - -### Version 3 (Namespace) +```javascript --run +import { validate as uuidValidate } from 'uuid'; -⚠️ Note: Per the RFC, "_If backward compatibility is not an issue, SHA-1 [Version 5] is preferred_." - -```javascript -import { v3 as uuidv3 } from 'uuid'; - -// Incantations -uuidv3(name, namespace); -uuidv3(name, namespace, buffer); -uuidv3(name, namespace, buffer, offset); +uuidValidate('not a UUID'); // RESULT +uuidValidate('6ec0bd7f-11c0-43da-975e-2a8ad9ebae0b'); // RESULT ``` -Generate and return a RFC4122 version 3 UUID. +### uuid.version(str) -- `name` - (String | Array[]) "name" to create UUID with -- `namespace` - (String | Array[]) "namespace" UUID either as a String or Array[16] of byte values -- `buffer` - (Array | Buffer) Array or buffer where UUID bytes are to be written. -- `offset` - (Number) Starting index in `buffer` at which to begin writing. Default = 0 +Detect RFC version of a UUID -Returns `buffer`, if specified, otherwise the string form of the UUID +| | | +| --------- | ---------------------------------------- | +| `str` | A valid UUID `String` | +| _returns_ | `Number` The RFC version of the UUID | +| _throws_ | `TypeError` if `str` is not a valid UUID | Example: -```javascript --run v35 -uuidv3('hello world', MY_NAMESPACE); // RESULT +```javascript --run +import { version as uuidVersion } from 'uuid'; + +uuidVersion('45637ec4-c85f-11ea-87d0-0242ac130003'); // RESULT +uuidVersion('6ec0bd7f-11c0-43da-975e-2a8ad9ebae0b'); // RESULT ``` ## Command Line @@ -329,7 +372,7 @@ To load this module directly into older browsers you can use the [UMD (Universal ``` -These CDNs all provide the same [`uuidv4()`](#version-4-random) method: +These CDNs all provide the same [`uuidv4()`](#uuidv4options-buffer-offset) method: ```html ``` -Methods for the other algorithms ([`uuidv1()`](#version-1-timestamp), [`uuidv3()`](#version-3-namespace) and [`uuidv5()`](#version-5-namespace)) are available from the files `uuidv1.min.js`, `uuidv3.min.js` and `uuidv5.min.js` respectively. +Methods for the other algorithms ([`uuidv1()`](#uuidv1options-buffer-offset), [`uuidv3()`](#uuidv3name-namespace-buffer-offset) and [`uuidv5()`](#uuidv5name-namespace-buffer-offset)) are available from the files `uuidv1.min.js`, `uuidv3.min.js` and `uuidv5.min.js` respectively. ## "getRandomValues() not supported" @@ -357,11 +400,11 @@ import { v4 as uuidv4 } from 'uuid'; [In Edge <= 18, Web Crypto is not supported in Web Workers or Service Workers](https://caniuse.com/#feat=cryptography) and we are not aware of a polyfill (let us know if you find one, please). -## Upgrading From uuid\@7 +## Upgrading From `uuid@7.x` ### Only Named Exports Supported When Using with Node.js ESM -uuid\@7 did not come with native ECMAScript Module (ESM) support for Node.js. Importing it in Node.js ESM consequently imported the CommonJS source with a default export. This library now comes with true Node.js ESM support and only provides named exports. +`uuid@7.x` did not come with native ECMAScript Module (ESM) support for Node.js. Importing it in Node.js ESM consequently imported the CommonJS source with a default export. This library now comes with true Node.js ESM support and only provides named exports. Instead of doing: @@ -379,24 +422,24 @@ uuidv4(); ### Deep Requires No Longer Supported -Deep requires like `require('uuid/v4')` [which have been deprecated in uuid\@7](#deep-requires-now-deprecated) are no longer supported. +Deep requires like `require('uuid/v4')` [which have been deprecated in `uuid@7.x`](#deep-requires-now-deprecated) are no longer supported. -## Upgrading From uuid\@3 +## Upgrading From `uuid@3.x` -"_Wait... what happened to uuid\@4 - uuid\@6?!?_" +"_Wait... what happened to `uuid@4.x` - `uuid@6.x`?!?_" -In order to avoid confusion with RFC [version 4](#version-4-random) and [version 5](#version-5-namespace) UUIDs, and a possible [version 6](http://gh.peabody.io/uuidv6/), releases 4 thru 6 of this module have been skipped. Hence, how we're now at uuid\@7. +In order to avoid confusion with RFC [version 4](#uuidv4options-buffer-offset) and [version 5](#uuidv5name-namespace-buffer-offset) UUIDs, and a possible [version 6](http://gh.peabody.io/uuidv6/), releases 4 thru 6 of this module have been skipped. ### Deep Requires Now Deprecated -uuid\@3 encouraged the use of deep requires to minimize the bundle size of browser builds: +`uuid@3.x` encouraged the use of deep requires to minimize the bundle size of browser builds: ```javascript const uuidv4 = require('uuid/v4'); // <== NOW DEPRECATED! uuidv4(); ``` -As of uuid\@7 this library now provides ECMAScript modules builds, which allow packagers like Webpack and Rollup to do "tree-shaking" to remove dead code. Instead, use the `import` syntax: +As of `uuid@7.x` this library now provides ECMAScript modules builds, which allow packagers like Webpack and Rollup to do "tree-shaking" to remove dead code. Instead, use the `import` syntax: ```javascript import { v4 as uuidv4 } from 'uuid'; @@ -412,10 +455,10 @@ uuidv4(); ### Default Export Removed -uuid\@3 was exporting the Version 4 UUID method as a default export: +`uuid@3.x` was exporting the Version 4 UUID method as a default export: ```javascript const uuid = require('uuid'); // <== REMOVED! ``` -This usage pattern was already discouraged in uuid\@3 and has been removed in uuid\@7. +This usage pattern was already discouraged in `uuid@3.x` and has been removed in `uuid@7.x`. diff --git a/bundlewatch.config.json b/bundlewatch.config.json index e8f62881..91b8b756 100644 --- a/bundlewatch.config.json +++ b/bundlewatch.config.json @@ -1,13 +1,13 @@ { "files": [ - { "path": "./examples/browser-rollup/dist/v1-size.js", "maxSize": "0.8 kB" }, - { "path": "./examples/browser-rollup/dist/v3-size.js", "maxSize": "1.8 kB" }, - { "path": "./examples/browser-rollup/dist/v4-size.js", "maxSize": "0.5 kB" }, - { "path": "./examples/browser-rollup/dist/v5-size.js", "maxSize": "1.2 kB" }, + { "path": "./examples/browser-rollup/dist/v1-size.js", "maxSize": "1.0 kB" }, + { "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-webpack/dist/v1-size.js", "maxSize": "1.0 kB" }, - { "path": "./examples/browser-webpack/dist/v3-size.js", "maxSize": "2.0 kB" }, - { "path": "./examples/browser-webpack/dist/v4-size.js", "maxSize": "0.7 kB" }, - { "path": "./examples/browser-webpack/dist/v5-size.js", "maxSize": "1.4 kB" } + { "path": "./examples/browser-webpack/dist/v1-size.js", "maxSize": "1.3 kB" }, + { "path": "./examples/browser-webpack/dist/v3-size.js", "maxSize": "2.5 kB" }, + { "path": "./examples/browser-webpack/dist/v4-size.js", "maxSize": "1.0 kB" }, + { "path": "./examples/browser-webpack/dist/v5-size.js", "maxSize": "1.9 kB" } ] } diff --git a/examples/benchmark/benchmark.html b/examples/benchmark/benchmark.html index 54be63f8..2f3bf78f 100644 --- a/examples/benchmark/benchmark.html +++ b/examples/benchmark/benchmark.html @@ -5,6 +5,8 @@ + + diff --git a/examples/benchmark/benchmark.js b/examples/benchmark/benchmark.js index dc3ff19f..cc36adcb 100644 --- a/examples/benchmark/benchmark.js +++ b/examples/benchmark/benchmark.js @@ -5,46 +5,96 @@ const uuidv1 = (typeof window !== 'undefined' && window.uuidv1) || require('uuid const uuidv4 = (typeof window !== 'undefined' && window.uuidv4) || require('uuid').v4; const uuidv3 = (typeof window !== 'undefined' && window.uuidv3) || require('uuid').v3; const uuidv5 = (typeof window !== 'undefined' && window.uuidv5) || require('uuid').v5; +const uuidParse = (typeof window !== 'undefined' && window.uuidParse) || require('uuid').parse; +const uuidStringify = + (typeof window !== 'undefined' && window.uuidStringify) || require('uuid').stringify; console.log('Starting. Tests take ~1 minute to run ...'); -const array = new Array(16); - -const suite = new Benchmark.Suite({ - onError(event) { - console.error(event.target.error); - }, -}); - -suite - .add('uuidv1()', function () { - uuidv1(); - }) - .add('uuidv1() fill existing array', function () { - try { - uuidv1(null, array, 0); - } catch (err) { - // The spec (https://tools.ietf.org/html/rfc4122#section-4.2.1.2) defines that only 10M/s v1 - // UUIDs can be generated on a single node. This library throws an error if we hit that limit - // (which can happen on modern hardware and modern Node.js versions). - } - }) - .add('uuidv4()', function () { - uuidv4(); - }) - .add('uuidv4() fill existing array', function () { - uuidv4(null, array, 0); - }) - .add('uuidv3()', function () { - uuidv3('hello.example.com', uuidv3.DNS); - }) - .add('uuidv5()', function () { - uuidv5('hello.example.com', uuidv5.DNS); - }) - .on('cycle', function (event) { - console.log(event.target.toString()); - }) - .on('complete', function () { - console.log('Fastest is ' + this.filter('fastest').map('name')); - }) - .run(); +function testParseAndStringify() { + const suite = new Benchmark.Suite({ + onError(event) { + console.error(event.target.error); + }, + }); + + const BYTES = [ + 0x0f, + 0x5a, + 0xbc, + 0xd1, + 0xc1, + 0x94, + 0x47, + 0xf3, + 0x90, + 0x5b, + 0x2d, + 0xf7, + 0x26, + 0x3a, + 0x08, + 0x4b, + ]; + + suite + .add('uuidStringify()', function () { + uuidStringify(BYTES); + }) + .add('uuidParse()', function () { + uuidParse('0f5abcd1-c194-47f3-905b-2df7263a084b'); + }) + .on('cycle', function (event) { + console.log(event.target.toString()); + }) + .on('complete', function () { + console.log('---\n'); + }) + .run(); +} + +function testGeneration() { + const array = new Array(16); + + const suite = new Benchmark.Suite({ + onError(event) { + console.error(event.target.error); + }, + }); + + suite + .add('uuidv1()', function () { + uuidv1(); + }) + .add('uuidv1() fill existing array', function () { + try { + uuidv1(null, array, 0); + } catch (err) { + // The spec (https://tools.ietf.org/html/rfc4122#section-4.2.1.2) defines that only 10M/s v1 + // UUIDs can be generated on a single node. This library throws an error if we hit that limit + // (which can happen on modern hardware and modern Node.js versions). + } + }) + .add('uuidv4()', function () { + uuidv4(); + }) + .add('uuidv4() fill existing array', function () { + uuidv4(null, array, 0); + }) + .add('uuidv3()', function () { + uuidv3('hello.example.com', uuidv3.DNS); + }) + .add('uuidv5()', function () { + uuidv5('hello.example.com', uuidv5.DNS); + }) + .on('cycle', function (event) { + console.log(event.target.toString()); + }) + .on('complete', function () { + console.log('Fastest is ' + this.filter('fastest').map('name')); + }) + .run(); +} + +testParseAndStringify(); +testGeneration(); diff --git a/package-lock.json b/package-lock.json index 37b3a034..7958fc1b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14136,6 +14136,15 @@ "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", "dev": true }, + "random-seed": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/random-seed/-/random-seed-0.3.0.tgz", + "integrity": "sha1-2UXy4fOPSejViRNDG4v2u5N1Vs0=", + "dev": true, + "requires": { + "json-stringify-safe": "^5.0.1" + } + }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", diff --git a/package.json b/package.json index 5e15606c..8a09f079 100644 --- a/package.json +++ b/package.json @@ -73,6 +73,7 @@ "lint-staged": "10.2.11", "npm-run-all": "4.1.5", "prettier": "2.0.5", + "random-seed": "0.3.0", "rollup": "2.18.0", "rollup-plugin-terser": "6.1.0", "runmd": "1.3.2", diff --git a/rollup.config.js b/rollup.config.js index 34544dfd..37de4056 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -20,4 +20,11 @@ export default [ chunk('v3', 'uuidv3'), chunk('v4', 'uuidv4'), chunk('v5', 'uuidv5'), + + chunk('nil', 'uuidNIL'), + + chunk('version', 'uuidVersion'), + chunk('validate', 'uuidValidate'), + chunk('parse', 'uuidParse'), + chunk('stringify', 'uuidStringify'), ]; diff --git a/src/bytesToUuid.js b/src/bytesToUuid.js deleted file mode 100644 index 0f57c69f..00000000 --- a/src/bytesToUuid.js +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Convert array of 16 byte values to UUID string format of the form: - * XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX - */ -const byteToHex = []; - -for (let i = 0; i < 256; ++i) { - byteToHex.push((i + 0x100).toString(16).substr(1)); -} - -function bytesToUuid(buf, offset_) { - const offset = offset_ || 0; - - // Note: Be careful editing this code! It's been tuned for performance - // and works in ways you may not expect. See https://github.com/uuidjs/uuid/pull/434 - return ( - byteToHex[buf[offset + 0]] + - byteToHex[buf[offset + 1]] + - byteToHex[buf[offset + 2]] + - byteToHex[buf[offset + 3]] + - '-' + - byteToHex[buf[offset + 4]] + - byteToHex[buf[offset + 5]] + - '-' + - byteToHex[buf[offset + 6]] + - byteToHex[buf[offset + 7]] + - '-' + - byteToHex[buf[offset + 8]] + - byteToHex[buf[offset + 9]] + - '-' + - byteToHex[buf[offset + 10]] + - byteToHex[buf[offset + 11]] + - byteToHex[buf[offset + 12]] + - byteToHex[buf[offset + 13]] + - byteToHex[buf[offset + 14]] + - byteToHex[buf[offset + 15]] - ).toLowerCase(); -} - -export default bytesToUuid; diff --git a/src/index.js b/src/index.js index 30870af0..142ce9e4 100644 --- a/src/index.js +++ b/src/index.js @@ -2,3 +2,8 @@ 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 NIL } from './nil.js'; +export { default as version } from './version.js'; +export { default as validate } from './validate.js'; +export { default as stringify } from './stringify.js'; +export { default as parse } from './parse.js'; diff --git a/src/nil.js b/src/nil.js new file mode 100644 index 00000000..de6f830e --- /dev/null +++ b/src/nil.js @@ -0,0 +1 @@ +export default '00000000-0000-0000-0000-000000000000'; diff --git a/src/parse.js b/src/parse.js new file mode 100644 index 00000000..85edd846 --- /dev/null +++ b/src/parse.js @@ -0,0 +1,41 @@ +import validate from './validate.js'; + +function parse(uuid) { + if (!validate(uuid)) { + throw TypeError('Invalid UUID'); + } + + let v; + const arr = new Uint8Array(16); + + // Parse ########-....-....-....-............ + arr[0] = (v = parseInt(uuid.slice(0, 8), 16)) >>> 24; + arr[1] = (v >>> 16) & 0xff; + arr[2] = (v >>> 8) & 0xff; + arr[3] = v & 0xff; + + // Parse ........-####-....-....-............ + arr[4] = (v = parseInt(uuid.slice(9, 13), 16)) >>> 8; + arr[5] = v & 0xff; + + // Parse ........-....-####-....-............ + arr[6] = (v = parseInt(uuid.slice(14, 18), 16)) >>> 8; + arr[7] = v & 0xff; + + // Parse ........-....-....-####-............ + arr[8] = (v = parseInt(uuid.slice(19, 23), 16)) >>> 8; + arr[9] = v & 0xff; + + // Parse ........-....-....-....-############ + // (Use "/" to avoid 32-bit truncation when bit-shifting high-order bytes) + arr[10] = ((v = parseInt(uuid.slice(24, 36), 16)) / 0x10000000000) & 0xff; + arr[11] = (v / 0x100000000) & 0xff; + arr[12] = (v >>> 24) & 0xff; + arr[13] = (v >>> 16) & 0xff; + arr[14] = (v >>> 8) & 0xff; + arr[15] = v & 0xff; + + return arr; +} + +export default parse; diff --git a/src/regex.js b/src/regex.js new file mode 100644 index 00000000..92f79a1e --- /dev/null +++ b/src/regex.js @@ -0,0 +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; diff --git a/src/sha1-browser.js b/src/sha1-browser.js index 2bfa2cb0..377dc24c 100644 --- a/src/sha1-browser.js +++ b/src/sha1-browser.js @@ -29,6 +29,9 @@ function sha1(bytes) { for (let i = 0; i < msg.length; ++i) { bytes.push(msg.charCodeAt(i)); } + } else if (!Array.isArray(bytes)) { + // Convert Array-like to Array + bytes = Array.prototype.slice.call(bytes); } bytes.push(0x80); diff --git a/src/stringify.js b/src/stringify.js new file mode 100644 index 00000000..42fd4ecd --- /dev/null +++ b/src/stringify.js @@ -0,0 +1,51 @@ +import validate from './validate.js'; + +/** + * Convert array of 16 byte values to UUID string format of the form: + * XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX + */ +const byteToHex = []; + +for (let i = 0; i < 256; ++i) { + byteToHex.push((i + 0x100).toString(16).substr(1)); +} + +function stringify(arr, offset = 0) { + // Note: Be careful editing this code! It's been tuned for performance + // and works in ways you may not expect. See https://github.com/uuidjs/uuid/pull/434 + const uuid = ( + byteToHex[arr[offset + 0]] + + byteToHex[arr[offset + 1]] + + byteToHex[arr[offset + 2]] + + byteToHex[arr[offset + 3]] + + '-' + + byteToHex[arr[offset + 4]] + + byteToHex[arr[offset + 5]] + + '-' + + byteToHex[arr[offset + 6]] + + byteToHex[arr[offset + 7]] + + '-' + + byteToHex[arr[offset + 8]] + + byteToHex[arr[offset + 9]] + + '-' + + byteToHex[arr[offset + 10]] + + byteToHex[arr[offset + 11]] + + byteToHex[arr[offset + 12]] + + byteToHex[arr[offset + 13]] + + byteToHex[arr[offset + 14]] + + byteToHex[arr[offset + 15]] + ).toLowerCase(); + + // Consistency check for valid UUID. If this throws, it's likely due to one + // of the following: + // - One or more input array values don't map to a hex octet (leading to + // "undefined" in the uuid) + // - Invalid input values for the RFC `version` or `variant` fields + if (!validate(uuid)) { + throw TypeError('Stringified UUID is invalid'); + } + + return uuid; +} + +export default stringify; diff --git a/src/v1.js b/src/v1.js index dbf4f5ca..0643675e 100644 --- a/src/v1.js +++ b/src/v1.js @@ -1,5 +1,5 @@ import rng from './rng.js'; -import bytesToUuid from './bytesToUuid.js'; +import stringify from './stringify.js'; // **`v1()` - Generate time-based UUID** // @@ -109,7 +109,7 @@ function v1(options, buf, offset) { b[i + n] = node[n]; } - return buf || bytesToUuid(b); + return buf || stringify(b); } export default v1; diff --git a/src/v35.js b/src/v35.js index 67fa8d8c..e8706ff0 100644 --- a/src/v35.js +++ b/src/v35.js @@ -1,15 +1,5 @@ -import bytesToUuid from './bytesToUuid.js'; - -function uuidToBytes(uuid) { - // Note: We assume we're being passed a valid uuid string - const bytes = []; - - uuid.replace(/[a-fA-F0-9]{2}/g, function (hex) { - bytes.push(parseInt(hex, 16)); - }); - - return bytes; -} +import stringify from './stringify.js'; +import parse from './parse.js'; function stringToBytes(str) { str = unescape(encodeURIComponent(str)); // UTF8 escape @@ -33,19 +23,21 @@ export default function (name, version, hashfunc) { } if (typeof namespace === 'string') { - namespace = uuidToBytes(namespace); + namespace = parse(namespace); } - if (!Array.isArray(value)) { - throw TypeError('value must be an array of bytes'); + if (namespace.length !== 16) { + throw TypeError('Namespace must be array-like (16 iterable integer values, 0-255)'); } - if (!Array.isArray(namespace) || namespace.length !== 16) { - throw TypeError('namespace must be uuid string or an Array of 16 byte values'); - } + // Compute hash of namespace and value, Per 4.3 + // Future: Use spread syntax when supported on all platforms, e.g. `bytes = + // hashfunc([...namespace, ... value])` + let bytes = new Uint8Array(16 + value.length); + bytes.set(namespace); + bytes.set(value, namespace.length); + bytes = hashfunc(bytes); - // Per 4.3 - const bytes = hashfunc(namespace.concat(value)); bytes[6] = (bytes[6] & 0x0f) | version; bytes[8] = (bytes[8] & 0x3f) | 0x80; @@ -59,7 +51,7 @@ export default function (name, version, hashfunc) { return buf; } - return bytesToUuid(bytes); + return stringify(bytes); } // Function#name is not settable on some platforms (#270) diff --git a/src/v4.js b/src/v4.js index 16765828..520613a4 100644 --- a/src/v4.js +++ b/src/v4.js @@ -1,5 +1,5 @@ import rng from './rng.js'; -import bytesToUuid from './bytesToUuid.js'; +import stringify from './stringify.js'; function v4(options, buf, offset) { options = options || {}; @@ -21,7 +21,7 @@ function v4(options, buf, offset) { return buf; } - return bytesToUuid(rnds); + return stringify(rnds); } export default v4; diff --git a/src/validate.js b/src/validate.js new file mode 100644 index 00000000..22a1217e --- /dev/null +++ b/src/validate.js @@ -0,0 +1,7 @@ +import REGEX from './regex.js'; + +function validate(uuid) { + return typeof uuid === 'string' && REGEX.test(uuid); +} + +export default validate; diff --git a/src/version.js b/src/version.js new file mode 100644 index 00000000..2b993703 --- /dev/null +++ b/src/version.js @@ -0,0 +1,11 @@ +import validate from './validate.js'; + +function version(uuid) { + if (!validate(uuid)) { + throw TypeError('Invalid UUID'); + } + + return parseInt(uuid.substr(14, 1), 16); +} + +export default version; diff --git a/test/unit/parse.test.js b/test/unit/parse.test.js new file mode 100644 index 00000000..7137d953 --- /dev/null +++ b/test/unit/parse.test.js @@ -0,0 +1,68 @@ +import assert from 'assert'; +import uuidv4 from '../../src/v4.js'; +import parse from '../../src/parse.js'; +import stringify from '../../src/stringify.js'; +import gen from 'random-seed'; + +// Use deterministic PRNG for reproducable tests +const rand = gen.create('He who wonders discovers that this in itself is wonder.'); +function rng(bytes = []) { + for (let i = 0; i < 16; i++) { + bytes[i] = rand(256); + } + return bytes; +} + +describe('parse', () => { + test('String -> bytes parsing', () => { + assert.deepStrictEqual( + parse('0f5abcd1-c194-47f3-905b-2df7263a084b'), + Uint8Array.from([ + 0x0f, + 0x5a, + 0xbc, + 0xd1, + 0xc1, + 0x94, + 0x47, + 0xf3, + 0x90, + 0x5b, + 0x2d, + 0xf7, + 0x26, + 0x3a, + 0x08, + 0x4b, + ]), + ); + }); + + test('String -> bytes -> string symmetry for assorted uuids', () => { + for (let i = 0; i < 1000; i++) { + const uuid = uuidv4({ rng }); + assert.equal(stringify(parse(uuid)), uuid); + } + }); + + test('Case neutrality', () => { + // Verify upper/lower case neutrality + assert.deepStrictEqual( + parse('0f5abcd1-c194-47f3-905b-2df7263a084b'), + parse('0f5abcd1-c194-47f3-905b-2df7263a084b'.toUpperCase()), + ); + }); + + test('Null UUID case', () => { + assert.deepStrictEqual( + parse('00000000-0000-0000-0000-000000000000'), + Uint8Array.from(new Array(16).fill(0)), + ); + }); + + test('UUID validation', () => { + assert.throws(() => parse()); + assert.throws(() => parse('invalid uuid')); + assert.throws(() => parse('zyxwvuts-rqpo-nmlk-jihg-fedcba000000')); + }); +}); diff --git a/test/unit/stringify.test.js b/test/unit/stringify.test.js new file mode 100644 index 00000000..94de77d1 --- /dev/null +++ b/test/unit/stringify.test.js @@ -0,0 +1,55 @@ +import assert from 'assert'; +import stringify from '../../src/stringify.js'; + +const BYTES = [ + 0x0f, + 0x5a, + 0xbc, + 0xd1, + 0xc1, + 0x94, + 0x47, + 0xf3, + 0x90, + 0x5b, + 0x2d, + 0xf7, + 0x26, + 0x3a, + 0x08, + 0x4b, +]; + +describe('stringify', () => { + test('Stringify Array', () => { + assert.equal(stringify(BYTES), '0f5abcd1-c194-47f3-905b-2df7263a084b'); + }); + + test('Stringify TypedArray', () => { + assert.equal(stringify(Uint8Array.from(BYTES)), '0f5abcd1-c194-47f3-905b-2df7263a084b'); + assert.equal(stringify(Int32Array.from(BYTES)), '0f5abcd1-c194-47f3-905b-2df7263a084b'); + }); + + test('Stringify w/ offset', () => { + assert.equal(stringify([0, 0, 0, ...BYTES], 3), '0f5abcd1-c194-47f3-905b-2df7263a084b'); + }); + + test('Throws on not enough values', () => { + const bytes = [...BYTES]; + bytes.length = 15; + assert.throws(() => stringify(bytes)); + }); + + test('Throws on undefined value', () => { + const bytes = [...BYTES]; + delete bytes[3]; + bytes.length = 15; + assert.throws(() => stringify(bytes)); + }); + + test('Throws on invalid value', () => { + const bytes = [...BYTES]; + bytes[3] = 256; + assert.throws(() => stringify(bytes)); + }); +}); diff --git a/test/unit/validate.test.js b/test/unit/validate.test.js new file mode 100644 index 00000000..29dbfe98 --- /dev/null +++ b/test/unit/validate.test.js @@ -0,0 +1,32 @@ +import assert from 'assert'; +import validate from '../../src/validate.js'; +import NIL from '../../src/nil.js'; + +describe('validate', () => { + test('validate uuid', () => { + assert.strictEqual(validate(NIL), true); + + assert.strictEqual(validate('d9428888-122b-11e1-b85c-61cd3cbb3210'), true); + + assert.strictEqual(validate('109156be-c4fb-41ea-b1b4-efe1671c5836'), true); + + assert.strictEqual(validate('a981a0c2-68b1-35dc-bcfc-296e52ab01ec'), true); + + assert.strictEqual(validate('90123e1c-7512-523e-bb28-76fab9f2f73d'), true); + + assert.strictEqual(validate(), false); + + assert.strictEqual(validate(''), false); + + assert.strictEqual(validate('invalid uuid string'), false); + + assert.strictEqual(validate('00000000000000000000000000000000'), false); + + assert.strictEqual( + validate( + '=Y00a-f*v00b*-00c-00d#-p00f\b-00g-00h-####00i^^^-00j*1*2*3&-L00k-\n00l-/00m-----00n-fg000-00p-00r+', + ), + false, + ); + }); +}); diff --git a/test/unit/version.test.js b/test/unit/version.test.js new file mode 100644 index 00000000..9e0b8925 --- /dev/null +++ b/test/unit/version.test.js @@ -0,0 +1,33 @@ +import assert from 'assert'; +import version from '../../src/version.js'; +import NIL from '../../src/nil.js'; + +describe('version', () => { + test('check uuid version', () => { + assert.strictEqual(version(NIL), 0); + + assert.strictEqual(version('d9428888-122b-11e1-b85c-61cd3cbb3210'), 1); + + assert.strictEqual(version('109156be-c4fb-41ea-b1b4-efe1671c5836'), 4); + + assert.strictEqual(version('a981a0c2-68b1-35dc-bcfc-296e52ab01ec'), 3); + + assert.strictEqual(version('90123e1c-7512-523e-bb28-76fab9f2f73d'), 5); + + assert.throws(() => version()); + + assert.throws(() => version('')); + + assert.throws(() => version('invalid uuid string')); + + assert.throws(() => { + version('00000000000000000000000000000000'); + }); + + assert.throws(() => { + version( + '=Y00a-f*v00b*-00c-00d#-p00f\b-00g-00h-####00i^^^-00j*1*2*3&-L00k-\n00l-/00m-----00n-fg000-00p-00r+', + ); + }); + }); +}); diff --git a/wdio.conf.js b/wdio.conf.js index 4381e784..483618e8 100644 --- a/wdio.conf.js +++ b/wdio.conf.js @@ -12,6 +12,7 @@ const commonCapabilities = { name: 'browser test', 'browserstack.local': true, 'browserstack.debug': false, + 'browserstack.console': 'errors', resolution: '1024x768', };