├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── cli.js ├── dist ├── index.cjs ├── index.d.cts ├── index.d.ts └── index.js ├── docs ├── .nojekyll ├── assets │ ├── highlight.css │ ├── icons.js │ ├── icons.svg │ ├── main.js │ ├── navigation.js │ ├── search.js │ └── style.css ├── classes │ ├── UUID.html │ └── V7Generator.html ├── functions │ ├── uuidv4.html │ ├── uuidv4obj.html │ ├── uuidv7.html │ └── uuidv7obj.html ├── index.html └── modules.html ├── package-lock.json ├── package.json ├── src ├── index.cts └── index.ts ├── test ├── gen.test.js ├── index.html ├── uuid.test.js ├── uuidv4.test.js └── uuidv7.test.js └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## v1.0.2 - 2024-09-04 4 | 5 | - Updated dev dependencies 6 | - Updated documents 7 | 8 | ## v1.0.1 - 2024-06-19 9 | 10 | - Updated dev dependencies 11 | 12 | ## v1.0.0 - 2024-05-11 13 | 14 | - Initial stable release 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # uuidv7: A JavaScript implementation of UUID version 7 2 | 3 | [![npm](https://img.shields.io/npm/v/uuidv7)](https://www.npmjs.com/package/uuidv7) 4 | [![License](https://img.shields.io/npm/l/uuidv7)](https://github.com/LiosK/uuidv7/blob/main/LICENSE) 5 | 6 | ```javascript 7 | import { uuidv7 } from "uuidv7"; 8 | 9 | const result = uuidv7(); // e.g., "017fe537-bb13-7c35-b52a-cb5490cce7be" 10 | ``` 11 | 12 | On browsers and Deno: 13 | 14 | ```javascript 15 | import { uuidv7 } from "https://unpkg.com/uuidv7@^1"; 16 | 17 | const result = uuidv7(); // e.g., "017fe537-bb13-7c35-b52a-cb5490cce7be" 18 | ``` 19 | 20 | Command-line interface: 21 | 22 | ```bash 23 | $ npx uuidv7 24 | 0189f7e5-c883-7106-8272-ccb7fcba0575 25 | $ 26 | $ npx uuidv7 -n 4 27 | 0189f7ea-ae2c-7809-8aeb-b819cf5e9e7f 28 | 0189f7ea-ae2f-72b9-9be8-9c3c5a60214f 29 | 0189f7ea-ae2f-72b9-9be8-9c3d224082ef 30 | 0189f7ea-ae2f-72b9-9be8-9c3e3e8abae8 31 | ``` 32 | 33 | See [RFC 9562](https://www.rfc-editor.org/rfc/rfc9562). 34 | 35 | ## Field and bit layout 36 | 37 | This implementation produces identifiers with the following bit layout: 38 | 39 | ```text 40 | 0 1 2 3 41 | 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 42 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 43 | | unix_ts_ms | 44 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 45 | | unix_ts_ms | ver | counter | 46 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 47 | |var| counter | 48 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 49 | | rand | 50 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 51 | ``` 52 | 53 | Where: 54 | 55 | - The 48-bit `unix_ts_ms` field is dedicated to the Unix timestamp in 56 | milliseconds. 57 | - The 4-bit `ver` field is set at `0111`. 58 | - The 42-bit `counter` field accommodates a counter that ensures the increasing 59 | order of IDs generated within a millisecond. The counter is incremented by one 60 | for each new ID and is reset to a random number when the `unix_ts_ms` changes. 61 | - The 2-bit `var` field is set at `10`. 62 | - The remaining 32 `rand` bits are filled with a cryptographically strong random 63 | number. 64 | 65 | The 42-bit `counter` is sufficiently large, so you do not usually need to worry 66 | about overflow, but in an extremely rare circumstance where it overflows, this 67 | library increments the `unix_ts_ms` field to continue instant monotonic 68 | generation. As a result, the `unix_ts_ms` may have a greater value than that of 69 | the system's real-time clock. (See also [Why so large counter? (42bits)]). 70 | 71 | UUIDv7, by design, relies on the system clock to guarantee the monotonically 72 | increasing order of generated IDs. A generator may not be able to produce a 73 | monotonic sequence if the system clock goes backwards. This library ignores a 74 | clock rollback and reuses the previous `unix_ts_ms` unless the clock rollback is 75 | considered significant (by default, more than ten seconds). If such a 76 | significant rollback takes place, this library resets the generator by default 77 | and thus breaks the increasing order of generated IDs. 78 | 79 | [Why so large counter? (42bits)]: https://github.com/LiosK/uuidv7/issues/13#issuecomment-2306922356 80 | 81 | ## Other features 82 | 83 | This library also supports the generation of UUID version 4: 84 | 85 | ```javascript 86 | import { uuidv4 } from "uuidv7"; 87 | 88 | const result = uuidv4(); // e.g., "83229083-75c3-4da5-8378-f88ef1a2bcd1" 89 | ``` 90 | 91 | `uuidv7obj()` and `uuidv4obj()` return an object that represents a UUID as a 92 | 16-byte byte array: 93 | 94 | ```javascript 95 | import { uuidv7obj } from "uuidv7"; 96 | 97 | const object = uuidv7obj(); 98 | console.log(object.bytes); // Uint8Array(16) [ ... ] 99 | console.log(String(object)); // e.g., "017fea6b-b877-7aef-b422-57db9ed15e9d" 100 | 101 | console.assert(object.getVariant() === "VAR_10"); 102 | console.assert(object.getVersion() === 7); 103 | 104 | console.assert(object.clone().equals(object)); 105 | console.assert(object.compareTo(uuidv7obj()) < 0); 106 | ``` 107 | 108 | The `V7Generator` primitive allows to utilize a separate counter state from that 109 | of the global generator. It also provides a fallible variant of the generator 110 | function to give an absolute guarantee of the increasing order of UUIDs despite 111 | a significant rollback of the system timestamp source. 112 | 113 | ```javascript 114 | import { V7Generator } from "uuidv7"; 115 | 116 | const g = new V7Generator(); 117 | const x = g.generate(); 118 | const y = g.generateOrAbort(); 119 | if (y === undefined) { 120 | throw new Error("The clock went backwards by ten seconds!"); 121 | } 122 | console.assert(x.compareTo(y) < 0); 123 | ``` 124 | 125 | See the [API documentation](https://liosk.github.io/uuidv7/) for details. 126 | 127 | ## CommonJS support 128 | 129 | The CommonJS entry point is deprecated and provided for backward compatibility 130 | purposes only. The entry point is no longer tested and will be removed in the 131 | future. 132 | 133 | ## License 134 | 135 | Licensed under the Apache License, Version 2.0. 136 | 137 | ## Related project 138 | 139 | [Uuid25](https://www.npmjs.com/package/uuid25) provides the conversion to/from a 140 | condensed, sortable, case-insensitive, 25-digit Base36 representation of UUID as 141 | well as other well-known textual representations. Uuid25 is available in several 142 | languages including Go, JavaScript, Python, Rust, and Swift. 143 | 144 | ```javascript 145 | import { uuidv7obj } from "uuidv7"; 146 | import { Uuid25 } from "uuid25"; 147 | 148 | const uuid25 = Uuid25.fromBytes(uuidv7obj().bytes); 149 | 150 | console.log(uuid25.value); 151 | // e.g., "03a2s63x4x0b9mev9e88i7gpm" 152 | 153 | console.log(uuid25.toHex()); 154 | // e.g., "0189f8068f1a79b6bb21123c6accc25a" 155 | console.log(uuid25.toHyphenated()); 156 | // e.g., "0189f806-8f1a-79b6-bb21-123c6accc25a" 157 | console.log(uuid25.toBraced()); 158 | // e.g., "{0189f806-8f1a-79b6-bb21-123c6accc25a}" 159 | console.log(uuid25.toUrn()); 160 | // e.g., "urn:uuid:0189f806-8f1a-79b6-bb21-123c6accc25a" 161 | ``` 162 | -------------------------------------------------------------------------------- /cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import { EOL } from "node:os"; 4 | import { exit, stdout } from "node:process"; 5 | import { parseArgs } from "node:util"; 6 | import { pipeline } from "node:stream/promises"; 7 | 8 | import { uuidv7 } from "uuidv7"; 9 | 10 | // check arguments 11 | let options = undefined; 12 | try { 13 | options = parseArgs({ 14 | options: { 15 | count: { type: "string", short: "n" }, 16 | help: { type: "boolean", short: "h" }, 17 | }, 18 | }).values; 19 | 20 | if (options.count && !/^[0-9]+$/.test(options.count)) { 21 | throw new TypeError("Invalid argument to option '-n, --count '"); 22 | } 23 | } catch (e) { 24 | console.error(`Error: ${e.message ?? e}`); 25 | exit(1); 26 | } 27 | 28 | // print usage if requested 29 | if (options.help) { 30 | console.log("Usage: uuidv7 [-n ]"); 31 | exit(0); 32 | } 33 | 34 | // write `-n` UUIDv7 strings to `stdout` 35 | const count = parseInt(options.count ?? "1", 10); 36 | await pipeline( 37 | (function* (count) { 38 | for (let i = 0; i < count; i++) { 39 | yield uuidv7() + EOL; 40 | } 41 | })(count), 42 | stdout, 43 | ); 44 | -------------------------------------------------------------------------------- /dist/index.cjs: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /** 3 | * uuidv7: A JavaScript implementation of UUID version 7 4 | * 5 | * Copyright 2021-2024 LiosK 6 | * 7 | * @license Apache-2.0 8 | * @packageDocumentation 9 | */ 10 | Object.defineProperty(exports, "__esModule", { value: true }); 11 | exports.uuidv4obj = exports.uuidv4 = exports.uuidv7obj = exports.uuidv7 = exports.V7Generator = exports.UUID = void 0; 12 | const DIGITS = "0123456789abcdef"; 13 | /** Represents a UUID as a 16-byte byte array. */ 14 | class UUID { 15 | /** @param bytes - The 16-byte byte array representation. */ 16 | constructor(bytes) { 17 | this.bytes = bytes; 18 | } 19 | /** 20 | * Creates an object from the internal representation, a 16-byte byte array 21 | * containing the binary UUID representation in the big-endian byte order. 22 | * 23 | * This method does NOT shallow-copy the argument, and thus the created object 24 | * holds the reference to the underlying buffer. 25 | * 26 | * @throws TypeError if the length of the argument is not 16. 27 | */ 28 | static ofInner(bytes) { 29 | if (bytes.length !== 16) { 30 | throw new TypeError("not 128-bit length"); 31 | } 32 | else { 33 | return new UUID(bytes); 34 | } 35 | } 36 | /** 37 | * Builds a byte array from UUIDv7 field values. 38 | * 39 | * @param unixTsMs - A 48-bit `unix_ts_ms` field value. 40 | * @param randA - A 12-bit `rand_a` field value. 41 | * @param randBHi - The higher 30 bits of 62-bit `rand_b` field value. 42 | * @param randBLo - The lower 32 bits of 62-bit `rand_b` field value. 43 | * @throws RangeError if any field value is out of the specified range. 44 | */ 45 | static fromFieldsV7(unixTsMs, randA, randBHi, randBLo) { 46 | if (!Number.isInteger(unixTsMs) || 47 | !Number.isInteger(randA) || 48 | !Number.isInteger(randBHi) || 49 | !Number.isInteger(randBLo) || 50 | unixTsMs < 0 || 51 | randA < 0 || 52 | randBHi < 0 || 53 | randBLo < 0 || 54 | unixTsMs > 281474976710655 || 55 | randA > 0xfff || 56 | randBHi > 1073741823 || 57 | randBLo > 4294967295) { 58 | throw new RangeError("invalid field value"); 59 | } 60 | const bytes = new Uint8Array(16); 61 | bytes[0] = unixTsMs / 2 ** 40; 62 | bytes[1] = unixTsMs / 2 ** 32; 63 | bytes[2] = unixTsMs / 2 ** 24; 64 | bytes[3] = unixTsMs / 2 ** 16; 65 | bytes[4] = unixTsMs / 2 ** 8; 66 | bytes[5] = unixTsMs; 67 | bytes[6] = 0x70 | (randA >>> 8); 68 | bytes[7] = randA; 69 | bytes[8] = 0x80 | (randBHi >>> 24); 70 | bytes[9] = randBHi >>> 16; 71 | bytes[10] = randBHi >>> 8; 72 | bytes[11] = randBHi; 73 | bytes[12] = randBLo >>> 24; 74 | bytes[13] = randBLo >>> 16; 75 | bytes[14] = randBLo >>> 8; 76 | bytes[15] = randBLo; 77 | return new UUID(bytes); 78 | } 79 | /** 80 | * Builds a byte array from a string representation. 81 | * 82 | * This method accepts the following formats: 83 | * 84 | * - 32-digit hexadecimal format without hyphens: `0189dcd553117d408db09496a2eef37b` 85 | * - 8-4-4-4-12 hyphenated format: `0189dcd5-5311-7d40-8db0-9496a2eef37b` 86 | * - Hyphenated format with surrounding braces: `{0189dcd5-5311-7d40-8db0-9496a2eef37b}` 87 | * - RFC 9562 URN format: `urn:uuid:0189dcd5-5311-7d40-8db0-9496a2eef37b` 88 | * 89 | * Leading and trailing whitespaces represents an error. 90 | * 91 | * @throws SyntaxError if the argument could not parse as a valid UUID string. 92 | */ 93 | static parse(uuid) { 94 | var _a, _b, _c, _d; 95 | let hex = undefined; 96 | switch (uuid.length) { 97 | case 32: 98 | hex = (_a = /^[0-9a-f]{32}$/i.exec(uuid)) === null || _a === void 0 ? void 0 : _a[0]; 99 | break; 100 | case 36: 101 | hex = 102 | (_b = /^([0-9a-f]{8})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{12})$/i 103 | .exec(uuid)) === null || _b === void 0 ? void 0 : _b.slice(1, 6).join(""); 104 | break; 105 | case 38: 106 | hex = 107 | (_c = /^\{([0-9a-f]{8})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{12})\}$/i 108 | .exec(uuid)) === null || _c === void 0 ? void 0 : _c.slice(1, 6).join(""); 109 | break; 110 | case 45: 111 | hex = 112 | (_d = /^urn:uuid:([0-9a-f]{8})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{12})$/i 113 | .exec(uuid)) === null || _d === void 0 ? void 0 : _d.slice(1, 6).join(""); 114 | break; 115 | default: 116 | break; 117 | } 118 | if (hex) { 119 | const inner = new Uint8Array(16); 120 | for (let i = 0; i < 16; i += 4) { 121 | const n = parseInt(hex.substring(2 * i, 2 * i + 8), 16); 122 | inner[i + 0] = n >>> 24; 123 | inner[i + 1] = n >>> 16; 124 | inner[i + 2] = n >>> 8; 125 | inner[i + 3] = n; 126 | } 127 | return new UUID(inner); 128 | } 129 | else { 130 | throw new SyntaxError("could not parse UUID string"); 131 | } 132 | } 133 | /** 134 | * @returns The 8-4-4-4-12 canonical hexadecimal string representation 135 | * (`0189dcd5-5311-7d40-8db0-9496a2eef37b`). 136 | */ 137 | toString() { 138 | let text = ""; 139 | for (let i = 0; i < this.bytes.length; i++) { 140 | text += DIGITS.charAt(this.bytes[i] >>> 4); 141 | text += DIGITS.charAt(this.bytes[i] & 0xf); 142 | if (i === 3 || i === 5 || i === 7 || i === 9) { 143 | text += "-"; 144 | } 145 | } 146 | return text; 147 | } 148 | /** 149 | * @returns The 32-digit hexadecimal representation without hyphens 150 | * (`0189dcd553117d408db09496a2eef37b`). 151 | */ 152 | toHex() { 153 | let text = ""; 154 | for (let i = 0; i < this.bytes.length; i++) { 155 | text += DIGITS.charAt(this.bytes[i] >>> 4); 156 | text += DIGITS.charAt(this.bytes[i] & 0xf); 157 | } 158 | return text; 159 | } 160 | /** @returns The 8-4-4-4-12 canonical hexadecimal string representation. */ 161 | toJSON() { 162 | return this.toString(); 163 | } 164 | /** 165 | * Reports the variant field value of the UUID or, if appropriate, "NIL" or 166 | * "MAX". 167 | * 168 | * For convenience, this method reports "NIL" or "MAX" if `this` represents 169 | * the Nil or Max UUID, although the Nil and Max UUIDs are technically 170 | * subsumed under the variants `0b0` and `0b111`, respectively. 171 | */ 172 | getVariant() { 173 | const n = this.bytes[8] >>> 4; 174 | if (n < 0) { 175 | throw new Error("unreachable"); 176 | } 177 | else if (n <= 0b0111) { 178 | return this.bytes.every((e) => e === 0) ? "NIL" : "VAR_0"; 179 | } 180 | else if (n <= 0b1011) { 181 | return "VAR_10"; 182 | } 183 | else if (n <= 0b1101) { 184 | return "VAR_110"; 185 | } 186 | else if (n <= 0b1111) { 187 | return this.bytes.every((e) => e === 0xff) ? "MAX" : "VAR_RESERVED"; 188 | } 189 | else { 190 | throw new Error("unreachable"); 191 | } 192 | } 193 | /** 194 | * Returns the version field value of the UUID or `undefined` if the UUID does 195 | * not have the variant field value of `0b10`. 196 | */ 197 | getVersion() { 198 | return this.getVariant() === "VAR_10" ? this.bytes[6] >>> 4 : undefined; 199 | } 200 | /** Creates an object from `this`. */ 201 | clone() { 202 | return new UUID(this.bytes.slice(0)); 203 | } 204 | /** Returns true if `this` is equivalent to `other`. */ 205 | equals(other) { 206 | return this.compareTo(other) === 0; 207 | } 208 | /** 209 | * Returns a negative integer, zero, or positive integer if `this` is less 210 | * than, equal to, or greater than `other`, respectively. 211 | */ 212 | compareTo(other) { 213 | for (let i = 0; i < 16; i++) { 214 | const diff = this.bytes[i] - other.bytes[i]; 215 | if (diff !== 0) { 216 | return Math.sign(diff); 217 | } 218 | } 219 | return 0; 220 | } 221 | } 222 | exports.UUID = UUID; 223 | /** 224 | * Encapsulates the monotonic counter state. 225 | * 226 | * This class provides APIs to utilize a separate counter state from that of the 227 | * global generator used by {@link uuidv7} and {@link uuidv7obj}. In addition to 228 | * the default {@link generate} method, this class has {@link generateOrAbort} 229 | * that is useful to absolutely guarantee the monotonically increasing order of 230 | * generated UUIDs. See their respective documentation for details. 231 | */ 232 | class V7Generator { 233 | /** 234 | * Creates a generator object with the default random number generator, or 235 | * with the specified one if passed as an argument. The specified random 236 | * number generator should be cryptographically strong and securely seeded. 237 | */ 238 | constructor(randomNumberGenerator) { 239 | this.timestamp = 0; 240 | this.counter = 0; 241 | this.random = randomNumberGenerator !== null && randomNumberGenerator !== void 0 ? randomNumberGenerator : getDefaultRandom(); 242 | } 243 | /** 244 | * Generates a new UUIDv7 object from the current timestamp, or resets the 245 | * generator upon significant timestamp rollback. 246 | * 247 | * This method returns a monotonically increasing UUID by reusing the previous 248 | * timestamp even if the up-to-date timestamp is smaller than the immediately 249 | * preceding UUID's. However, when such a clock rollback is considered 250 | * significant (i.e., by more than ten seconds), this method resets the 251 | * generator and returns a new UUID based on the given timestamp, breaking the 252 | * increasing order of UUIDs. 253 | * 254 | * See {@link generateOrAbort} for the other mode of generation and 255 | * {@link generateOrResetCore} for the low-level primitive. 256 | */ 257 | generate() { 258 | return this.generateOrResetCore(Date.now(), 10000); 259 | } 260 | /** 261 | * Generates a new UUIDv7 object from the current timestamp, or returns 262 | * `undefined` upon significant timestamp rollback. 263 | * 264 | * This method returns a monotonically increasing UUID by reusing the previous 265 | * timestamp even if the up-to-date timestamp is smaller than the immediately 266 | * preceding UUID's. However, when such a clock rollback is considered 267 | * significant (i.e., by more than ten seconds), this method aborts and 268 | * returns `undefined` immediately. 269 | * 270 | * See {@link generate} for the other mode of generation and 271 | * {@link generateOrAbortCore} for the low-level primitive. 272 | */ 273 | generateOrAbort() { 274 | return this.generateOrAbortCore(Date.now(), 10000); 275 | } 276 | /** 277 | * Generates a new UUIDv7 object from the `unixTsMs` passed, or resets the 278 | * generator upon significant timestamp rollback. 279 | * 280 | * This method is equivalent to {@link generate} except that it takes a custom 281 | * timestamp and clock rollback allowance. 282 | * 283 | * @param rollbackAllowance - The amount of `unixTsMs` rollback that is 284 | * considered significant. A suggested value is `10_000` (milliseconds). 285 | * @throws RangeError if `unixTsMs` is not a 48-bit positive integer. 286 | */ 287 | generateOrResetCore(unixTsMs, rollbackAllowance) { 288 | let value = this.generateOrAbortCore(unixTsMs, rollbackAllowance); 289 | if (value === undefined) { 290 | // reset state and resume 291 | this.timestamp = 0; 292 | value = this.generateOrAbortCore(unixTsMs, rollbackAllowance); 293 | } 294 | return value; 295 | } 296 | /** 297 | * Generates a new UUIDv7 object from the `unixTsMs` passed, or returns 298 | * `undefined` upon significant timestamp rollback. 299 | * 300 | * This method is equivalent to {@link generateOrAbort} except that it takes a 301 | * custom timestamp and clock rollback allowance. 302 | * 303 | * @param rollbackAllowance - The amount of `unixTsMs` rollback that is 304 | * considered significant. A suggested value is `10_000` (milliseconds). 305 | * @throws RangeError if `unixTsMs` is not a 48-bit positive integer. 306 | */ 307 | generateOrAbortCore(unixTsMs, rollbackAllowance) { 308 | const MAX_COUNTER = 4398046511103; 309 | if (!Number.isInteger(unixTsMs) || 310 | unixTsMs < 1 || 311 | unixTsMs > 281474976710655) { 312 | throw new RangeError("`unixTsMs` must be a 48-bit positive integer"); 313 | } 314 | else if (rollbackAllowance < 0 || rollbackAllowance > 281474976710655) { 315 | throw new RangeError("`rollbackAllowance` out of reasonable range"); 316 | } 317 | if (unixTsMs > this.timestamp) { 318 | this.timestamp = unixTsMs; 319 | this.resetCounter(); 320 | } 321 | else if (unixTsMs + rollbackAllowance >= this.timestamp) { 322 | // go on with previous timestamp if new one is not much smaller 323 | this.counter++; 324 | if (this.counter > MAX_COUNTER) { 325 | // increment timestamp at counter overflow 326 | this.timestamp++; 327 | this.resetCounter(); 328 | } 329 | } 330 | else { 331 | // abort if clock went backwards to unbearable extent 332 | return undefined; 333 | } 334 | return UUID.fromFieldsV7(this.timestamp, Math.trunc(this.counter / 2 ** 30), this.counter & (2 ** 30 - 1), this.random.nextUint32()); 335 | } 336 | /** Initializes the counter at a 42-bit random integer. */ 337 | resetCounter() { 338 | this.counter = 339 | this.random.nextUint32() * 0x400 + (this.random.nextUint32() & 0x3ff); 340 | } 341 | /** 342 | * Generates a new UUIDv4 object utilizing the random number generator inside. 343 | * 344 | * @internal 345 | */ 346 | generateV4() { 347 | const bytes = new Uint8Array(Uint32Array.of(this.random.nextUint32(), this.random.nextUint32(), this.random.nextUint32(), this.random.nextUint32()).buffer); 348 | bytes[6] = 0x40 | (bytes[6] >>> 4); 349 | bytes[8] = 0x80 | (bytes[8] >>> 2); 350 | return UUID.ofInner(bytes); 351 | } 352 | } 353 | exports.V7Generator = V7Generator; 354 | /** Returns the default random number generator available in the environment. */ 355 | const getDefaultRandom = () => { 356 | // detect Web Crypto API 357 | if (typeof crypto !== "undefined" && 358 | typeof crypto.getRandomValues !== "undefined") { 359 | return new BufferedCryptoRandom(); 360 | } 361 | else { 362 | // fall back on Math.random() unless the flag is set to true 363 | if (typeof UUIDV7_DENY_WEAK_RNG !== "undefined" && UUIDV7_DENY_WEAK_RNG) { 364 | throw new Error("no cryptographically strong RNG available"); 365 | } 366 | return { 367 | nextUint32: () => Math.trunc(Math.random() * 65536) * 65536 + 368 | Math.trunc(Math.random() * 65536), 369 | }; 370 | } 371 | }; 372 | /** 373 | * Wraps `crypto.getRandomValues()` to enable buffering; this uses a small 374 | * buffer by default to avoid both unbearable throughput decline in some 375 | * environments and the waste of time and space for unused values. 376 | */ 377 | class BufferedCryptoRandom { 378 | constructor() { 379 | this.buffer = new Uint32Array(8); 380 | this.cursor = 0xffff; 381 | } 382 | nextUint32() { 383 | if (this.cursor >= this.buffer.length) { 384 | crypto.getRandomValues(this.buffer); 385 | this.cursor = 0; 386 | } 387 | return this.buffer[this.cursor++]; 388 | } 389 | } 390 | let defaultGenerator; 391 | /** 392 | * Generates a UUIDv7 string. 393 | * 394 | * @returns The 8-4-4-4-12 canonical hexadecimal string representation 395 | * ("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"). 396 | */ 397 | const uuidv7 = () => (0, exports.uuidv7obj)().toString(); 398 | exports.uuidv7 = uuidv7; 399 | /** Generates a UUIDv7 object. */ 400 | const uuidv7obj = () => (defaultGenerator || (defaultGenerator = new V7Generator())).generate(); 401 | exports.uuidv7obj = uuidv7obj; 402 | /** 403 | * Generates a UUIDv4 string. 404 | * 405 | * @returns The 8-4-4-4-12 canonical hexadecimal string representation 406 | * ("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"). 407 | */ 408 | const uuidv4 = () => (0, exports.uuidv4obj)().toString(); 409 | exports.uuidv4 = uuidv4; 410 | /** Generates a UUIDv4 object. */ 411 | const uuidv4obj = () => (defaultGenerator || (defaultGenerator = new V7Generator())).generateV4(); 412 | exports.uuidv4obj = uuidv4obj; 413 | -------------------------------------------------------------------------------- /dist/index.d.cts: -------------------------------------------------------------------------------- 1 | /** 2 | * uuidv7: A JavaScript implementation of UUID version 7 3 | * 4 | * Copyright 2021-2024 LiosK 5 | * 6 | * @license Apache-2.0 7 | * @packageDocumentation 8 | */ 9 | /** Represents a UUID as a 16-byte byte array. */ 10 | export declare class UUID { 11 | readonly bytes: Readonly; 12 | /** @param bytes - The 16-byte byte array representation. */ 13 | private constructor(); 14 | /** 15 | * Creates an object from the internal representation, a 16-byte byte array 16 | * containing the binary UUID representation in the big-endian byte order. 17 | * 18 | * This method does NOT shallow-copy the argument, and thus the created object 19 | * holds the reference to the underlying buffer. 20 | * 21 | * @throws TypeError if the length of the argument is not 16. 22 | */ 23 | static ofInner(bytes: Readonly): UUID; 24 | /** 25 | * Builds a byte array from UUIDv7 field values. 26 | * 27 | * @param unixTsMs - A 48-bit `unix_ts_ms` field value. 28 | * @param randA - A 12-bit `rand_a` field value. 29 | * @param randBHi - The higher 30 bits of 62-bit `rand_b` field value. 30 | * @param randBLo - The lower 32 bits of 62-bit `rand_b` field value. 31 | * @throws RangeError if any field value is out of the specified range. 32 | */ 33 | static fromFieldsV7(unixTsMs: number, randA: number, randBHi: number, randBLo: number): UUID; 34 | /** 35 | * Builds a byte array from a string representation. 36 | * 37 | * This method accepts the following formats: 38 | * 39 | * - 32-digit hexadecimal format without hyphens: `0189dcd553117d408db09496a2eef37b` 40 | * - 8-4-4-4-12 hyphenated format: `0189dcd5-5311-7d40-8db0-9496a2eef37b` 41 | * - Hyphenated format with surrounding braces: `{0189dcd5-5311-7d40-8db0-9496a2eef37b}` 42 | * - RFC 9562 URN format: `urn:uuid:0189dcd5-5311-7d40-8db0-9496a2eef37b` 43 | * 44 | * Leading and trailing whitespaces represents an error. 45 | * 46 | * @throws SyntaxError if the argument could not parse as a valid UUID string. 47 | */ 48 | static parse(uuid: string): UUID; 49 | /** 50 | * @returns The 8-4-4-4-12 canonical hexadecimal string representation 51 | * (`0189dcd5-5311-7d40-8db0-9496a2eef37b`). 52 | */ 53 | toString(): string; 54 | /** 55 | * @returns The 32-digit hexadecimal representation without hyphens 56 | * (`0189dcd553117d408db09496a2eef37b`). 57 | */ 58 | toHex(): string; 59 | /** @returns The 8-4-4-4-12 canonical hexadecimal string representation. */ 60 | toJSON(): string; 61 | /** 62 | * Reports the variant field value of the UUID or, if appropriate, "NIL" or 63 | * "MAX". 64 | * 65 | * For convenience, this method reports "NIL" or "MAX" if `this` represents 66 | * the Nil or Max UUID, although the Nil and Max UUIDs are technically 67 | * subsumed under the variants `0b0` and `0b111`, respectively. 68 | */ 69 | getVariant(): "VAR_0" | "VAR_10" | "VAR_110" | "VAR_RESERVED" | "NIL" | "MAX"; 70 | /** 71 | * Returns the version field value of the UUID or `undefined` if the UUID does 72 | * not have the variant field value of `0b10`. 73 | */ 74 | getVersion(): number | undefined; 75 | /** Creates an object from `this`. */ 76 | clone(): UUID; 77 | /** Returns true if `this` is equivalent to `other`. */ 78 | equals(other: UUID): boolean; 79 | /** 80 | * Returns a negative integer, zero, or positive integer if `this` is less 81 | * than, equal to, or greater than `other`, respectively. 82 | */ 83 | compareTo(other: UUID): number; 84 | } 85 | /** 86 | * Encapsulates the monotonic counter state. 87 | * 88 | * This class provides APIs to utilize a separate counter state from that of the 89 | * global generator used by {@link uuidv7} and {@link uuidv7obj}. In addition to 90 | * the default {@link generate} method, this class has {@link generateOrAbort} 91 | * that is useful to absolutely guarantee the monotonically increasing order of 92 | * generated UUIDs. See their respective documentation for details. 93 | */ 94 | export declare class V7Generator { 95 | private timestamp; 96 | private counter; 97 | /** The random number generator used by the generator. */ 98 | private readonly random; 99 | /** 100 | * Creates a generator object with the default random number generator, or 101 | * with the specified one if passed as an argument. The specified random 102 | * number generator should be cryptographically strong and securely seeded. 103 | */ 104 | constructor(randomNumberGenerator?: { 105 | /** Returns a 32-bit random unsigned integer. */ 106 | nextUint32(): number; 107 | }); 108 | /** 109 | * Generates a new UUIDv7 object from the current timestamp, or resets the 110 | * generator upon significant timestamp rollback. 111 | * 112 | * This method returns a monotonically increasing UUID by reusing the previous 113 | * timestamp even if the up-to-date timestamp is smaller than the immediately 114 | * preceding UUID's. However, when such a clock rollback is considered 115 | * significant (i.e., by more than ten seconds), this method resets the 116 | * generator and returns a new UUID based on the given timestamp, breaking the 117 | * increasing order of UUIDs. 118 | * 119 | * See {@link generateOrAbort} for the other mode of generation and 120 | * {@link generateOrResetCore} for the low-level primitive. 121 | */ 122 | generate(): UUID; 123 | /** 124 | * Generates a new UUIDv7 object from the current timestamp, or returns 125 | * `undefined` upon significant timestamp rollback. 126 | * 127 | * This method returns a monotonically increasing UUID by reusing the previous 128 | * timestamp even if the up-to-date timestamp is smaller than the immediately 129 | * preceding UUID's. However, when such a clock rollback is considered 130 | * significant (i.e., by more than ten seconds), this method aborts and 131 | * returns `undefined` immediately. 132 | * 133 | * See {@link generate} for the other mode of generation and 134 | * {@link generateOrAbortCore} for the low-level primitive. 135 | */ 136 | generateOrAbort(): UUID | undefined; 137 | /** 138 | * Generates a new UUIDv7 object from the `unixTsMs` passed, or resets the 139 | * generator upon significant timestamp rollback. 140 | * 141 | * This method is equivalent to {@link generate} except that it takes a custom 142 | * timestamp and clock rollback allowance. 143 | * 144 | * @param rollbackAllowance - The amount of `unixTsMs` rollback that is 145 | * considered significant. A suggested value is `10_000` (milliseconds). 146 | * @throws RangeError if `unixTsMs` is not a 48-bit positive integer. 147 | */ 148 | generateOrResetCore(unixTsMs: number, rollbackAllowance: number): UUID; 149 | /** 150 | * Generates a new UUIDv7 object from the `unixTsMs` passed, or returns 151 | * `undefined` upon significant timestamp rollback. 152 | * 153 | * This method is equivalent to {@link generateOrAbort} except that it takes a 154 | * custom timestamp and clock rollback allowance. 155 | * 156 | * @param rollbackAllowance - The amount of `unixTsMs` rollback that is 157 | * considered significant. A suggested value is `10_000` (milliseconds). 158 | * @throws RangeError if `unixTsMs` is not a 48-bit positive integer. 159 | */ 160 | generateOrAbortCore(unixTsMs: number, rollbackAllowance: number): UUID | undefined; 161 | /** Initializes the counter at a 42-bit random integer. */ 162 | private resetCounter; 163 | } 164 | /** 165 | * Generates a UUIDv7 string. 166 | * 167 | * @returns The 8-4-4-4-12 canonical hexadecimal string representation 168 | * ("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"). 169 | */ 170 | export declare const uuidv7: () => string; 171 | /** Generates a UUIDv7 object. */ 172 | export declare const uuidv7obj: () => UUID; 173 | /** 174 | * Generates a UUIDv4 string. 175 | * 176 | * @returns The 8-4-4-4-12 canonical hexadecimal string representation 177 | * ("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"). 178 | */ 179 | export declare const uuidv4: () => string; 180 | /** Generates a UUIDv4 object. */ 181 | export declare const uuidv4obj: () => UUID; 182 | -------------------------------------------------------------------------------- /dist/index.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * uuidv7: A JavaScript implementation of UUID version 7 3 | * 4 | * Copyright 2021-2024 LiosK 5 | * 6 | * @license Apache-2.0 7 | * @packageDocumentation 8 | */ 9 | /** Represents a UUID as a 16-byte byte array. */ 10 | export declare class UUID { 11 | readonly bytes: Readonly; 12 | /** @param bytes - The 16-byte byte array representation. */ 13 | private constructor(); 14 | /** 15 | * Creates an object from the internal representation, a 16-byte byte array 16 | * containing the binary UUID representation in the big-endian byte order. 17 | * 18 | * This method does NOT shallow-copy the argument, and thus the created object 19 | * holds the reference to the underlying buffer. 20 | * 21 | * @throws TypeError if the length of the argument is not 16. 22 | */ 23 | static ofInner(bytes: Readonly): UUID; 24 | /** 25 | * Builds a byte array from UUIDv7 field values. 26 | * 27 | * @param unixTsMs - A 48-bit `unix_ts_ms` field value. 28 | * @param randA - A 12-bit `rand_a` field value. 29 | * @param randBHi - The higher 30 bits of 62-bit `rand_b` field value. 30 | * @param randBLo - The lower 32 bits of 62-bit `rand_b` field value. 31 | * @throws RangeError if any field value is out of the specified range. 32 | */ 33 | static fromFieldsV7(unixTsMs: number, randA: number, randBHi: number, randBLo: number): UUID; 34 | /** 35 | * Builds a byte array from a string representation. 36 | * 37 | * This method accepts the following formats: 38 | * 39 | * - 32-digit hexadecimal format without hyphens: `0189dcd553117d408db09496a2eef37b` 40 | * - 8-4-4-4-12 hyphenated format: `0189dcd5-5311-7d40-8db0-9496a2eef37b` 41 | * - Hyphenated format with surrounding braces: `{0189dcd5-5311-7d40-8db0-9496a2eef37b}` 42 | * - RFC 9562 URN format: `urn:uuid:0189dcd5-5311-7d40-8db0-9496a2eef37b` 43 | * 44 | * Leading and trailing whitespaces represents an error. 45 | * 46 | * @throws SyntaxError if the argument could not parse as a valid UUID string. 47 | */ 48 | static parse(uuid: string): UUID; 49 | /** 50 | * @returns The 8-4-4-4-12 canonical hexadecimal string representation 51 | * (`0189dcd5-5311-7d40-8db0-9496a2eef37b`). 52 | */ 53 | toString(): string; 54 | /** 55 | * @returns The 32-digit hexadecimal representation without hyphens 56 | * (`0189dcd553117d408db09496a2eef37b`). 57 | */ 58 | toHex(): string; 59 | /** @returns The 8-4-4-4-12 canonical hexadecimal string representation. */ 60 | toJSON(): string; 61 | /** 62 | * Reports the variant field value of the UUID or, if appropriate, "NIL" or 63 | * "MAX". 64 | * 65 | * For convenience, this method reports "NIL" or "MAX" if `this` represents 66 | * the Nil or Max UUID, although the Nil and Max UUIDs are technically 67 | * subsumed under the variants `0b0` and `0b111`, respectively. 68 | */ 69 | getVariant(): "VAR_0" | "VAR_10" | "VAR_110" | "VAR_RESERVED" | "NIL" | "MAX"; 70 | /** 71 | * Returns the version field value of the UUID or `undefined` if the UUID does 72 | * not have the variant field value of `0b10`. 73 | */ 74 | getVersion(): number | undefined; 75 | /** Creates an object from `this`. */ 76 | clone(): UUID; 77 | /** Returns true if `this` is equivalent to `other`. */ 78 | equals(other: UUID): boolean; 79 | /** 80 | * Returns a negative integer, zero, or positive integer if `this` is less 81 | * than, equal to, or greater than `other`, respectively. 82 | */ 83 | compareTo(other: UUID): number; 84 | } 85 | /** 86 | * Encapsulates the monotonic counter state. 87 | * 88 | * This class provides APIs to utilize a separate counter state from that of the 89 | * global generator used by {@link uuidv7} and {@link uuidv7obj}. In addition to 90 | * the default {@link generate} method, this class has {@link generateOrAbort} 91 | * that is useful to absolutely guarantee the monotonically increasing order of 92 | * generated UUIDs. See their respective documentation for details. 93 | */ 94 | export declare class V7Generator { 95 | private timestamp; 96 | private counter; 97 | /** The random number generator used by the generator. */ 98 | private readonly random; 99 | /** 100 | * Creates a generator object with the default random number generator, or 101 | * with the specified one if passed as an argument. The specified random 102 | * number generator should be cryptographically strong and securely seeded. 103 | */ 104 | constructor(randomNumberGenerator?: { 105 | /** Returns a 32-bit random unsigned integer. */ 106 | nextUint32(): number; 107 | }); 108 | /** 109 | * Generates a new UUIDv7 object from the current timestamp, or resets the 110 | * generator upon significant timestamp rollback. 111 | * 112 | * This method returns a monotonically increasing UUID by reusing the previous 113 | * timestamp even if the up-to-date timestamp is smaller than the immediately 114 | * preceding UUID's. However, when such a clock rollback is considered 115 | * significant (i.e., by more than ten seconds), this method resets the 116 | * generator and returns a new UUID based on the given timestamp, breaking the 117 | * increasing order of UUIDs. 118 | * 119 | * See {@link generateOrAbort} for the other mode of generation and 120 | * {@link generateOrResetCore} for the low-level primitive. 121 | */ 122 | generate(): UUID; 123 | /** 124 | * Generates a new UUIDv7 object from the current timestamp, or returns 125 | * `undefined` upon significant timestamp rollback. 126 | * 127 | * This method returns a monotonically increasing UUID by reusing the previous 128 | * timestamp even if the up-to-date timestamp is smaller than the immediately 129 | * preceding UUID's. However, when such a clock rollback is considered 130 | * significant (i.e., by more than ten seconds), this method aborts and 131 | * returns `undefined` immediately. 132 | * 133 | * See {@link generate} for the other mode of generation and 134 | * {@link generateOrAbortCore} for the low-level primitive. 135 | */ 136 | generateOrAbort(): UUID | undefined; 137 | /** 138 | * Generates a new UUIDv7 object from the `unixTsMs` passed, or resets the 139 | * generator upon significant timestamp rollback. 140 | * 141 | * This method is equivalent to {@link generate} except that it takes a custom 142 | * timestamp and clock rollback allowance. 143 | * 144 | * @param rollbackAllowance - The amount of `unixTsMs` rollback that is 145 | * considered significant. A suggested value is `10_000` (milliseconds). 146 | * @throws RangeError if `unixTsMs` is not a 48-bit positive integer. 147 | */ 148 | generateOrResetCore(unixTsMs: number, rollbackAllowance: number): UUID; 149 | /** 150 | * Generates a new UUIDv7 object from the `unixTsMs` passed, or returns 151 | * `undefined` upon significant timestamp rollback. 152 | * 153 | * This method is equivalent to {@link generateOrAbort} except that it takes a 154 | * custom timestamp and clock rollback allowance. 155 | * 156 | * @param rollbackAllowance - The amount of `unixTsMs` rollback that is 157 | * considered significant. A suggested value is `10_000` (milliseconds). 158 | * @throws RangeError if `unixTsMs` is not a 48-bit positive integer. 159 | */ 160 | generateOrAbortCore(unixTsMs: number, rollbackAllowance: number): UUID | undefined; 161 | /** Initializes the counter at a 42-bit random integer. */ 162 | private resetCounter; 163 | } 164 | /** 165 | * Generates a UUIDv7 string. 166 | * 167 | * @returns The 8-4-4-4-12 canonical hexadecimal string representation 168 | * ("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"). 169 | */ 170 | export declare const uuidv7: () => string; 171 | /** Generates a UUIDv7 object. */ 172 | export declare const uuidv7obj: () => UUID; 173 | /** 174 | * Generates a UUIDv4 string. 175 | * 176 | * @returns The 8-4-4-4-12 canonical hexadecimal string representation 177 | * ("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"). 178 | */ 179 | export declare const uuidv4: () => string; 180 | /** Generates a UUIDv4 object. */ 181 | export declare const uuidv4obj: () => UUID; 182 | -------------------------------------------------------------------------------- /dist/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * uuidv7: A JavaScript implementation of UUID version 7 3 | * 4 | * Copyright 2021-2024 LiosK 5 | * 6 | * @license Apache-2.0 7 | * @packageDocumentation 8 | */ 9 | const DIGITS = "0123456789abcdef"; 10 | /** Represents a UUID as a 16-byte byte array. */ 11 | export class UUID { 12 | /** @param bytes - The 16-byte byte array representation. */ 13 | constructor(bytes) { 14 | this.bytes = bytes; 15 | } 16 | /** 17 | * Creates an object from the internal representation, a 16-byte byte array 18 | * containing the binary UUID representation in the big-endian byte order. 19 | * 20 | * This method does NOT shallow-copy the argument, and thus the created object 21 | * holds the reference to the underlying buffer. 22 | * 23 | * @throws TypeError if the length of the argument is not 16. 24 | */ 25 | static ofInner(bytes) { 26 | if (bytes.length !== 16) { 27 | throw new TypeError("not 128-bit length"); 28 | } 29 | else { 30 | return new UUID(bytes); 31 | } 32 | } 33 | /** 34 | * Builds a byte array from UUIDv7 field values. 35 | * 36 | * @param unixTsMs - A 48-bit `unix_ts_ms` field value. 37 | * @param randA - A 12-bit `rand_a` field value. 38 | * @param randBHi - The higher 30 bits of 62-bit `rand_b` field value. 39 | * @param randBLo - The lower 32 bits of 62-bit `rand_b` field value. 40 | * @throws RangeError if any field value is out of the specified range. 41 | */ 42 | static fromFieldsV7(unixTsMs, randA, randBHi, randBLo) { 43 | if (!Number.isInteger(unixTsMs) || 44 | !Number.isInteger(randA) || 45 | !Number.isInteger(randBHi) || 46 | !Number.isInteger(randBLo) || 47 | unixTsMs < 0 || 48 | randA < 0 || 49 | randBHi < 0 || 50 | randBLo < 0 || 51 | unixTsMs > 281474976710655 || 52 | randA > 0xfff || 53 | randBHi > 1073741823 || 54 | randBLo > 4294967295) { 55 | throw new RangeError("invalid field value"); 56 | } 57 | const bytes = new Uint8Array(16); 58 | bytes[0] = unixTsMs / 2 ** 40; 59 | bytes[1] = unixTsMs / 2 ** 32; 60 | bytes[2] = unixTsMs / 2 ** 24; 61 | bytes[3] = unixTsMs / 2 ** 16; 62 | bytes[4] = unixTsMs / 2 ** 8; 63 | bytes[5] = unixTsMs; 64 | bytes[6] = 0x70 | (randA >>> 8); 65 | bytes[7] = randA; 66 | bytes[8] = 0x80 | (randBHi >>> 24); 67 | bytes[9] = randBHi >>> 16; 68 | bytes[10] = randBHi >>> 8; 69 | bytes[11] = randBHi; 70 | bytes[12] = randBLo >>> 24; 71 | bytes[13] = randBLo >>> 16; 72 | bytes[14] = randBLo >>> 8; 73 | bytes[15] = randBLo; 74 | return new UUID(bytes); 75 | } 76 | /** 77 | * Builds a byte array from a string representation. 78 | * 79 | * This method accepts the following formats: 80 | * 81 | * - 32-digit hexadecimal format without hyphens: `0189dcd553117d408db09496a2eef37b` 82 | * - 8-4-4-4-12 hyphenated format: `0189dcd5-5311-7d40-8db0-9496a2eef37b` 83 | * - Hyphenated format with surrounding braces: `{0189dcd5-5311-7d40-8db0-9496a2eef37b}` 84 | * - RFC 9562 URN format: `urn:uuid:0189dcd5-5311-7d40-8db0-9496a2eef37b` 85 | * 86 | * Leading and trailing whitespaces represents an error. 87 | * 88 | * @throws SyntaxError if the argument could not parse as a valid UUID string. 89 | */ 90 | static parse(uuid) { 91 | var _a, _b, _c, _d; 92 | let hex = undefined; 93 | switch (uuid.length) { 94 | case 32: 95 | hex = (_a = /^[0-9a-f]{32}$/i.exec(uuid)) === null || _a === void 0 ? void 0 : _a[0]; 96 | break; 97 | case 36: 98 | hex = 99 | (_b = /^([0-9a-f]{8})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{12})$/i 100 | .exec(uuid)) === null || _b === void 0 ? void 0 : _b.slice(1, 6).join(""); 101 | break; 102 | case 38: 103 | hex = 104 | (_c = /^\{([0-9a-f]{8})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{12})\}$/i 105 | .exec(uuid)) === null || _c === void 0 ? void 0 : _c.slice(1, 6).join(""); 106 | break; 107 | case 45: 108 | hex = 109 | (_d = /^urn:uuid:([0-9a-f]{8})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{12})$/i 110 | .exec(uuid)) === null || _d === void 0 ? void 0 : _d.slice(1, 6).join(""); 111 | break; 112 | default: 113 | break; 114 | } 115 | if (hex) { 116 | const inner = new Uint8Array(16); 117 | for (let i = 0; i < 16; i += 4) { 118 | const n = parseInt(hex.substring(2 * i, 2 * i + 8), 16); 119 | inner[i + 0] = n >>> 24; 120 | inner[i + 1] = n >>> 16; 121 | inner[i + 2] = n >>> 8; 122 | inner[i + 3] = n; 123 | } 124 | return new UUID(inner); 125 | } 126 | else { 127 | throw new SyntaxError("could not parse UUID string"); 128 | } 129 | } 130 | /** 131 | * @returns The 8-4-4-4-12 canonical hexadecimal string representation 132 | * (`0189dcd5-5311-7d40-8db0-9496a2eef37b`). 133 | */ 134 | toString() { 135 | let text = ""; 136 | for (let i = 0; i < this.bytes.length; i++) { 137 | text += DIGITS.charAt(this.bytes[i] >>> 4); 138 | text += DIGITS.charAt(this.bytes[i] & 0xf); 139 | if (i === 3 || i === 5 || i === 7 || i === 9) { 140 | text += "-"; 141 | } 142 | } 143 | return text; 144 | } 145 | /** 146 | * @returns The 32-digit hexadecimal representation without hyphens 147 | * (`0189dcd553117d408db09496a2eef37b`). 148 | */ 149 | toHex() { 150 | let text = ""; 151 | for (let i = 0; i < this.bytes.length; i++) { 152 | text += DIGITS.charAt(this.bytes[i] >>> 4); 153 | text += DIGITS.charAt(this.bytes[i] & 0xf); 154 | } 155 | return text; 156 | } 157 | /** @returns The 8-4-4-4-12 canonical hexadecimal string representation. */ 158 | toJSON() { 159 | return this.toString(); 160 | } 161 | /** 162 | * Reports the variant field value of the UUID or, if appropriate, "NIL" or 163 | * "MAX". 164 | * 165 | * For convenience, this method reports "NIL" or "MAX" if `this` represents 166 | * the Nil or Max UUID, although the Nil and Max UUIDs are technically 167 | * subsumed under the variants `0b0` and `0b111`, respectively. 168 | */ 169 | getVariant() { 170 | const n = this.bytes[8] >>> 4; 171 | if (n < 0) { 172 | throw new Error("unreachable"); 173 | } 174 | else if (n <= 0b0111) { 175 | return this.bytes.every((e) => e === 0) ? "NIL" : "VAR_0"; 176 | } 177 | else if (n <= 0b1011) { 178 | return "VAR_10"; 179 | } 180 | else if (n <= 0b1101) { 181 | return "VAR_110"; 182 | } 183 | else if (n <= 0b1111) { 184 | return this.bytes.every((e) => e === 0xff) ? "MAX" : "VAR_RESERVED"; 185 | } 186 | else { 187 | throw new Error("unreachable"); 188 | } 189 | } 190 | /** 191 | * Returns the version field value of the UUID or `undefined` if the UUID does 192 | * not have the variant field value of `0b10`. 193 | */ 194 | getVersion() { 195 | return this.getVariant() === "VAR_10" ? this.bytes[6] >>> 4 : undefined; 196 | } 197 | /** Creates an object from `this`. */ 198 | clone() { 199 | return new UUID(this.bytes.slice(0)); 200 | } 201 | /** Returns true if `this` is equivalent to `other`. */ 202 | equals(other) { 203 | return this.compareTo(other) === 0; 204 | } 205 | /** 206 | * Returns a negative integer, zero, or positive integer if `this` is less 207 | * than, equal to, or greater than `other`, respectively. 208 | */ 209 | compareTo(other) { 210 | for (let i = 0; i < 16; i++) { 211 | const diff = this.bytes[i] - other.bytes[i]; 212 | if (diff !== 0) { 213 | return Math.sign(diff); 214 | } 215 | } 216 | return 0; 217 | } 218 | } 219 | /** 220 | * Encapsulates the monotonic counter state. 221 | * 222 | * This class provides APIs to utilize a separate counter state from that of the 223 | * global generator used by {@link uuidv7} and {@link uuidv7obj}. In addition to 224 | * the default {@link generate} method, this class has {@link generateOrAbort} 225 | * that is useful to absolutely guarantee the monotonically increasing order of 226 | * generated UUIDs. See their respective documentation for details. 227 | */ 228 | export class V7Generator { 229 | /** 230 | * Creates a generator object with the default random number generator, or 231 | * with the specified one if passed as an argument. The specified random 232 | * number generator should be cryptographically strong and securely seeded. 233 | */ 234 | constructor(randomNumberGenerator) { 235 | this.timestamp = 0; 236 | this.counter = 0; 237 | this.random = randomNumberGenerator !== null && randomNumberGenerator !== void 0 ? randomNumberGenerator : getDefaultRandom(); 238 | } 239 | /** 240 | * Generates a new UUIDv7 object from the current timestamp, or resets the 241 | * generator upon significant timestamp rollback. 242 | * 243 | * This method returns a monotonically increasing UUID by reusing the previous 244 | * timestamp even if the up-to-date timestamp is smaller than the immediately 245 | * preceding UUID's. However, when such a clock rollback is considered 246 | * significant (i.e., by more than ten seconds), this method resets the 247 | * generator and returns a new UUID based on the given timestamp, breaking the 248 | * increasing order of UUIDs. 249 | * 250 | * See {@link generateOrAbort} for the other mode of generation and 251 | * {@link generateOrResetCore} for the low-level primitive. 252 | */ 253 | generate() { 254 | return this.generateOrResetCore(Date.now(), 10000); 255 | } 256 | /** 257 | * Generates a new UUIDv7 object from the current timestamp, or returns 258 | * `undefined` upon significant timestamp rollback. 259 | * 260 | * This method returns a monotonically increasing UUID by reusing the previous 261 | * timestamp even if the up-to-date timestamp is smaller than the immediately 262 | * preceding UUID's. However, when such a clock rollback is considered 263 | * significant (i.e., by more than ten seconds), this method aborts and 264 | * returns `undefined` immediately. 265 | * 266 | * See {@link generate} for the other mode of generation and 267 | * {@link generateOrAbortCore} for the low-level primitive. 268 | */ 269 | generateOrAbort() { 270 | return this.generateOrAbortCore(Date.now(), 10000); 271 | } 272 | /** 273 | * Generates a new UUIDv7 object from the `unixTsMs` passed, or resets the 274 | * generator upon significant timestamp rollback. 275 | * 276 | * This method is equivalent to {@link generate} except that it takes a custom 277 | * timestamp and clock rollback allowance. 278 | * 279 | * @param rollbackAllowance - The amount of `unixTsMs` rollback that is 280 | * considered significant. A suggested value is `10_000` (milliseconds). 281 | * @throws RangeError if `unixTsMs` is not a 48-bit positive integer. 282 | */ 283 | generateOrResetCore(unixTsMs, rollbackAllowance) { 284 | let value = this.generateOrAbortCore(unixTsMs, rollbackAllowance); 285 | if (value === undefined) { 286 | // reset state and resume 287 | this.timestamp = 0; 288 | value = this.generateOrAbortCore(unixTsMs, rollbackAllowance); 289 | } 290 | return value; 291 | } 292 | /** 293 | * Generates a new UUIDv7 object from the `unixTsMs` passed, or returns 294 | * `undefined` upon significant timestamp rollback. 295 | * 296 | * This method is equivalent to {@link generateOrAbort} except that it takes a 297 | * custom timestamp and clock rollback allowance. 298 | * 299 | * @param rollbackAllowance - The amount of `unixTsMs` rollback that is 300 | * considered significant. A suggested value is `10_000` (milliseconds). 301 | * @throws RangeError if `unixTsMs` is not a 48-bit positive integer. 302 | */ 303 | generateOrAbortCore(unixTsMs, rollbackAllowance) { 304 | const MAX_COUNTER = 4398046511103; 305 | if (!Number.isInteger(unixTsMs) || 306 | unixTsMs < 1 || 307 | unixTsMs > 281474976710655) { 308 | throw new RangeError("`unixTsMs` must be a 48-bit positive integer"); 309 | } 310 | else if (rollbackAllowance < 0 || rollbackAllowance > 281474976710655) { 311 | throw new RangeError("`rollbackAllowance` out of reasonable range"); 312 | } 313 | if (unixTsMs > this.timestamp) { 314 | this.timestamp = unixTsMs; 315 | this.resetCounter(); 316 | } 317 | else if (unixTsMs + rollbackAllowance >= this.timestamp) { 318 | // go on with previous timestamp if new one is not much smaller 319 | this.counter++; 320 | if (this.counter > MAX_COUNTER) { 321 | // increment timestamp at counter overflow 322 | this.timestamp++; 323 | this.resetCounter(); 324 | } 325 | } 326 | else { 327 | // abort if clock went backwards to unbearable extent 328 | return undefined; 329 | } 330 | return UUID.fromFieldsV7(this.timestamp, Math.trunc(this.counter / 2 ** 30), this.counter & (2 ** 30 - 1), this.random.nextUint32()); 331 | } 332 | /** Initializes the counter at a 42-bit random integer. */ 333 | resetCounter() { 334 | this.counter = 335 | this.random.nextUint32() * 0x400 + (this.random.nextUint32() & 0x3ff); 336 | } 337 | /** 338 | * Generates a new UUIDv4 object utilizing the random number generator inside. 339 | * 340 | * @internal 341 | */ 342 | generateV4() { 343 | const bytes = new Uint8Array(Uint32Array.of(this.random.nextUint32(), this.random.nextUint32(), this.random.nextUint32(), this.random.nextUint32()).buffer); 344 | bytes[6] = 0x40 | (bytes[6] >>> 4); 345 | bytes[8] = 0x80 | (bytes[8] >>> 2); 346 | return UUID.ofInner(bytes); 347 | } 348 | } 349 | /** Returns the default random number generator available in the environment. */ 350 | const getDefaultRandom = () => { 351 | // detect Web Crypto API 352 | if (typeof crypto !== "undefined" && 353 | typeof crypto.getRandomValues !== "undefined") { 354 | return new BufferedCryptoRandom(); 355 | } 356 | else { 357 | // fall back on Math.random() unless the flag is set to true 358 | if (typeof UUIDV7_DENY_WEAK_RNG !== "undefined" && UUIDV7_DENY_WEAK_RNG) { 359 | throw new Error("no cryptographically strong RNG available"); 360 | } 361 | return { 362 | nextUint32: () => Math.trunc(Math.random() * 65536) * 65536 + 363 | Math.trunc(Math.random() * 65536), 364 | }; 365 | } 366 | }; 367 | /** 368 | * Wraps `crypto.getRandomValues()` to enable buffering; this uses a small 369 | * buffer by default to avoid both unbearable throughput decline in some 370 | * environments and the waste of time and space for unused values. 371 | */ 372 | class BufferedCryptoRandom { 373 | constructor() { 374 | this.buffer = new Uint32Array(8); 375 | this.cursor = 0xffff; 376 | } 377 | nextUint32() { 378 | if (this.cursor >= this.buffer.length) { 379 | crypto.getRandomValues(this.buffer); 380 | this.cursor = 0; 381 | } 382 | return this.buffer[this.cursor++]; 383 | } 384 | } 385 | let defaultGenerator; 386 | /** 387 | * Generates a UUIDv7 string. 388 | * 389 | * @returns The 8-4-4-4-12 canonical hexadecimal string representation 390 | * ("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"). 391 | */ 392 | export const uuidv7 = () => uuidv7obj().toString(); 393 | /** Generates a UUIDv7 object. */ 394 | export const uuidv7obj = () => (defaultGenerator || (defaultGenerator = new V7Generator())).generate(); 395 | /** 396 | * Generates a UUIDv4 string. 397 | * 398 | * @returns The 8-4-4-4-12 canonical hexadecimal string representation 399 | * ("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"). 400 | */ 401 | export const uuidv4 = () => uuidv4obj().toString(); 402 | /** Generates a UUIDv4 object. */ 403 | export const uuidv4obj = () => (defaultGenerator || (defaultGenerator = new V7Generator())).generateV4(); 404 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- 1 | TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. -------------------------------------------------------------------------------- /docs/assets/highlight.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --light-hl-0: #AF00DB; 3 | --dark-hl-0: #C586C0; 4 | --light-hl-1: #000000; 5 | --dark-hl-1: #D4D4D4; 6 | --light-hl-2: #001080; 7 | --dark-hl-2: #9CDCFE; 8 | --light-hl-3: #A31515; 9 | --dark-hl-3: #CE9178; 10 | --light-hl-4: #0000FF; 11 | --dark-hl-4: #569CD6; 12 | --light-hl-5: #0070C1; 13 | --dark-hl-5: #4FC1FF; 14 | --light-hl-6: #795E26; 15 | --dark-hl-6: #DCDCAA; 16 | --light-hl-7: #008000; 17 | --dark-hl-7: #6A9955; 18 | --light-hl-8: #098658; 19 | --dark-hl-8: #B5CEA8; 20 | --light-code-background: #FFFFFF; 21 | --dark-code-background: #1E1E1E; 22 | } 23 | 24 | @media (prefers-color-scheme: light) { :root { 25 | --hl-0: var(--light-hl-0); 26 | --hl-1: var(--light-hl-1); 27 | --hl-2: var(--light-hl-2); 28 | --hl-3: var(--light-hl-3); 29 | --hl-4: var(--light-hl-4); 30 | --hl-5: var(--light-hl-5); 31 | --hl-6: var(--light-hl-6); 32 | --hl-7: var(--light-hl-7); 33 | --hl-8: var(--light-hl-8); 34 | --code-background: var(--light-code-background); 35 | } } 36 | 37 | @media (prefers-color-scheme: dark) { :root { 38 | --hl-0: var(--dark-hl-0); 39 | --hl-1: var(--dark-hl-1); 40 | --hl-2: var(--dark-hl-2); 41 | --hl-3: var(--dark-hl-3); 42 | --hl-4: var(--dark-hl-4); 43 | --hl-5: var(--dark-hl-5); 44 | --hl-6: var(--dark-hl-6); 45 | --hl-7: var(--dark-hl-7); 46 | --hl-8: var(--dark-hl-8); 47 | --code-background: var(--dark-code-background); 48 | } } 49 | 50 | :root[data-theme='light'] { 51 | --hl-0: var(--light-hl-0); 52 | --hl-1: var(--light-hl-1); 53 | --hl-2: var(--light-hl-2); 54 | --hl-3: var(--light-hl-3); 55 | --hl-4: var(--light-hl-4); 56 | --hl-5: var(--light-hl-5); 57 | --hl-6: var(--light-hl-6); 58 | --hl-7: var(--light-hl-7); 59 | --hl-8: var(--light-hl-8); 60 | --code-background: var(--light-code-background); 61 | } 62 | 63 | :root[data-theme='dark'] { 64 | --hl-0: var(--dark-hl-0); 65 | --hl-1: var(--dark-hl-1); 66 | --hl-2: var(--dark-hl-2); 67 | --hl-3: var(--dark-hl-3); 68 | --hl-4: var(--dark-hl-4); 69 | --hl-5: var(--dark-hl-5); 70 | --hl-6: var(--dark-hl-6); 71 | --hl-7: var(--dark-hl-7); 72 | --hl-8: var(--dark-hl-8); 73 | --code-background: var(--dark-code-background); 74 | } 75 | 76 | .hl-0 { color: var(--hl-0); } 77 | .hl-1 { color: var(--hl-1); } 78 | .hl-2 { color: var(--hl-2); } 79 | .hl-3 { color: var(--hl-3); } 80 | .hl-4 { color: var(--hl-4); } 81 | .hl-5 { color: var(--hl-5); } 82 | .hl-6 { color: var(--hl-6); } 83 | .hl-7 { color: var(--hl-7); } 84 | .hl-8 { color: var(--hl-8); } 85 | pre, code { background: var(--code-background); } 86 | -------------------------------------------------------------------------------- /docs/assets/icons.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | addIcons(); 3 | function addIcons() { 4 | if (document.readyState === "loading") return document.addEventListener("DOMContentLoaded", addIcons); 5 | const svg = document.body.appendChild(document.createElementNS("http://www.w3.org/2000/svg", "svg")); 6 | svg.innerHTML = `""`; 7 | svg.style.display = "none"; 8 | if (location.protocol === "file:") updateUseElements(); 9 | } 10 | 11 | function updateUseElements() { 12 | document.querySelectorAll("use").forEach(el => { 13 | if (el.getAttribute("href").includes("#icon-")) { 14 | el.setAttribute("href", el.getAttribute("href").replace(/.*#/, "#")); 15 | } 16 | }); 17 | } 18 | })() -------------------------------------------------------------------------------- /docs/assets/icons.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/assets/navigation.js: -------------------------------------------------------------------------------- 1 | window.navigationData = "data:application/octet-stream;base64,H4sIAAAAAAAAE4uuVipJrShRslIKDfV0UdJRKkgsyQDyknMSi4tTi/VBonoZJbk5QKnszLwUJStDI4taHbiuMHP31LzUosSS/CJMzUiS+MwoLc1MKTNBaE8rzUsuyczPK9aHyKDqNTPB0JqflIVbN1CSkAHmOHWbE9SKz25zbHbHAgDi9KHtcwEAAA==" -------------------------------------------------------------------------------- /docs/assets/search.js: -------------------------------------------------------------------------------- 1 | window.searchData = "data:application/octet-stream;base64,H4sIAAAAAAAAE62YXW/aMBSG/4t7G1GOCQlwN23a1l2s0rpxg9CUgmnTQdwlhm1C/PfZ+cDHtZM5rDdVHZ/39TF+fOzkSHL+qyCzxZH8SLM1mQGdBCRLdozMyLdvN+9IQPb5VjZW26QoWHGtHg4exW4re+pnspecgsaBDkNtwTc3WcbydpcrHYHcAvKc5CwTTRIt7puc796nbLsu5nHHEC/C+o8jgwrWMUDT7+UMQxqene//CBnf7tz0989Z8DuRp9lDhzkKucT/I/vdaV71X+L86e72c6d1HdDf+4GJeZKniQxs9zeCLhuD5UXKs3+McQ7qP8Zqy7MuIpv+/s7s5z7ZdiF5Drgga75TcV95V+Yoxm8/oYo1jz8wWUwSwe2Sg/o669cYKEo4K0S+X/n4XZnB7txxgq34lAH26loDosj/Hu02f3PPc3tXtA6qBa8w9hdWMPGW5z0mbYpea/59c8CiPjlE+gjY79P1QR9fm322ErIsFNdVRyerlg2/f2p1kn29zMI2p7CfTUdOYUtOy4BIL3l+zI7kUJfJGaGD0WAqIzflWS4vLdU4gaorO1ZW6zVf7ct/l3XYnKn9qIKr6OshCRbDgMaDmEbLZbBoxGVH+aDx0E9KIcgWuIRgCcEQUtmiLiG1hNQQjmRr5BKOLOHIEMqVW4QuYWgJQ0M4lq2xSzi2hGNDGMlW5BJGljAyhBL8RewSxpYwNoQT2Zq4hBNLODGEkqDF1CWcWsKpCYDiAZzsgA0PvKCnxMfNjwMgkyBQXICTIbAhApMiUGyAkyOwQQKTJFB8gJMlsGECkyZQjICTJ7CBApMoUJyAkymwoQKTKlCsgJMrsMECkyxQvICTLbDhApMuUMyAky+wAQOTMKqYoU7CqE0YNQmjihnqJIzahNEXNaosUu4q5ShT1aOySsvyLNj6pqrWssjWbyxH8r2u4Ofj4EhC+ed00vVatlDJVn3lwNXdVTvIPXe2kNvKy6O6RQpu+FDkQz190J0OOaFJgd+smmszMgFkAl4m6k22Ov3UjUFboYn5zUtfHVE+Y5TPuJcNz5PqMojcIuQWXeK2Kq9VyHGCHCc9HXN1WbQcY+QYezqKQ/NuqI1QZr6JieZeg2ym2mbqZcM3afXxBM0JTcnLo/54oR1G2mHk5SD4o3rV1w5o6f1WXvCnwvwp0Mr4LYzgRf0xQ5sgoP14VpdSbIAKj1/daS7OaGuiBaF+K4KuzcgH73G/Td68WSA4EGHghxh6r0DJoB+G+v0yh/hBv5qjjBBt4IGbPHOe02e2TeURMVssT6e/65x4y8IUAAA="; -------------------------------------------------------------------------------- /docs/classes/V7Generator.html: -------------------------------------------------------------------------------- 1 | V7Generator | uuidv7

Class V7Generator

Encapsulates the monotonic counter state.

2 |

This class provides APIs to utilize a separate counter state from that of the 3 | global generator used by uuidv7 and uuidv7obj. In addition to 4 | the default generate method, this class has generateOrAbort 5 | that is useful to absolutely guarantee the monotonically increasing order of 6 | generated UUIDs. See their respective documentation for details.

7 |

Constructors

  • Creates a generator object with the default random number generator, or 13 | with the specified one if passed as an argument. The specified random 14 | number generator should be cryptographically strong and securely seeded.

    15 |

    Parameters

    • OptionalrandomNumberGenerator: {
          nextUint32(): number;
      }
      • nextUint32:function
        • Returns a 32-bit random unsigned integer.

          16 |

          Returns number

    Returns V7Generator

Methods

  • Generates a new UUIDv7 object from the current timestamp, or resets the 17 | generator upon significant timestamp rollback.

    18 |

    This method returns a monotonically increasing UUID by reusing the previous 19 | timestamp even if the up-to-date timestamp is smaller than the immediately 20 | preceding UUID's. However, when such a clock rollback is considered 21 | significant (i.e., by more than ten seconds), this method resets the 22 | generator and returns a new UUID based on the given timestamp, breaking the 23 | increasing order of UUIDs.

    24 |

    See generateOrAbort for the other mode of generation and 25 | generateOrResetCore for the low-level primitive.

    26 |

    Returns UUID

  • Generates a new UUIDv7 object from the current timestamp, or returns 27 | undefined upon significant timestamp rollback.

    28 |

    This method returns a monotonically increasing UUID by reusing the previous 29 | timestamp even if the up-to-date timestamp is smaller than the immediately 30 | preceding UUID's. However, when such a clock rollback is considered 31 | significant (i.e., by more than ten seconds), this method aborts and 32 | returns undefined immediately.

    33 |

    See generate for the other mode of generation and 34 | generateOrAbortCore for the low-level primitive.

    35 |

    Returns undefined | UUID

  • Generates a new UUIDv7 object from the unixTsMs passed, or returns 36 | undefined upon significant timestamp rollback.

    37 |

    This method is equivalent to generateOrAbort except that it takes a 38 | custom timestamp and clock rollback allowance.

    39 |

    Parameters

    • unixTsMs: number
    • rollbackAllowance: number

      The amount of unixTsMs rollback that is 40 | considered significant. A suggested value is 10_000 (milliseconds).

      41 |

    Returns undefined | UUID

    RangeError if unixTsMs is not a 48-bit positive integer.

    42 |
  • Generates a new UUIDv7 object from the unixTsMs passed, or resets the 43 | generator upon significant timestamp rollback.

    44 |

    This method is equivalent to generate except that it takes a custom 45 | timestamp and clock rollback allowance.

    46 |

    Parameters

    • unixTsMs: number
    • rollbackAllowance: number

      The amount of unixTsMs rollback that is 47 | considered significant. A suggested value is 10_000 (milliseconds).

      48 |

    Returns UUID

    RangeError if unixTsMs is not a 48-bit positive integer.

    49 |
50 | -------------------------------------------------------------------------------- /docs/functions/uuidv4.html: -------------------------------------------------------------------------------- 1 | uuidv4 | uuidv7

Function uuidv4

Generates a UUIDv4 string.

2 |
  • Returns string

    The 8-4-4-4-12 canonical hexadecimal string representation 3 | ("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx").

    4 |
5 | -------------------------------------------------------------------------------- /docs/functions/uuidv4obj.html: -------------------------------------------------------------------------------- 1 | uuidv4obj | uuidv7

Function uuidv4obj

Generates a UUIDv4 object.

2 |
3 | -------------------------------------------------------------------------------- /docs/functions/uuidv7.html: -------------------------------------------------------------------------------- 1 | uuidv7 | uuidv7

Function uuidv7

Generates a UUIDv7 string.

2 |
  • Returns string

    The 8-4-4-4-12 canonical hexadecimal string representation 3 | ("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx").

    4 |
5 | -------------------------------------------------------------------------------- /docs/functions/uuidv7obj.html: -------------------------------------------------------------------------------- 1 | uuidv7obj | uuidv7

Function uuidv7obj

Generates a UUIDv7 object.

2 |
3 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | uuidv7

uuidv7

uuidv7: A JavaScript implementation of UUID version 7

npm 2 | License

3 |
import { uuidv7 } from "uuidv7";

const result = uuidv7(); // e.g., "017fe537-bb13-7c35-b52a-cb5490cce7be" 4 |
5 | 6 |

On browsers and Deno:

7 |
import { uuidv7 } from "https://unpkg.com/uuidv7@^1";

const result = uuidv7(); // e.g., "017fe537-bb13-7c35-b52a-cb5490cce7be" 8 |
9 | 10 |

Command-line interface:

11 |
$ npx uuidv7
0189f7e5-c883-7106-8272-ccb7fcba0575
$
$ npx uuidv7 -n 4
0189f7ea-ae2c-7809-8aeb-b819cf5e9e7f
0189f7ea-ae2f-72b9-9be8-9c3c5a60214f
0189f7ea-ae2f-72b9-9be8-9c3d224082ef
0189f7ea-ae2f-72b9-9be8-9c3e3e8abae8 12 |
13 | 14 |

See RFC 9562.

15 |

This implementation produces identifiers with the following bit layout:

16 |
 0                   1                   2                   3
17 |  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
18 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
19 | |                          unix_ts_ms                           |
20 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
21 | |          unix_ts_ms           |  ver  |        counter        |
22 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
23 | |var|                        counter                            |
24 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
25 | |                             rand                              |
26 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
27 | 
28 | 29 |

Where:

30 |
    31 |
  • The 48-bit unix_ts_ms field is dedicated to the Unix timestamp in 32 | milliseconds.
  • 33 |
  • The 4-bit ver field is set at 0111.
  • 34 |
  • The 42-bit counter field accommodates a counter that ensures the increasing 35 | order of IDs generated within a millisecond. The counter is incremented by one 36 | for each new ID and is reset to a random number when the unix_ts_ms changes.
  • 37 |
  • The 2-bit var field is set at 10.
  • 38 |
  • The remaining 32 rand bits are filled with a cryptographically strong random 39 | number.
  • 40 |
41 |

The 42-bit counter is sufficiently large, so you do not usually need to worry 42 | about overflow, but in an extremely rare circumstance where it overflows, this 43 | library increments the unix_ts_ms field to continue instant monotonic 44 | generation. As a result, the unix_ts_ms may have a greater value than that of 45 | the system's real-time clock. (See also Why so large counter? (42bits)).

46 |

UUIDv7, by design, relies on the system clock to guarantee the monotonically 47 | increasing order of generated IDs. A generator may not be able to produce a 48 | monotonic sequence if the system clock goes backwards. This library ignores a 49 | clock rollback and reuses the previous unix_ts_ms unless the clock rollback is 50 | considered significant (by default, more than ten seconds). If such a 51 | significant rollback takes place, this library resets the generator by default 52 | and thus breaks the increasing order of generated IDs.

53 |

This library also supports the generation of UUID version 4:

54 |
import { uuidv4 } from "uuidv7";

const result = uuidv4(); // e.g., "83229083-75c3-4da5-8378-f88ef1a2bcd1" 55 |
56 | 57 |

uuidv7obj() and uuidv4obj() return an object that represents a UUID as a 58 | 16-byte byte array:

59 |
import { uuidv7obj } from "uuidv7";

const object = uuidv7obj();
console.log(object.bytes); // Uint8Array(16) [ ... ]
console.log(String(object)); // e.g., "017fea6b-b877-7aef-b422-57db9ed15e9d"

console.assert(object.getVariant() === "VAR_10");
console.assert(object.getVersion() === 7);

console.assert(object.clone().equals(object));
console.assert(object.compareTo(uuidv7obj()) < 0); 60 |
61 | 62 |

The V7Generator primitive allows to utilize a separate counter state from that 63 | of the global generator. It also provides a fallible variant of the generator 64 | function to give an absolute guarantee of the increasing order of UUIDs despite 65 | a significant rollback of the system timestamp source.

66 |
import { V7Generator } from "uuidv7";

const g = new V7Generator();
const x = g.generate();
const y = g.generateOrAbort();
if (y === undefined) {
throw new Error("The clock went backwards by ten seconds!");
}
console.assert(x.compareTo(y) < 0); 67 |
68 | 69 |

See the API documentation for details.

70 |

The CommonJS entry point is deprecated and provided for backward compatibility 71 | purposes only. The entry point is no longer tested and will be removed in the 72 | future.

73 |

Licensed under the Apache License, Version 2.0.

74 |

Uuid25 provides the conversion to/from a 75 | condensed, sortable, case-insensitive, 25-digit Base36 representation of UUID as 76 | well as other well-known textual representations. Uuid25 is available in several 77 | languages including Go, JavaScript, Python, Rust, and Swift.

78 |
import { uuidv7obj } from "uuidv7";
import { Uuid25 } from "uuid25";

const uuid25 = Uuid25.fromBytes(uuidv7obj().bytes);

console.log(uuid25.value);
// e.g., "03a2s63x4x0b9mev9e88i7gpm"

console.log(uuid25.toHex());
// e.g., "0189f8068f1a79b6bb21123c6accc25a"
console.log(uuid25.toHyphenated());
// e.g., "0189f806-8f1a-79b6-bb21-123c6accc25a"
console.log(uuid25.toBraced());
// e.g., "{0189f806-8f1a-79b6-bb21-123c6accc25a}"
console.log(uuid25.toUrn());
// e.g., "urn:uuid:0189f806-8f1a-79b6-bb21-123c6accc25a" 79 |
80 | 81 |
82 | -------------------------------------------------------------------------------- /docs/modules.html: -------------------------------------------------------------------------------- 1 | uuidv7

uuidv7

Index

Classes

UUID 2 | V7Generator 3 |

Functions

uuidv4 4 | uuidv4obj 5 | uuidv7 6 | uuidv7obj 7 |
8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "uuidv7", 3 | "version": "1.0.2", 4 | "description": "A JavaScript implementation of UUID version 7", 5 | "type": "module", 6 | "main": "dist/index.js", 7 | "types": "./dist/index.d.ts", 8 | "exports": { 9 | "require": "./dist/index.cjs", 10 | "default": "./dist/index.js" 11 | }, 12 | "bin": { 13 | "uuidv7": "cli.js" 14 | }, 15 | "files": [ 16 | "CHANGELOG.md", 17 | "dist" 18 | ], 19 | "sideEffects": false, 20 | "scripts": { 21 | "build": "tsc", 22 | "doc": "typedoc ./src/index.ts --gitRevision \"v$npm_package_version\"", 23 | "prebuild": "rm -rf ./dist", 24 | "predoc": "rm -rf ./docs", 25 | "prepare": "npm run build && npm run doc && npm run test", 26 | "test": "mocha" 27 | }, 28 | "repository": { 29 | "type": "git", 30 | "url": "git+https://github.com/LiosK/uuidv7.git" 31 | }, 32 | "keywords": [ 33 | "uuid", 34 | "uuidv7" 35 | ], 36 | "author": "LiosK ", 37 | "license": "Apache-2.0", 38 | "bugs": { 39 | "url": "https://github.com/LiosK/uuidv7/issues" 40 | }, 41 | "homepage": "https://github.com/LiosK/uuidv7#readme", 42 | "devDependencies": { 43 | "mocha": "^10.7.3", 44 | "typedoc": "^0.26.6", 45 | "typescript": "^5.5.4" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/index.cts: -------------------------------------------------------------------------------- 1 | index.ts -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * uuidv7: A JavaScript implementation of UUID version 7 3 | * 4 | * Copyright 2021-2024 LiosK 5 | * 6 | * @license Apache-2.0 7 | * @packageDocumentation 8 | */ 9 | 10 | const DIGITS = "0123456789abcdef"; 11 | 12 | /** Represents a UUID as a 16-byte byte array. */ 13 | export class UUID { 14 | /** @param bytes - The 16-byte byte array representation. */ 15 | private constructor(readonly bytes: Readonly) {} 16 | 17 | /** 18 | * Creates an object from the internal representation, a 16-byte byte array 19 | * containing the binary UUID representation in the big-endian byte order. 20 | * 21 | * This method does NOT shallow-copy the argument, and thus the created object 22 | * holds the reference to the underlying buffer. 23 | * 24 | * @throws TypeError if the length of the argument is not 16. 25 | */ 26 | static ofInner(bytes: Readonly): UUID { 27 | if (bytes.length !== 16) { 28 | throw new TypeError("not 128-bit length"); 29 | } else { 30 | return new UUID(bytes); 31 | } 32 | } 33 | 34 | /** 35 | * Builds a byte array from UUIDv7 field values. 36 | * 37 | * @param unixTsMs - A 48-bit `unix_ts_ms` field value. 38 | * @param randA - A 12-bit `rand_a` field value. 39 | * @param randBHi - The higher 30 bits of 62-bit `rand_b` field value. 40 | * @param randBLo - The lower 32 bits of 62-bit `rand_b` field value. 41 | * @throws RangeError if any field value is out of the specified range. 42 | */ 43 | static fromFieldsV7( 44 | unixTsMs: number, 45 | randA: number, 46 | randBHi: number, 47 | randBLo: number, 48 | ): UUID { 49 | if ( 50 | !Number.isInteger(unixTsMs) || 51 | !Number.isInteger(randA) || 52 | !Number.isInteger(randBHi) || 53 | !Number.isInteger(randBLo) || 54 | unixTsMs < 0 || 55 | randA < 0 || 56 | randBHi < 0 || 57 | randBLo < 0 || 58 | unixTsMs > 0xffff_ffff_ffff || 59 | randA > 0xfff || 60 | randBHi > 0x3fff_ffff || 61 | randBLo > 0xffff_ffff 62 | ) { 63 | throw new RangeError("invalid field value"); 64 | } 65 | 66 | const bytes = new Uint8Array(16); 67 | bytes[0] = unixTsMs / 2 ** 40; 68 | bytes[1] = unixTsMs / 2 ** 32; 69 | bytes[2] = unixTsMs / 2 ** 24; 70 | bytes[3] = unixTsMs / 2 ** 16; 71 | bytes[4] = unixTsMs / 2 ** 8; 72 | bytes[5] = unixTsMs; 73 | bytes[6] = 0x70 | (randA >>> 8); 74 | bytes[7] = randA; 75 | bytes[8] = 0x80 | (randBHi >>> 24); 76 | bytes[9] = randBHi >>> 16; 77 | bytes[10] = randBHi >>> 8; 78 | bytes[11] = randBHi; 79 | bytes[12] = randBLo >>> 24; 80 | bytes[13] = randBLo >>> 16; 81 | bytes[14] = randBLo >>> 8; 82 | bytes[15] = randBLo; 83 | return new UUID(bytes); 84 | } 85 | 86 | /** 87 | * Builds a byte array from a string representation. 88 | * 89 | * This method accepts the following formats: 90 | * 91 | * - 32-digit hexadecimal format without hyphens: `0189dcd553117d408db09496a2eef37b` 92 | * - 8-4-4-4-12 hyphenated format: `0189dcd5-5311-7d40-8db0-9496a2eef37b` 93 | * - Hyphenated format with surrounding braces: `{0189dcd5-5311-7d40-8db0-9496a2eef37b}` 94 | * - RFC 9562 URN format: `urn:uuid:0189dcd5-5311-7d40-8db0-9496a2eef37b` 95 | * 96 | * Leading and trailing whitespaces represents an error. 97 | * 98 | * @throws SyntaxError if the argument could not parse as a valid UUID string. 99 | */ 100 | static parse(uuid: string): UUID { 101 | let hex: string | undefined = undefined; 102 | switch (uuid.length) { 103 | case 32: 104 | hex = /^[0-9a-f]{32}$/i.exec(uuid)?.[0]; 105 | break; 106 | case 36: 107 | hex = 108 | /^([0-9a-f]{8})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{12})$/i 109 | .exec(uuid) 110 | ?.slice(1, 6) 111 | .join(""); 112 | break; 113 | case 38: 114 | hex = 115 | /^\{([0-9a-f]{8})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{12})\}$/i 116 | .exec(uuid) 117 | ?.slice(1, 6) 118 | .join(""); 119 | break; 120 | case 45: 121 | hex = 122 | /^urn:uuid:([0-9a-f]{8})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{12})$/i 123 | .exec(uuid) 124 | ?.slice(1, 6) 125 | .join(""); 126 | break; 127 | default: 128 | break; 129 | } 130 | 131 | if (hex) { 132 | const inner = new Uint8Array(16); 133 | for (let i = 0; i < 16; i += 4) { 134 | const n = parseInt(hex.substring(2 * i, 2 * i + 8), 16); 135 | inner[i + 0] = n >>> 24; 136 | inner[i + 1] = n >>> 16; 137 | inner[i + 2] = n >>> 8; 138 | inner[i + 3] = n; 139 | } 140 | return new UUID(inner); 141 | } else { 142 | throw new SyntaxError("could not parse UUID string"); 143 | } 144 | } 145 | 146 | /** 147 | * @returns The 8-4-4-4-12 canonical hexadecimal string representation 148 | * (`0189dcd5-5311-7d40-8db0-9496a2eef37b`). 149 | */ 150 | toString(): string { 151 | let text = ""; 152 | for (let i = 0; i < this.bytes.length; i++) { 153 | text += DIGITS.charAt(this.bytes[i] >>> 4); 154 | text += DIGITS.charAt(this.bytes[i] & 0xf); 155 | if (i === 3 || i === 5 || i === 7 || i === 9) { 156 | text += "-"; 157 | } 158 | } 159 | return text; 160 | } 161 | 162 | /** 163 | * @returns The 32-digit hexadecimal representation without hyphens 164 | * (`0189dcd553117d408db09496a2eef37b`). 165 | */ 166 | toHex(): string { 167 | let text = ""; 168 | for (let i = 0; i < this.bytes.length; i++) { 169 | text += DIGITS.charAt(this.bytes[i] >>> 4); 170 | text += DIGITS.charAt(this.bytes[i] & 0xf); 171 | } 172 | return text; 173 | } 174 | 175 | /** @returns The 8-4-4-4-12 canonical hexadecimal string representation. */ 176 | toJSON(): string { 177 | return this.toString(); 178 | } 179 | 180 | /** 181 | * Reports the variant field value of the UUID or, if appropriate, "NIL" or 182 | * "MAX". 183 | * 184 | * For convenience, this method reports "NIL" or "MAX" if `this` represents 185 | * the Nil or Max UUID, although the Nil and Max UUIDs are technically 186 | * subsumed under the variants `0b0` and `0b111`, respectively. 187 | */ 188 | getVariant(): 189 | | "VAR_0" 190 | | "VAR_10" 191 | | "VAR_110" 192 | | "VAR_RESERVED" 193 | | "NIL" 194 | | "MAX" { 195 | const n = this.bytes[8] >>> 4; 196 | if (n < 0) { 197 | throw new Error("unreachable"); 198 | } else if (n <= 0b0111) { 199 | return this.bytes.every((e) => e === 0) ? "NIL" : "VAR_0"; 200 | } else if (n <= 0b1011) { 201 | return "VAR_10"; 202 | } else if (n <= 0b1101) { 203 | return "VAR_110"; 204 | } else if (n <= 0b1111) { 205 | return this.bytes.every((e) => e === 0xff) ? "MAX" : "VAR_RESERVED"; 206 | } else { 207 | throw new Error("unreachable"); 208 | } 209 | } 210 | 211 | /** 212 | * Returns the version field value of the UUID or `undefined` if the UUID does 213 | * not have the variant field value of `0b10`. 214 | */ 215 | getVersion(): number | undefined { 216 | return this.getVariant() === "VAR_10" ? this.bytes[6] >>> 4 : undefined; 217 | } 218 | 219 | /** Creates an object from `this`. */ 220 | clone(): UUID { 221 | return new UUID(this.bytes.slice(0)); 222 | } 223 | 224 | /** Returns true if `this` is equivalent to `other`. */ 225 | equals(other: UUID): boolean { 226 | return this.compareTo(other) === 0; 227 | } 228 | 229 | /** 230 | * Returns a negative integer, zero, or positive integer if `this` is less 231 | * than, equal to, or greater than `other`, respectively. 232 | */ 233 | compareTo(other: UUID): number { 234 | for (let i = 0; i < 16; i++) { 235 | const diff = this.bytes[i] - other.bytes[i]; 236 | if (diff !== 0) { 237 | return Math.sign(diff); 238 | } 239 | } 240 | return 0; 241 | } 242 | } 243 | 244 | /** 245 | * Encapsulates the monotonic counter state. 246 | * 247 | * This class provides APIs to utilize a separate counter state from that of the 248 | * global generator used by {@link uuidv7} and {@link uuidv7obj}. In addition to 249 | * the default {@link generate} method, this class has {@link generateOrAbort} 250 | * that is useful to absolutely guarantee the monotonically increasing order of 251 | * generated UUIDs. See their respective documentation for details. 252 | */ 253 | export class V7Generator { 254 | private timestamp = 0; 255 | private counter = 0; 256 | 257 | /** The random number generator used by the generator. */ 258 | private readonly random: { nextUint32(): number }; 259 | 260 | /** 261 | * Creates a generator object with the default random number generator, or 262 | * with the specified one if passed as an argument. The specified random 263 | * number generator should be cryptographically strong and securely seeded. 264 | */ 265 | constructor(randomNumberGenerator?: { 266 | /** Returns a 32-bit random unsigned integer. */ 267 | nextUint32(): number; 268 | }) { 269 | this.random = randomNumberGenerator ?? getDefaultRandom(); 270 | } 271 | 272 | /** 273 | * Generates a new UUIDv7 object from the current timestamp, or resets the 274 | * generator upon significant timestamp rollback. 275 | * 276 | * This method returns a monotonically increasing UUID by reusing the previous 277 | * timestamp even if the up-to-date timestamp is smaller than the immediately 278 | * preceding UUID's. However, when such a clock rollback is considered 279 | * significant (i.e., by more than ten seconds), this method resets the 280 | * generator and returns a new UUID based on the given timestamp, breaking the 281 | * increasing order of UUIDs. 282 | * 283 | * See {@link generateOrAbort} for the other mode of generation and 284 | * {@link generateOrResetCore} for the low-level primitive. 285 | */ 286 | generate(): UUID { 287 | return this.generateOrResetCore(Date.now(), 10_000); 288 | } 289 | 290 | /** 291 | * Generates a new UUIDv7 object from the current timestamp, or returns 292 | * `undefined` upon significant timestamp rollback. 293 | * 294 | * This method returns a monotonically increasing UUID by reusing the previous 295 | * timestamp even if the up-to-date timestamp is smaller than the immediately 296 | * preceding UUID's. However, when such a clock rollback is considered 297 | * significant (i.e., by more than ten seconds), this method aborts and 298 | * returns `undefined` immediately. 299 | * 300 | * See {@link generate} for the other mode of generation and 301 | * {@link generateOrAbortCore} for the low-level primitive. 302 | */ 303 | generateOrAbort(): UUID | undefined { 304 | return this.generateOrAbortCore(Date.now(), 10_000); 305 | } 306 | 307 | /** 308 | * Generates a new UUIDv7 object from the `unixTsMs` passed, or resets the 309 | * generator upon significant timestamp rollback. 310 | * 311 | * This method is equivalent to {@link generate} except that it takes a custom 312 | * timestamp and clock rollback allowance. 313 | * 314 | * @param rollbackAllowance - The amount of `unixTsMs` rollback that is 315 | * considered significant. A suggested value is `10_000` (milliseconds). 316 | * @throws RangeError if `unixTsMs` is not a 48-bit positive integer. 317 | */ 318 | generateOrResetCore(unixTsMs: number, rollbackAllowance: number): UUID { 319 | let value = this.generateOrAbortCore(unixTsMs, rollbackAllowance); 320 | if (value === undefined) { 321 | // reset state and resume 322 | this.timestamp = 0; 323 | value = this.generateOrAbortCore(unixTsMs, rollbackAllowance)!; 324 | } 325 | return value; 326 | } 327 | 328 | /** 329 | * Generates a new UUIDv7 object from the `unixTsMs` passed, or returns 330 | * `undefined` upon significant timestamp rollback. 331 | * 332 | * This method is equivalent to {@link generateOrAbort} except that it takes a 333 | * custom timestamp and clock rollback allowance. 334 | * 335 | * @param rollbackAllowance - The amount of `unixTsMs` rollback that is 336 | * considered significant. A suggested value is `10_000` (milliseconds). 337 | * @throws RangeError if `unixTsMs` is not a 48-bit positive integer. 338 | */ 339 | generateOrAbortCore( 340 | unixTsMs: number, 341 | rollbackAllowance: number, 342 | ): UUID | undefined { 343 | const MAX_COUNTER = 0x3ff_ffff_ffff; 344 | 345 | if ( 346 | !Number.isInteger(unixTsMs) || 347 | unixTsMs < 1 || 348 | unixTsMs > 0xffff_ffff_ffff 349 | ) { 350 | throw new RangeError("`unixTsMs` must be a 48-bit positive integer"); 351 | } else if (rollbackAllowance < 0 || rollbackAllowance > 0xffff_ffff_ffff) { 352 | throw new RangeError("`rollbackAllowance` out of reasonable range"); 353 | } 354 | 355 | if (unixTsMs > this.timestamp) { 356 | this.timestamp = unixTsMs; 357 | this.resetCounter(); 358 | } else if (unixTsMs + rollbackAllowance >= this.timestamp) { 359 | // go on with previous timestamp if new one is not much smaller 360 | this.counter++; 361 | if (this.counter > MAX_COUNTER) { 362 | // increment timestamp at counter overflow 363 | this.timestamp++; 364 | this.resetCounter(); 365 | } 366 | } else { 367 | // abort if clock went backwards to unbearable extent 368 | return undefined; 369 | } 370 | 371 | return UUID.fromFieldsV7( 372 | this.timestamp, 373 | Math.trunc(this.counter / 2 ** 30), 374 | this.counter & (2 ** 30 - 1), 375 | this.random.nextUint32(), 376 | ); 377 | } 378 | 379 | /** Initializes the counter at a 42-bit random integer. */ 380 | private resetCounter(): void { 381 | this.counter = 382 | this.random.nextUint32() * 0x400 + (this.random.nextUint32() & 0x3ff); 383 | } 384 | 385 | /** 386 | * Generates a new UUIDv4 object utilizing the random number generator inside. 387 | * 388 | * @internal 389 | */ 390 | generateV4(): UUID { 391 | const bytes = new Uint8Array( 392 | Uint32Array.of( 393 | this.random.nextUint32(), 394 | this.random.nextUint32(), 395 | this.random.nextUint32(), 396 | this.random.nextUint32(), 397 | ).buffer, 398 | ); 399 | bytes[6] = 0x40 | (bytes[6] >>> 4); 400 | bytes[8] = 0x80 | (bytes[8] >>> 2); 401 | return UUID.ofInner(bytes); 402 | } 403 | } 404 | 405 | /** A global flag to force use of cryptographically strong RNG. */ 406 | declare const UUIDV7_DENY_WEAK_RNG: boolean; 407 | 408 | /** Returns the default random number generator available in the environment. */ 409 | const getDefaultRandom = (): { nextUint32(): number } => { 410 | // detect Web Crypto API 411 | if ( 412 | typeof crypto !== "undefined" && 413 | typeof crypto.getRandomValues !== "undefined" 414 | ) { 415 | return new BufferedCryptoRandom(); 416 | } else { 417 | // fall back on Math.random() unless the flag is set to true 418 | if (typeof UUIDV7_DENY_WEAK_RNG !== "undefined" && UUIDV7_DENY_WEAK_RNG) { 419 | throw new Error("no cryptographically strong RNG available"); 420 | } 421 | return { 422 | nextUint32: (): number => 423 | Math.trunc(Math.random() * 0x1_0000) * 0x1_0000 + 424 | Math.trunc(Math.random() * 0x1_0000), 425 | }; 426 | } 427 | }; 428 | 429 | /** 430 | * Wraps `crypto.getRandomValues()` to enable buffering; this uses a small 431 | * buffer by default to avoid both unbearable throughput decline in some 432 | * environments and the waste of time and space for unused values. 433 | */ 434 | class BufferedCryptoRandom { 435 | private readonly buffer = new Uint32Array(8); 436 | private cursor = 0xffff; 437 | nextUint32(): number { 438 | if (this.cursor >= this.buffer.length) { 439 | crypto.getRandomValues(this.buffer); 440 | this.cursor = 0; 441 | } 442 | return this.buffer[this.cursor++]; 443 | } 444 | } 445 | 446 | let defaultGenerator: V7Generator | undefined; 447 | 448 | /** 449 | * Generates a UUIDv7 string. 450 | * 451 | * @returns The 8-4-4-4-12 canonical hexadecimal string representation 452 | * ("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"). 453 | */ 454 | export const uuidv7 = (): string => uuidv7obj().toString(); 455 | 456 | /** Generates a UUIDv7 object. */ 457 | export const uuidv7obj = (): UUID => 458 | (defaultGenerator || (defaultGenerator = new V7Generator())).generate(); 459 | 460 | /** 461 | * Generates a UUIDv4 string. 462 | * 463 | * @returns The 8-4-4-4-12 canonical hexadecimal string representation 464 | * ("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"). 465 | */ 466 | export const uuidv4 = (): string => uuidv4obj().toString(); 467 | 468 | /** Generates a UUIDv4 object. */ 469 | export const uuidv4obj = (): UUID => 470 | (defaultGenerator || (defaultGenerator = new V7Generator())).generateV4(); 471 | -------------------------------------------------------------------------------- /test/gen.test.js: -------------------------------------------------------------------------------- 1 | import { V7Generator } from "../dist/index.js"; 2 | const assert = (expression, message = "") => { 3 | if (!expression) { 4 | throw new Error("Assertion failed" + (message ? ": " + message : "")); 5 | } 6 | }; 7 | 8 | globalThis.UUIDV7_DENY_WEAK_RNG = true; 9 | 10 | describe("V7Generator", function () { 11 | /** Extracts the `unix_ts_ms` field value as a number from a UUID object. */ 12 | const timestamp = (uuid) => 13 | uuid.bytes.slice(0, 6).reduce((acc, e) => acc * 256 + e); 14 | 15 | describe("#generateOrResetCore()", function () { 16 | it("generates increasing IDs even with decreasing or constant timestamp", function () { 17 | const ts = 0x0123_4567_89ab; 18 | const g = new V7Generator(); 19 | 20 | let prev = g.generateOrResetCore(ts, 10_000); 21 | assert(timestamp(prev) === ts); 22 | 23 | for (let i = 0; i < 100_000; i++) { 24 | const curr = g.generateOrResetCore(ts - Math.min(9_999, i), 10_000); 25 | assert(prev.compareTo(curr) < 0); 26 | prev = curr; 27 | } 28 | assert(timestamp(prev) >= ts); 29 | }); 30 | 31 | it("breaks increasing order of IDs if timestamp goes backwards a lot", function () { 32 | const ts = 0x0123_4567_89ab; 33 | const g = new V7Generator(); 34 | 35 | let prev = g.generateOrResetCore(ts, 10_000); 36 | assert(timestamp(prev) === ts); 37 | 38 | let curr = g.generateOrResetCore(ts - 10_000, 10_000); 39 | assert(prev.compareTo(curr) < 0); 40 | 41 | prev = curr; 42 | curr = g.generateOrResetCore(ts - 10_001, 10_000); 43 | assert(prev.compareTo(curr) > 0); 44 | assert(timestamp(curr) == ts - 10_001); 45 | 46 | prev = curr; 47 | curr = g.generateOrResetCore(ts - 10_002, 10_000); 48 | assert(prev.compareTo(curr) < 0); 49 | }); 50 | }); 51 | 52 | describe("#generateOrAbortCore()", function () { 53 | it("generates increasing IDs even with decreasing or constant timestamp", function () { 54 | const ts = 0x0123_4567_89ab; 55 | const g = new V7Generator(); 56 | 57 | let prev = g.generateOrAbortCore(ts, 10_000); 58 | assert(prev !== undefined); 59 | assert(timestamp(prev) === ts); 60 | 61 | for (let i = 0; i < 100_000; i++) { 62 | const curr = g.generateOrAbortCore(ts - Math.min(9_999, i), 10_000); 63 | assert(curr !== undefined); 64 | assert(prev.compareTo(curr) < 0); 65 | prev = curr; 66 | } 67 | assert(timestamp(prev) >= ts); 68 | }); 69 | 70 | it("returns undefined if timestamp goes backwards a lot", function () { 71 | const ts = 0x0123_4567_89ab; 72 | const g = new V7Generator(); 73 | 74 | const prev = g.generateOrAbortCore(ts, 10_000); 75 | assert(prev !== undefined); 76 | assert(timestamp(prev) === ts); 77 | 78 | let curr = g.generateOrAbortCore(ts - 10_000, 10_000); 79 | assert(curr !== undefined); 80 | assert(prev.compareTo(curr) < 0); 81 | 82 | curr = g.generateOrAbortCore(ts - 10_001, 10_000); 83 | assert(curr === undefined); 84 | 85 | curr = g.generateOrAbortCore(ts - 10_002, 10_000); 86 | assert(curr === undefined); 87 | }); 88 | }); 89 | }); 90 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Mocha Tests 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 18 | 19 | 20 | 21 | 22 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /test/uuid.test.js: -------------------------------------------------------------------------------- 1 | import { UUID, uuidv7, uuidv7obj, uuidv4 } from "../dist/index.js"; 2 | const assert = (expression, message = "") => { 3 | if (!expression) { 4 | throw new Error("Assertion failed" + (message ? ": " + message : "")); 5 | } 6 | }; 7 | 8 | globalThis.UUIDV7_DENY_WEAK_RNG = true; 9 | 10 | describe("UUID object", function () { 11 | it("supports clone and comparison methods", function () { 12 | const ordered = [ 13 | UUID.ofInner(new Uint8Array(16).fill(0x00)), 14 | UUID.fromFieldsV7(0, 0, 0, 0), 15 | UUID.fromFieldsV7(0, 0, 0, 1), 16 | UUID.fromFieldsV7(0, 0, 0, 2 ** 32 - 1), 17 | UUID.fromFieldsV7(0, 0, 1, 0), 18 | UUID.fromFieldsV7(0, 0, 2 ** 30 - 1, 0), 19 | UUID.fromFieldsV7(0, 1, 0, 0), 20 | UUID.fromFieldsV7(0, 2 ** 12 - 1, 0, 0), 21 | UUID.fromFieldsV7(1, 0, 0, 0), 22 | UUID.fromFieldsV7(2, 0, 0, 0), 23 | ]; 24 | 25 | for (let i = 0; i < 1_000; i++) { 26 | ordered.push(uuidv7obj()); 27 | } 28 | 29 | ordered.push(UUID.fromFieldsV7(2 ** 48 - 1, 0, 0, 0)); 30 | ordered.push(UUID.ofInner(new Uint8Array(16).fill(0xff))); 31 | 32 | let prev = ordered.shift(); 33 | for (const curr of ordered) { 34 | assert(!curr.equals(prev)); 35 | assert(!prev.equals(curr)); 36 | assert(curr.compareTo(prev) > 0); 37 | assert(prev.compareTo(curr) < 0); 38 | 39 | const clone = curr.clone(); 40 | assert(curr != clone); 41 | assert(clone != curr); 42 | assert(curr.bytes.buffer != clone.bytes.buffer); 43 | assert(clone.bytes.buffer != curr.bytes.buffer); 44 | 45 | assert(curr.equals(clone)); 46 | assert(clone.equals(curr)); 47 | assert(curr.compareTo(clone) === 0); 48 | assert(clone.compareTo(curr) === 0); 49 | 50 | prev = curr; 51 | } 52 | }); 53 | 54 | it("reports variant and version fields", function () { 55 | const nil = UUID.ofInner(new Uint8Array(16).fill(0x00)); 56 | assert(nil.getVariant() === "NIL" && nil.getVersion() === undefined); 57 | 58 | const max = UUID.ofInner(new Uint8Array(16).fill(0xff)); 59 | assert(max.getVariant() === "MAX" && max.getVersion() === undefined); 60 | 61 | const obj = uuidv7obj(); 62 | for (let oct6 = 0; oct6 < 0x100; oct6++) { 63 | obj.bytes[6] = oct6; 64 | for (let oct8 = 0; oct8 < 0x100; oct8++) { 65 | obj.bytes[8] = oct8; 66 | 67 | const v = obj.getVariant(); 68 | if (v === "VAR_0") { 69 | assert(oct8 >>> 7 === 0b0 && obj.getVersion() === undefined); 70 | } else if (v === "VAR_10") { 71 | assert(oct8 >>> 6 === 0b10 && obj.getVersion() === oct6 >>> 4); 72 | } else if (v === "VAR_110") { 73 | assert(oct8 >>> 5 === 0b110 && obj.getVersion() === undefined); 74 | } else if (v === "VAR_RESERVED") { 75 | assert(oct8 >>> 5 === 0b111 && obj.getVersion() === undefined); 76 | } else { 77 | throw new Error("unexpected type value: " + v); 78 | } 79 | } 80 | } 81 | }); 82 | 83 | it("provides symmetric parse() and toString()", function () { 84 | const nil = UUID.ofInner(new Uint8Array(16).fill(0x00)).toString(); 85 | assert(UUID.parse(nil).toString() === nil); 86 | const max = UUID.ofInner(new Uint8Array(16).fill(0xff)).toString(); 87 | assert(UUID.parse(max).toString() === max); 88 | 89 | for (let i = 0; i < 1_000; i++) { 90 | const v7 = uuidv7(); 91 | assert(UUID.parse(v7).toString() === v7); 92 | 93 | const v4 = uuidv4(); 94 | assert(UUID.parse(v4).toString() === v4); 95 | } 96 | }); 97 | 98 | it("constructs instance from UUIDv7 fields", function () { 99 | for (const e of EXAMPLE_UUIDS) { 100 | const fs = e.fieldsV7; 101 | if (fs) { 102 | const x = UUID.fromFieldsV7( 103 | Number(fs[0]), 104 | Number(fs[1]), 105 | Number(fs[2] >> 32n), 106 | Number(fs[2] & 0xffff_ffffn), 107 | ); 108 | assert(x.toString() === e.hyphenated); 109 | assert(x.toHex() === e.hex); 110 | assert(x.bytes.length === e.bytes.length); 111 | for (let i = 0; i < e.bytes.length; i++) { 112 | assert(x.bytes[i] === e.bytes[i]); 113 | } 114 | } 115 | } 116 | }); 117 | }); 118 | 119 | describe("UUID.parse()", function () { 120 | it("parses manually prepared correct cases", function () { 121 | for (const format of ["hyphenated", "hex", "braced", "urn"]) { 122 | for (const e of EXAMPLE_UUIDS) { 123 | const x = UUID.parse(e[format]); 124 | assert(x.toString() === e.hyphenated); 125 | assert(x.toHex() === e.hex); 126 | assert(x.bytes.length === e.bytes.length); 127 | for (let i = 0; i < e.bytes.length; i++) { 128 | assert(x.bytes[i] === e.bytes[i]); 129 | } 130 | 131 | assert(UUID.parse(e[format].toUpperCase()).toString() === e.hyphenated); 132 | } 133 | } 134 | }); 135 | 136 | it("rejects manually prepared failing cases", function () { 137 | const fail = [ 138 | "", 139 | "0", 140 | " 0180a8f0-5b82-75b4-9fef-ecad657c30bb", 141 | "0180a8f0-5b84-7438-ab50-f0626f78002b ", 142 | " 0180a8f0-5b84-7438-ab50-f063bd5331af ", 143 | "+0180a8f0-5b84-7438-ab50-f06405d35edb", 144 | "-0180a8f0-5b84-7438-ab50-f06508df4c2d", 145 | "+180a8f0-5b84-7438-ab50-f066aa10a367", 146 | "-180a8f0-5b84-7438-ab50-f067cdce1d69", 147 | "0180a8f0-5b847438-ab50-f06991838802", 148 | "0180a8f0-5b84-74 8-ab50-f06bed27bdc7", 149 | "0180a8g0-5b84-7438-ab50-f06c91175b8a", 150 | "0180a8f0-5b84-7438-ab50_f06d3ea24429", 151 | " 82f1dd3c-de95-075b-93ff-a240f135f8fd", 152 | "82f1dd3c-de95-075b-93ff-a240f135f8fd ", 153 | " 82f1dd3c-de95-075b-93ff-a240f135f8fd ", 154 | "82f1dd3cd-e95-075b-93ff-a240f135f8fd", 155 | "82f1dd3c-de95075b-93ff-a240f135f8fd", 156 | "82f1dd3c-de95-075b93ff-a240-f135f8fd", 157 | "{8273b64c5ed0a88b10dad09a6a2b963c}", 158 | "urn:uuid:8273b64c5ed0a88b10dad09a6a2b963c", 159 | "06536892-0g22-499d-8aaf-b0dd9cfa69a4", 160 | "864eh78f-0571-46jf-a1w4-538v0fdoacff", 161 | "45f63383 ef0e 0d9d b1ba 834a9726829e", 162 | "leading c86e2e5f-1962-42c9-85d6-cb127040b107", 163 | "97f43427-788b-47bb-b2e8-cc7d79432a75 trailing", 164 | "910e7851-4521-45c4-866b-fc5464", 165 | "44b1796d-9d0b-4aac-81cd-ef8ed2b90e18b6fe54", 166 | "{0189f965-7b27-7dc0-8f96-1d8eb026b7e2]", 167 | "(0189f965-7b27-7dc0-8f96-1d8eb026b7e2}", 168 | "urn:uuld:0189f965-7b27-7dc0-8f96-1d8eb026b7e2", 169 | "0189f965-7b27-7dc0-8f96-1d8ebſ6b7e2", 170 | ].flatMap((e) => [e, e.toUpperCase()]); 171 | 172 | for (const e of fail) { 173 | let errCaught = undefined; 174 | try { 175 | UUID.parse(e); 176 | } catch (err) { 177 | errCaught = err; 178 | } 179 | assert(errCaught instanceof SyntaxError); 180 | } 181 | }); 182 | }); 183 | 184 | const EXAMPLE_UUIDS = [ 185 | { 186 | hyphenated: "00000000-0000-7000-8000-000000000000", 187 | hex: "00000000000070008000000000000000", 188 | braced: "{00000000-0000-7000-8000-000000000000}", 189 | urn: "urn:uuid:00000000-0000-7000-8000-000000000000", 190 | fieldsV7: [0x000000000000n, 0x000n, 0x0000000000000000n], 191 | bytes: [0, 0, 0, 0, 0, 0, 112, 0, 128, 0, 0, 0, 0, 0, 0, 0], 192 | }, 193 | { 194 | hyphenated: "00000000-0000-7000-bfff-ffffffffffff", 195 | hex: "0000000000007000bfffffffffffffff", 196 | braced: "{00000000-0000-7000-bfff-ffffffffffff}", 197 | urn: "urn:uuid:00000000-0000-7000-bfff-ffffffffffff", 198 | fieldsV7: [0x000000000000n, 0x000n, 0x3fffffffffffffffn], 199 | bytes: [0, 0, 0, 0, 0, 0, 112, 0, 191, 255, 255, 255, 255, 255, 255, 255], 200 | }, 201 | { 202 | hyphenated: "00000000-0000-7fff-8000-000000000000", 203 | hex: "0000000000007fff8000000000000000", 204 | braced: "{00000000-0000-7fff-8000-000000000000}", 205 | urn: "urn:uuid:00000000-0000-7fff-8000-000000000000", 206 | fieldsV7: [0x000000000000n, 0xfffn, 0x0000000000000000n], 207 | bytes: [0, 0, 0, 0, 0, 0, 127, 255, 128, 0, 0, 0, 0, 0, 0, 0], 208 | }, 209 | { 210 | hyphenated: "ffffffff-ffff-7000-8000-000000000000", 211 | hex: "ffffffffffff70008000000000000000", 212 | braced: "{ffffffff-ffff-7000-8000-000000000000}", 213 | urn: "urn:uuid:ffffffff-ffff-7000-8000-000000000000", 214 | fieldsV7: [0xffffffffffffn, 0x000n, 0x0000000000000000n], 215 | bytes: [255, 255, 255, 255, 255, 255, 112, 0, 128, 0, 0, 0, 0, 0, 0, 0], 216 | }, 217 | { 218 | hyphenated: "ffffffff-ffff-7fff-bfff-ffffffffffff", 219 | hex: "ffffffffffff7fffbfffffffffffffff", 220 | braced: "{ffffffff-ffff-7fff-bfff-ffffffffffff}", 221 | urn: "urn:uuid:ffffffff-ffff-7fff-bfff-ffffffffffff", 222 | fieldsV7: [0xffffffffffffn, 0xfffn, 0x3fffffffffffffffn], 223 | bytes: [ 224 | 255, 255, 255, 255, 255, 255, 127, 255, 191, 255, 255, 255, 255, 255, 255, 225 | 255, 226 | ], 227 | }, 228 | { 229 | hyphenated: "00c7ad2f-67fc-7775-aa5d-177c68e6c25e", 230 | hex: "00c7ad2f67fc7775aa5d177c68e6c25e", 231 | braced: "{00c7ad2f-67fc-7775-aa5d-177c68e6c25e}", 232 | urn: "urn:uuid:00c7ad2f-67fc-7775-aa5d-177c68e6c25e", 233 | fieldsV7: [0x00c7ad2f67fcn, 0x775n, 0x2a5d177c68e6c25en], 234 | bytes: [ 235 | 0, 199, 173, 47, 103, 252, 119, 117, 170, 93, 23, 124, 104, 230, 194, 94, 236 | ], 237 | }, 238 | { 239 | hyphenated: "017f22e2-79b0-7cc3-98c4-dc0c0c07398f", 240 | hex: "017f22e279b07cc398c4dc0c0c07398f", 241 | braced: "{017f22e2-79b0-7cc3-98c4-dc0c0c07398f}", 242 | urn: "urn:uuid:017f22e2-79b0-7cc3-98c4-dc0c0c07398f", 243 | fieldsV7: [0x017f22e279b0n, 0xcc3n, 0x18c4dc0c0c07398fn], 244 | bytes: [ 245 | 1, 127, 34, 226, 121, 176, 124, 195, 152, 196, 220, 12, 12, 7, 57, 143, 246 | ], 247 | }, 248 | { 249 | hyphenated: "0180ae59-078c-7b80-b113-2fe14a615fb3", 250 | hex: "0180ae59078c7b80b1132fe14a615fb3", 251 | braced: "{0180ae59-078c-7b80-b113-2fe14a615fb3}", 252 | urn: "urn:uuid:0180ae59-078c-7b80-b113-2fe14a615fb3", 253 | fieldsV7: [0x0180ae59078cn, 0xb80n, 0x31132fe14a615fb3n], 254 | bytes: [ 255 | 1, 128, 174, 89, 7, 140, 123, 128, 177, 19, 47, 225, 74, 97, 95, 179, 256 | ], 257 | }, 258 | { 259 | hyphenated: "0180ae59-0790-7f6d-897d-79370b09dd07", 260 | hex: "0180ae5907907f6d897d79370b09dd07", 261 | braced: "{0180ae59-0790-7f6d-897d-79370b09dd07}", 262 | urn: "urn:uuid:0180ae59-0790-7f6d-897d-79370b09dd07", 263 | fieldsV7: [0x0180ae590790n, 0xf6dn, 0x097d79370b09dd07n], 264 | bytes: [ 265 | 1, 128, 174, 89, 7, 144, 127, 109, 137, 125, 121, 55, 11, 9, 221, 7, 266 | ], 267 | }, 268 | { 269 | hyphenated: "0180ae59-0790-7f6d-897d-7938e16176fc", 270 | hex: "0180ae5907907f6d897d7938e16176fc", 271 | braced: "{0180ae59-0790-7f6d-897d-7938e16176fc}", 272 | urn: "urn:uuid:0180ae59-0790-7f6d-897d-7938e16176fc", 273 | fieldsV7: [0x0180ae590790n, 0xf6dn, 0x097d7938e16176fcn], 274 | bytes: [ 275 | 1, 128, 174, 89, 7, 144, 127, 109, 137, 125, 121, 56, 225, 97, 118, 252, 276 | ], 277 | }, 278 | { 279 | hyphenated: "0180ae59-0790-7f6d-897d-7939dbb56111", 280 | hex: "0180ae5907907f6d897d7939dbb56111", 281 | braced: "{0180ae59-0790-7f6d-897d-7939dbb56111}", 282 | urn: "urn:uuid:0180ae59-0790-7f6d-897d-7939dbb56111", 283 | fieldsV7: [0x0180ae590790n, 0xf6dn, 0x097d7939dbb56111n], 284 | bytes: [ 285 | 1, 128, 174, 89, 7, 144, 127, 109, 137, 125, 121, 57, 219, 181, 97, 17, 286 | ], 287 | }, 288 | { 289 | hyphenated: "0180ae59-0790-7f6d-897d-793af4b611fb", 290 | hex: "0180ae5907907f6d897d793af4b611fb", 291 | braced: "{0180ae59-0790-7f6d-897d-793af4b611fb}", 292 | urn: "urn:uuid:0180ae59-0790-7f6d-897d-793af4b611fb", 293 | fieldsV7: [0x0180ae590790n, 0xf6dn, 0x097d793af4b611fbn], 294 | bytes: [ 295 | 1, 128, 174, 89, 7, 144, 127, 109, 137, 125, 121, 58, 244, 182, 17, 251, 296 | ], 297 | }, 298 | { 299 | hyphenated: "0180ae59-0790-7f6d-897d-793be80c6ca4", 300 | hex: "0180ae5907907f6d897d793be80c6ca4", 301 | braced: "{0180ae59-0790-7f6d-897d-793be80c6ca4}", 302 | urn: "urn:uuid:0180ae59-0790-7f6d-897d-793be80c6ca4", 303 | fieldsV7: [0x0180ae590790n, 0xf6dn, 0x097d793be80c6ca4n], 304 | bytes: [ 305 | 1, 128, 174, 89, 7, 144, 127, 109, 137, 125, 121, 59, 232, 12, 108, 164, 306 | ], 307 | }, 308 | { 309 | hyphenated: "0180ae59-0790-7f6d-897d-793c00a6b6d7", 310 | hex: "0180ae5907907f6d897d793c00a6b6d7", 311 | braced: "{0180ae59-0790-7f6d-897d-793c00a6b6d7}", 312 | urn: "urn:uuid:0180ae59-0790-7f6d-897d-793c00a6b6d7", 313 | fieldsV7: [0x0180ae590790n, 0xf6dn, 0x097d793c00a6b6d7n], 314 | bytes: [ 315 | 1, 128, 174, 89, 7, 144, 127, 109, 137, 125, 121, 60, 0, 166, 182, 215, 316 | ], 317 | }, 318 | { 319 | hyphenated: "0180ae59-0791-7e79-8804-02ce2b5bc8d2", 320 | hex: "0180ae5907917e79880402ce2b5bc8d2", 321 | braced: "{0180ae59-0791-7e79-8804-02ce2b5bc8d2}", 322 | urn: "urn:uuid:0180ae59-0791-7e79-8804-02ce2b5bc8d2", 323 | fieldsV7: [0x0180ae590791n, 0xe79n, 0x080402ce2b5bc8d2n], 324 | bytes: [ 325 | 1, 128, 174, 89, 7, 145, 126, 121, 136, 4, 2, 206, 43, 91, 200, 210, 326 | ], 327 | }, 328 | { 329 | hyphenated: "09ed4f9e-971f-7343-a8a7-40c969795aa6", 330 | hex: "09ed4f9e971f7343a8a740c969795aa6", 331 | braced: "{09ed4f9e-971f-7343-a8a7-40c969795aa6}", 332 | urn: "urn:uuid:09ed4f9e-971f-7343-a8a7-40c969795aa6", 333 | fieldsV7: [0x09ed4f9e971fn, 0x343n, 0x28a740c969795aa6n], 334 | bytes: [ 335 | 9, 237, 79, 158, 151, 31, 115, 67, 168, 167, 64, 201, 105, 121, 90, 166, 336 | ], 337 | }, 338 | { 339 | hyphenated: "28c21084-2287-7e81-8f72-f0ca391ae12a", 340 | hex: "28c2108422877e818f72f0ca391ae12a", 341 | braced: "{28c21084-2287-7e81-8f72-f0ca391ae12a}", 342 | urn: "urn:uuid:28c21084-2287-7e81-8f72-f0ca391ae12a", 343 | fieldsV7: [0x28c210842287n, 0xe81n, 0x0f72f0ca391ae12an], 344 | bytes: [ 345 | 40, 194, 16, 132, 34, 135, 126, 129, 143, 114, 240, 202, 57, 26, 225, 42, 346 | ], 347 | }, 348 | { 349 | hyphenated: "36c6849e-d55a-740d-86e0-32eec9e03663", 350 | hex: "36c6849ed55a740d86e032eec9e03663", 351 | braced: "{36c6849e-d55a-740d-86e0-32eec9e03663}", 352 | urn: "urn:uuid:36c6849e-d55a-740d-86e0-32eec9e03663", 353 | fieldsV7: [0x36c6849ed55an, 0x40dn, 0x06e032eec9e03663n], 354 | bytes: [ 355 | 54, 198, 132, 158, 213, 90, 116, 13, 134, 224, 50, 238, 201, 224, 54, 99, 356 | ], 357 | }, 358 | { 359 | hyphenated: "3c118418-e261-7925-a2a0-bed422629452", 360 | hex: "3c118418e2617925a2a0bed422629452", 361 | braced: "{3c118418-e261-7925-a2a0-bed422629452}", 362 | urn: "urn:uuid:3c118418-e261-7925-a2a0-bed422629452", 363 | fieldsV7: [0x3c118418e261n, 0x925n, 0x22a0bed422629452n], 364 | bytes: [ 365 | 60, 17, 132, 24, 226, 97, 121, 37, 162, 160, 190, 212, 34, 98, 148, 82, 366 | ], 367 | }, 368 | { 369 | hyphenated: "462a9021-a1fb-78ac-9b64-24bc29937258", 370 | hex: "462a9021a1fb78ac9b6424bc29937258", 371 | braced: "{462a9021-a1fb-78ac-9b64-24bc29937258}", 372 | urn: "urn:uuid:462a9021-a1fb-78ac-9b64-24bc29937258", 373 | fieldsV7: [0x462a9021a1fbn, 0x8acn, 0x1b6424bc29937258n], 374 | bytes: [ 375 | 70, 42, 144, 33, 161, 251, 120, 172, 155, 100, 36, 188, 41, 147, 114, 88, 376 | ], 377 | }, 378 | { 379 | hyphenated: "565aa057-c667-7112-b002-f21048341917", 380 | hex: "565aa057c6677112b002f21048341917", 381 | braced: "{565aa057-c667-7112-b002-f21048341917}", 382 | urn: "urn:uuid:565aa057-c667-7112-b002-f21048341917", 383 | fieldsV7: [0x565aa057c667n, 0x112n, 0x3002f21048341917n], 384 | bytes: [ 385 | 86, 90, 160, 87, 198, 103, 113, 18, 176, 2, 242, 16, 72, 52, 25, 23, 386 | ], 387 | }, 388 | { 389 | hyphenated: "6ad0aeb5-2304-7b0c-9a8e-81248f251fd0", 390 | hex: "6ad0aeb523047b0c9a8e81248f251fd0", 391 | braced: "{6ad0aeb5-2304-7b0c-9a8e-81248f251fd0}", 392 | urn: "urn:uuid:6ad0aeb5-2304-7b0c-9a8e-81248f251fd0", 393 | fieldsV7: [0x6ad0aeb52304n, 0xb0cn, 0x1a8e81248f251fd0n], 394 | bytes: [ 395 | 106, 208, 174, 181, 35, 4, 123, 12, 154, 142, 129, 36, 143, 37, 31, 208, 396 | ], 397 | }, 398 | { 399 | hyphenated: "748f153a-906f-74dc-bf75-b34645e00cf6", 400 | hex: "748f153a906f74dcbf75b34645e00cf6", 401 | braced: "{748f153a-906f-74dc-bf75-b34645e00cf6}", 402 | urn: "urn:uuid:748f153a-906f-74dc-bf75-b34645e00cf6", 403 | fieldsV7: [0x748f153a906fn, 0x4dcn, 0x3f75b34645e00cf6n], 404 | bytes: [ 405 | 116, 143, 21, 58, 144, 111, 116, 220, 191, 117, 179, 70, 69, 224, 12, 246, 406 | ], 407 | }, 408 | { 409 | hyphenated: "936b5620-ef3d-7fc4-b44b-bd7257ac08aa", 410 | hex: "936b5620ef3d7fc4b44bbd7257ac08aa", 411 | braced: "{936b5620-ef3d-7fc4-b44b-bd7257ac08aa}", 412 | urn: "urn:uuid:936b5620-ef3d-7fc4-b44b-bd7257ac08aa", 413 | fieldsV7: [0x936b5620ef3dn, 0xfc4n, 0x344bbd7257ac08aan], 414 | bytes: [ 415 | 147, 107, 86, 32, 239, 61, 127, 196, 180, 75, 189, 114, 87, 172, 8, 170, 416 | ], 417 | }, 418 | { 419 | hyphenated: "a4f42edd-870a-7d95-8055-edd081914d74", 420 | hex: "a4f42edd870a7d958055edd081914d74", 421 | braced: "{a4f42edd-870a-7d95-8055-edd081914d74}", 422 | urn: "urn:uuid:a4f42edd-870a-7d95-8055-edd081914d74", 423 | fieldsV7: [0xa4f42edd870an, 0xd95n, 0x0055edd081914d74n], 424 | bytes: [ 425 | 164, 244, 46, 221, 135, 10, 125, 149, 128, 85, 237, 208, 129, 145, 77, 426 | 116, 427 | ], 428 | }, 429 | { 430 | hyphenated: "b5c73543-6c73-719f-8035-7b0dc5d14202", 431 | hex: "b5c735436c73719f80357b0dc5d14202", 432 | braced: "{b5c73543-6c73-719f-8035-7b0dc5d14202}", 433 | urn: "urn:uuid:b5c73543-6c73-719f-8035-7b0dc5d14202", 434 | fieldsV7: [0xb5c735436c73n, 0x19fn, 0x00357b0dc5d14202n], 435 | bytes: [ 436 | 181, 199, 53, 67, 108, 115, 113, 159, 128, 53, 123, 13, 197, 209, 66, 2, 437 | ], 438 | }, 439 | { 440 | hyphenated: "b6b89352-7d5b-7683-9e97-df98d7a5b321", 441 | hex: "b6b893527d5b76839e97df98d7a5b321", 442 | braced: "{b6b89352-7d5b-7683-9e97-df98d7a5b321}", 443 | urn: "urn:uuid:b6b89352-7d5b-7683-9e97-df98d7a5b321", 444 | fieldsV7: [0xb6b893527d5bn, 0x683n, 0x1e97df98d7a5b321n], 445 | bytes: [ 446 | 182, 184, 147, 82, 125, 91, 118, 131, 158, 151, 223, 152, 215, 165, 179, 447 | 33, 448 | ], 449 | }, 450 | { 451 | hyphenated: "c1074072-1c18-71d2-8fb4-869d5ad33723", 452 | hex: "c10740721c1871d28fb4869d5ad33723", 453 | braced: "{c1074072-1c18-71d2-8fb4-869d5ad33723}", 454 | urn: "urn:uuid:c1074072-1c18-71d2-8fb4-869d5ad33723", 455 | fieldsV7: [0xc10740721c18n, 0x1d2n, 0x0fb4869d5ad33723n], 456 | bytes: [ 457 | 193, 7, 64, 114, 28, 24, 113, 210, 143, 180, 134, 157, 90, 211, 55, 35, 458 | ], 459 | }, 460 | { 461 | hyphenated: "d73eac55-5b53-7d53-bf85-0566d85ea524", 462 | hex: "d73eac555b537d53bf850566d85ea524", 463 | braced: "{d73eac55-5b53-7d53-bf85-0566d85ea524}", 464 | urn: "urn:uuid:d73eac55-5b53-7d53-bf85-0566d85ea524", 465 | fieldsV7: [0xd73eac555b53n, 0xd53n, 0x3f850566d85ea524n], 466 | bytes: [ 467 | 215, 62, 172, 85, 91, 83, 125, 83, 191, 133, 5, 102, 216, 94, 165, 36, 468 | ], 469 | }, 470 | { 471 | hyphenated: "ee413d37-e4fc-71ba-942d-d8e5b3097832", 472 | hex: "ee413d37e4fc71ba942dd8e5b3097832", 473 | braced: "{ee413d37-e4fc-71ba-942d-d8e5b3097832}", 474 | urn: "urn:uuid:ee413d37-e4fc-71ba-942d-d8e5b3097832", 475 | fieldsV7: [0xee413d37e4fcn, 0x1ban, 0x142dd8e5b3097832n], 476 | bytes: [ 477 | 238, 65, 61, 55, 228, 252, 113, 186, 148, 45, 216, 229, 179, 9, 120, 50, 478 | ], 479 | }, 480 | { 481 | hyphenated: "00000000-0000-0000-0000-000000000000", 482 | hex: "00000000000000000000000000000000", 483 | braced: "{00000000-0000-0000-0000-000000000000}", 484 | urn: "urn:uuid:00000000-0000-0000-0000-000000000000", 485 | fieldsV7: undefined, 486 | bytes: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 487 | }, 488 | { 489 | hyphenated: "ffffffff-ffff-ffff-ffff-ffffffffffff", 490 | hex: "ffffffffffffffffffffffffffffffff", 491 | braced: "{ffffffff-ffff-ffff-ffff-ffffffffffff}", 492 | urn: "urn:uuid:ffffffff-ffff-ffff-ffff-ffffffffffff", 493 | fieldsV7: undefined, 494 | bytes: [ 495 | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 496 | 255, 497 | ], 498 | }, 499 | { 500 | hyphenated: "90252ae1-bdee-b5e6-4549-83a13e69d556", 501 | hex: "90252ae1bdeeb5e6454983a13e69d556", 502 | braced: "{90252ae1-bdee-b5e6-4549-83a13e69d556}", 503 | urn: "urn:uuid:90252ae1-bdee-b5e6-4549-83a13e69d556", 504 | fieldsV7: undefined, 505 | bytes: [ 506 | 144, 37, 42, 225, 189, 238, 181, 230, 69, 73, 131, 161, 62, 105, 213, 86, 507 | ], 508 | }, 509 | { 510 | hyphenated: "19c63717-dd78-907f-153d-c2d12a357ebb", 511 | hex: "19c63717dd78907f153dc2d12a357ebb", 512 | braced: "{19c63717-dd78-907f-153d-c2d12a357ebb}", 513 | urn: "urn:uuid:19c63717-dd78-907f-153d-c2d12a357ebb", 514 | fieldsV7: undefined, 515 | bytes: [ 516 | 25, 198, 55, 23, 221, 120, 144, 127, 21, 61, 194, 209, 42, 53, 126, 187, 517 | ], 518 | }, 519 | { 520 | hyphenated: "1df0de92-3543-c988-6d44-6b0ef75df795", 521 | hex: "1df0de923543c9886d446b0ef75df795", 522 | braced: "{1df0de92-3543-c988-6d44-6b0ef75df795}", 523 | urn: "urn:uuid:1df0de92-3543-c988-6d44-6b0ef75df795", 524 | fieldsV7: undefined, 525 | bytes: [ 526 | 29, 240, 222, 146, 53, 67, 201, 136, 109, 68, 107, 14, 247, 93, 247, 149, 527 | ], 528 | }, 529 | { 530 | hyphenated: "14e0fa56-29c7-0c0d-663f-5d326e51f1ce", 531 | hex: "14e0fa5629c70c0d663f5d326e51f1ce", 532 | braced: "{14e0fa56-29c7-0c0d-663f-5d326e51f1ce}", 533 | urn: "urn:uuid:14e0fa56-29c7-0c0d-663f-5d326e51f1ce", 534 | fieldsV7: undefined, 535 | bytes: [ 536 | 20, 224, 250, 86, 41, 199, 12, 13, 102, 63, 93, 50, 110, 81, 241, 206, 537 | ], 538 | }, 539 | { 540 | hyphenated: "bd3ba1d1-ed92-4804-b900-4b6f96124cf4", 541 | hex: "bd3ba1d1ed924804b9004b6f96124cf4", 542 | braced: "{bd3ba1d1-ed92-4804-b900-4b6f96124cf4}", 543 | urn: "urn:uuid:bd3ba1d1-ed92-4804-b900-4b6f96124cf4", 544 | fieldsV7: undefined, 545 | bytes: [ 546 | 189, 59, 161, 209, 237, 146, 72, 4, 185, 0, 75, 111, 150, 18, 76, 244, 547 | ], 548 | }, 549 | { 550 | hyphenated: "e8e1d087-617c-3a88-e8f4-789ab4a7cf65", 551 | hex: "e8e1d087617c3a88e8f4789ab4a7cf65", 552 | braced: "{e8e1d087-617c-3a88-e8f4-789ab4a7cf65}", 553 | urn: "urn:uuid:e8e1d087-617c-3a88-e8f4-789ab4a7cf65", 554 | fieldsV7: undefined, 555 | bytes: [ 556 | 232, 225, 208, 135, 97, 124, 58, 136, 232, 244, 120, 154, 180, 167, 207, 557 | 101, 558 | ], 559 | }, 560 | { 561 | hyphenated: "f309d5b0-2bf3-a736-7400-75948ad1ffc5", 562 | hex: "f309d5b02bf3a736740075948ad1ffc5", 563 | braced: "{f309d5b0-2bf3-a736-7400-75948ad1ffc5}", 564 | urn: "urn:uuid:f309d5b0-2bf3-a736-7400-75948ad1ffc5", 565 | fieldsV7: undefined, 566 | bytes: [ 567 | 243, 9, 213, 176, 43, 243, 167, 54, 116, 0, 117, 148, 138, 209, 255, 197, 568 | ], 569 | }, 570 | { 571 | hyphenated: "171fd840-f315-e732-2796-dea092d372b2", 572 | hex: "171fd840f315e7322796dea092d372b2", 573 | braced: "{171fd840-f315-e732-2796-dea092d372b2}", 574 | urn: "urn:uuid:171fd840-f315-e732-2796-dea092d372b2", 575 | fieldsV7: undefined, 576 | bytes: [ 577 | 23, 31, 216, 64, 243, 21, 231, 50, 39, 150, 222, 160, 146, 211, 114, 178, 578 | ], 579 | }, 580 | { 581 | hyphenated: "c885af25-4a61-954a-1687-c08e41f9940b", 582 | hex: "c885af254a61954a1687c08e41f9940b", 583 | braced: "{c885af25-4a61-954a-1687-c08e41f9940b}", 584 | urn: "urn:uuid:c885af25-4a61-954a-1687-c08e41f9940b", 585 | fieldsV7: undefined, 586 | bytes: [ 587 | 200, 133, 175, 37, 74, 97, 149, 74, 22, 135, 192, 142, 65, 249, 148, 11, 588 | ], 589 | }, 590 | { 591 | hyphenated: "3d46fe79-7828-7d4f-f1e5-7bdf80ab30e1", 592 | hex: "3d46fe7978287d4ff1e57bdf80ab30e1", 593 | braced: "{3d46fe79-7828-7d4f-f1e5-7bdf80ab30e1}", 594 | urn: "urn:uuid:3d46fe79-7828-7d4f-f1e5-7bdf80ab30e1", 595 | fieldsV7: undefined, 596 | bytes: [ 597 | 61, 70, 254, 121, 120, 40, 125, 79, 241, 229, 123, 223, 128, 171, 48, 225, 598 | ], 599 | }, 600 | { 601 | hyphenated: "e5d7215d-6e2c-3299-1506-498b84b32d33", 602 | hex: "e5d7215d6e2c32991506498b84b32d33", 603 | braced: "{e5d7215d-6e2c-3299-1506-498b84b32d33}", 604 | urn: "urn:uuid:e5d7215d-6e2c-3299-1506-498b84b32d33", 605 | fieldsV7: undefined, 606 | bytes: [ 607 | 229, 215, 33, 93, 110, 44, 50, 153, 21, 6, 73, 139, 132, 179, 45, 51, 608 | ], 609 | }, 610 | { 611 | hyphenated: "c2416789-944c-b584-e886-ac162d9112b7", 612 | hex: "c2416789944cb584e886ac162d9112b7", 613 | braced: "{c2416789-944c-b584-e886-ac162d9112b7}", 614 | urn: "urn:uuid:c2416789-944c-b584-e886-ac162d9112b7", 615 | fieldsV7: undefined, 616 | bytes: [ 617 | 194, 65, 103, 137, 148, 76, 181, 132, 232, 134, 172, 22, 45, 145, 18, 183, 618 | ], 619 | }, 620 | { 621 | hyphenated: "0947fa84-3806-088a-77aa-1b1ed69b7789", 622 | hex: "0947fa843806088a77aa1b1ed69b7789", 623 | braced: "{0947fa84-3806-088a-77aa-1b1ed69b7789}", 624 | urn: "urn:uuid:0947fa84-3806-088a-77aa-1b1ed69b7789", 625 | fieldsV7: undefined, 626 | bytes: [ 627 | 9, 71, 250, 132, 56, 6, 8, 138, 119, 170, 27, 30, 214, 155, 119, 137, 628 | ], 629 | }, 630 | { 631 | hyphenated: "44e76ce2-1f2e-77bd-badb-64850026fd86", 632 | hex: "44e76ce21f2e77bdbadb64850026fd86", 633 | braced: "{44e76ce2-1f2e-77bd-badb-64850026fd86}", 634 | urn: "urn:uuid:44e76ce2-1f2e-77bd-badb-64850026fd86", 635 | fieldsV7: undefined, 636 | bytes: [ 637 | 68, 231, 108, 226, 31, 46, 119, 189, 186, 219, 100, 133, 0, 38, 253, 134, 638 | ], 639 | }, 640 | { 641 | hyphenated: "7275ea47-7628-0fa8-2afb-0c4b47f148c3", 642 | hex: "7275ea4776280fa82afb0c4b47f148c3", 643 | braced: "{7275ea47-7628-0fa8-2afb-0c4b47f148c3}", 644 | urn: "urn:uuid:7275ea47-7628-0fa8-2afb-0c4b47f148c3", 645 | fieldsV7: undefined, 646 | bytes: [ 647 | 114, 117, 234, 71, 118, 40, 15, 168, 42, 251, 12, 75, 71, 241, 72, 195, 648 | ], 649 | }, 650 | { 651 | hyphenated: "20a6bdda-fff4-faa1-4e8f-c0eb75a169f9", 652 | hex: "20a6bddafff4faa14e8fc0eb75a169f9", 653 | braced: "{20a6bdda-fff4-faa1-4e8f-c0eb75a169f9}", 654 | urn: "urn:uuid:20a6bdda-fff4-faa1-4e8f-c0eb75a169f9", 655 | fieldsV7: undefined, 656 | bytes: [ 657 | 32, 166, 189, 218, 255, 244, 250, 161, 78, 143, 192, 235, 117, 161, 105, 658 | 249, 659 | ], 660 | }, 661 | ]; 662 | -------------------------------------------------------------------------------- /test/uuidv4.test.js: -------------------------------------------------------------------------------- 1 | import { UUID, uuidv4, uuidv4obj } from "../dist/index.js"; 2 | const assert = (expression, message = "") => { 3 | if (!expression) { 4 | throw new Error("Assertion failed" + (message ? ": " + message : "")); 5 | } 6 | }; 7 | 8 | globalThis.UUIDV7_DENY_WEAK_RNG = true; 9 | 10 | describe("uuidv4()", function () { 11 | const samples = []; 12 | for (let i = 0; i < 100_000; i++) { 13 | samples[i] = uuidv4(); 14 | } 15 | 16 | it("returns 8-4-4-4-12 hexadecimal string representation", function () { 17 | samples.forEach((e) => assert(typeof e === "string")); 18 | const re = 19 | /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/; 20 | samples.forEach((e) => assert(re.test(e))); 21 | }); 22 | 23 | it("generates unique identifier", function () { 24 | assert(new Set(samples).size === samples.length); 25 | }); 26 | 27 | it("sets constant bits and random bits properly", function () { 28 | // count '1' of each bit 29 | const bins = new Array(128).fill(0); 30 | for (const e of samples) { 31 | const hs = e.replaceAll("-", ""); 32 | for (let i = 0; i < 8; i++) { 33 | const n = parseInt(hs.substring(i * 4, i * 4 + 4), 16); 34 | for (let j = 0; j < 16; j++) { 35 | const mask = 0x8000 >>> j; 36 | if (n & mask) { 37 | bins[i * 16 + j]++; 38 | } 39 | } 40 | } 41 | } 42 | 43 | // test if constant bits are all set to 1 or 0 44 | const n = samples.length; 45 | assert(bins[48] === 0, "version bit 48"); 46 | assert(bins[49] === n, "version bit 49"); 47 | assert(bins[50] === 0, "version bit 50"); 48 | assert(bins[51] === 0, "version bit 51"); 49 | assert(bins[64] === n, "variant bit 64"); 50 | assert(bins[65] === 0, "variant bit 65"); 51 | 52 | // test if random bits are set to 1 at ~50% probability 53 | // set margin based on binom dist 99.999% confidence interval 54 | const margin = 4.417173 * Math.sqrt((0.5 * 0.5) / n); 55 | for (let i = 0; i < 128; i++) { 56 | if ( 57 | i === 48 || 58 | i === 49 || 59 | i === 50 || 60 | i === 51 || 61 | i === 64 || 62 | i === 65 63 | ) { 64 | continue; 65 | } 66 | const p = bins[i] / n; 67 | assert(Math.abs(p - 0.5) < margin, `random bit ${i}: ${p}`); 68 | } 69 | }); 70 | }); 71 | 72 | describe("uuidv4obj()", function () { 73 | const samples = []; 74 | for (let i = 0; i < 1_000; i++) { 75 | samples[i] = uuidv4obj(); 76 | } 77 | 78 | it("returns object with 16-byte byte array property", function () { 79 | samples.forEach((e) => 80 | assert( 81 | e instanceof UUID && 82 | e.bytes instanceof Uint8Array && 83 | e.bytes.length === 16, 84 | ), 85 | ); 86 | }); 87 | 88 | it("returns object with correct variant and version", function () { 89 | samples.forEach((e) => 90 | assert(e.getVariant() === "VAR_10" && e.getVersion() === 4), 91 | ); 92 | }); 93 | 94 | it("returns object with toString() that returns 8-4-4-4-12", function () { 95 | const re = 96 | /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/; 97 | samples.forEach((e) => assert(re.test(String(e)))); 98 | }); 99 | 100 | it("returns object with toJSON() that returns 8-4-4-4-12", function () { 101 | const re = 102 | /^"[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}"$/; 103 | samples.forEach((e) => assert(re.test(JSON.stringify(e)))); 104 | }); 105 | }); 106 | -------------------------------------------------------------------------------- /test/uuidv7.test.js: -------------------------------------------------------------------------------- 1 | import { UUID, uuidv7, uuidv7obj } from "../dist/index.js"; 2 | const assert = (expression, message = "") => { 3 | if (!expression) { 4 | throw new Error("Assertion failed" + (message ? ": " + message : "")); 5 | } 6 | }; 7 | 8 | globalThis.UUIDV7_DENY_WEAK_RNG = true; 9 | 10 | describe("uuidv7()", function () { 11 | const samples = []; 12 | for (let i = 0; i < 100_000; i++) { 13 | samples[i] = uuidv7(); 14 | } 15 | 16 | it("returns 8-4-4-4-12 hexadecimal string representation", function () { 17 | samples.forEach((e) => assert(typeof e === "string")); 18 | const re = 19 | /^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/; 20 | samples.forEach((e) => assert(re.test(e))); 21 | }); 22 | 23 | it("generates unique identifier", function () { 24 | assert(new Set(samples).size === samples.length); 25 | }); 26 | 27 | it("generates sortable string representation by creation time", function () { 28 | const sorted = samples.slice().sort(); 29 | for (let i = 0; i < samples.length; i++) { 30 | assert(samples[i] === sorted[i]); 31 | } 32 | }); 33 | 34 | it("encodes up-to-date unix timestamp", function () { 35 | // tests leading 48 bits only 36 | const re = /^([0-9a-f]{8})-([0-9a-f]{4})-/; 37 | for (let i = 0; i < 10_000; i++) { 38 | const now = Date.now(); 39 | const m = re.exec(uuidv7()); 40 | const unixTsMs = parseInt(m[1] + m[2], 16); 41 | assert(Math.abs(now - unixTsMs) < 16); 42 | } 43 | }); 44 | 45 | it("encodes sortable timestamp and counter", function () { 46 | // tests leading 96 bits only (subsuming version and variant under counter) 47 | const re = 48 | /^([0-9a-f]{8})-([0-9a-f]{4})-(7[0-9a-f]{3})-([89ab][0-9a-f]{3})-([0-9a-f]{4})/; 49 | 50 | const m = re.exec(samples[0]); 51 | let prevU = parseInt(m[1] + m[2], 16); 52 | let prevC = parseInt(m[3] + m[4] + m[5], 16); 53 | for (let i = 1; i < samples.length; i++) { 54 | const m = re.exec(samples[i]); 55 | const unixTsMs = parseInt(m[1] + m[2], 16); 56 | const counter = parseInt(m[3] + m[4] + m[5], 16); 57 | assert(prevU < unixTsMs || (prevU === unixTsMs && prevC < counter)); 58 | prevU = unixTsMs; 59 | prevC = counter; 60 | } 61 | }); 62 | 63 | it("sets constant bits and random bits properly", function () { 64 | // count '1' of each bit 65 | const bins = new Array(128).fill(0); 66 | for (const e of samples) { 67 | const hs = e.replaceAll("-", ""); 68 | for (let i = 0; i < 8; i++) { 69 | const n = parseInt(hs.substring(i * 4, i * 4 + 4), 16); 70 | for (let j = 0; j < 16; j++) { 71 | const mask = 0x8000 >>> j; 72 | if (n & mask) { 73 | bins[i * 16 + j]++; 74 | } 75 | } 76 | } 77 | } 78 | 79 | // test if constant bits are all set to 1 or 0 80 | const n = samples.length; 81 | assert(bins[48] === 0, "version bit 48"); 82 | assert(bins[49] === n, "version bit 49"); 83 | assert(bins[50] === n, "version bit 50"); 84 | assert(bins[51] === n, "version bit 51"); 85 | assert(bins[64] === n, "variant bit 64"); 86 | assert(bins[65] === 0, "variant bit 65"); 87 | 88 | // test if random bits are set to 1 at ~50% probability 89 | // set margin based on binom dist 99.999% confidence interval 90 | const margin = 4.417173 * Math.sqrt((0.5 * 0.5) / n); 91 | for (let i = 96; i < 128; i++) { 92 | const p = bins[i] / n; 93 | assert(Math.abs(p - 0.5) < margin, `random bit ${i}: ${p}`); 94 | } 95 | }); 96 | }); 97 | 98 | describe("uuidv7obj()", function () { 99 | const samples = []; 100 | for (let i = 0; i < 1_000; i++) { 101 | samples[i] = uuidv7obj(); 102 | } 103 | 104 | it("returns object with 16-byte byte array property", function () { 105 | samples.forEach((e) => 106 | assert( 107 | e instanceof UUID && 108 | e.bytes instanceof Uint8Array && 109 | e.bytes.length === 16, 110 | ), 111 | ); 112 | }); 113 | 114 | it("returns object with correct variant and version", function () { 115 | samples.forEach((e) => 116 | assert(e.getVariant() === "VAR_10" && e.getVersion() === 7), 117 | ); 118 | }); 119 | 120 | it("returns object with toString() that returns 8-4-4-4-12", function () { 121 | const re = 122 | /^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/; 123 | samples.forEach((e) => assert(re.test(String(e)))); 124 | }); 125 | 126 | it("returns object with toJSON() that returns 8-4-4-4-12", function () { 127 | const re = 128 | /^"[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}"$/; 129 | samples.forEach((e) => assert(re.test(JSON.stringify(e)))); 130 | }); 131 | }); 132 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2016", 4 | "module": "node16", 5 | "moduleResolution": "node16", 6 | "declaration": true, 7 | "outDir": "./dist/", 8 | "stripInternal": true, 9 | "isolatedModules": true, 10 | "esModuleInterop": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "strict": true, 13 | "skipLibCheck": true 14 | } 15 | } 16 | --------------------------------------------------------------------------------