uniku 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Minimal synchronous SHA-1 (FIPS 180-4) for name-based UUIDs.
3
+ *
4
+ * Web Crypto's `digest()` is async and every uniku generator is synchronous,
5
+ * so `uuid/v5` carries its own SHA-1 instead of returning a promise or adding a
6
+ * dependency. It serves identity only, never as a security primitive.
7
+ */
8
+
9
+ const W = new Int32Array(80)
10
+
11
+ /** Size of the buffer SHA-1 needs to pad a `length`-byte message: a multiple of 64 with room for 9 more bytes. */
12
+ export function sha1PaddedLength(length: number): number {
13
+ return (Math.floor((length + 8) / 64) + 1) * 64
14
+ }
15
+
16
+ /**
17
+ * Hash `msg[0, length)` and write the first 16 digest bytes to `out[offset, offset + 16)`.
18
+ *
19
+ * `msg` is caller-owned scratch space of at least `sha1PaddedLength(length)` bytes.
20
+ * Bytes from `length` on are overwritten with padding, so stale contents are fine.
21
+ */
22
+ export function sha1Digest128Into(msg: Uint8Array, length: number, out: Uint8Array, offset: number): void {
23
+ const padded = sha1PaddedLength(length)
24
+ msg[length] = 0x80
25
+ msg.fill(0, length + 1, padded - 8)
26
+
27
+ const bitsHigh = Math.floor(length / 0x20000000)
28
+ const bitsLow = (length << 3) >>> 0
29
+ msg[padded - 8] = bitsHigh >>> 24
30
+ msg[padded - 7] = bitsHigh >>> 16
31
+ msg[padded - 6] = bitsHigh >>> 8
32
+ msg[padded - 5] = bitsHigh
33
+ msg[padded - 4] = bitsLow >>> 24
34
+ msg[padded - 3] = bitsLow >>> 16
35
+ msg[padded - 2] = bitsLow >>> 8
36
+ msg[padded - 1] = bitsLow
37
+
38
+ let h0 = 0x67452301
39
+ let h1 = 0xefcdab89 | 0
40
+ let h2 = 0x98badcfe | 0
41
+ let h3 = 0x10325476
42
+ let h4 = 0xc3d2e1f0 | 0
43
+
44
+ for (let block = 0; block < padded; block += 64) {
45
+ for (let i = 0; i < 16; i += 1) {
46
+ const j = block + i * 4
47
+ W[i] = (msg[j] << 24) | (msg[j + 1] << 16) | (msg[j + 2] << 8) | msg[j + 3]
48
+ }
49
+ for (let i = 16; i < 80; i += 1) {
50
+ const x = W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16]
51
+ W[i] = (x << 1) | (x >>> 31)
52
+ }
53
+
54
+ let a = h0
55
+ let b = h1
56
+ let c = h2
57
+ let d = h3
58
+ let e = h4
59
+
60
+ for (let i = 0; i < 80; i += 1) {
61
+ let f: number
62
+ let k: number
63
+ if (i < 20) {
64
+ f = (b & c) | (~b & d)
65
+ k = 0x5a827999
66
+ } else if (i < 40) {
67
+ f = b ^ c ^ d
68
+ k = 0x6ed9eba1
69
+ } else if (i < 60) {
70
+ f = (b & c) | (b & d) | (c & d)
71
+ k = 0x8f1bbcdc | 0
72
+ } else {
73
+ f = b ^ c ^ d
74
+ k = 0xca62c1d6 | 0
75
+ }
76
+ const t = (((a << 5) | (a >>> 27)) + f + e + k + W[i]) | 0
77
+ e = d
78
+ d = c
79
+ c = (b << 30) | (b >>> 2)
80
+ b = a
81
+ a = t
82
+ }
83
+
84
+ h0 = (h0 + a) | 0
85
+ h1 = (h1 + b) | 0
86
+ h2 = (h2 + c) | 0
87
+ h3 = (h3 + d) | 0
88
+ h4 = (h4 + e) | 0
89
+ }
90
+
91
+ writeWord(out, offset, h0)
92
+ writeWord(out, offset + 4, h1)
93
+ writeWord(out, offset + 8, h2)
94
+ writeWord(out, offset + 12, h3)
95
+ }
96
+
97
+ function writeWord(out: Uint8Array, offset: number, word: number): void {
98
+ out[offset] = word >>> 24
99
+ out[offset + 1] = word >>> 16
100
+ out[offset + 2] = word >>> 8
101
+ out[offset + 3] = word
102
+ }
@@ -1,4 +1,5 @@
1
1
  import { BufferError, ParseError } from '../../errors'
2
+ import type { IdGenerator } from '../../generators'
2
3
  import { hexValue } from './hex'
3
4
 
4
5
  const UUID_BYTE_LENGTH = 16
@@ -135,9 +136,14 @@ export function formatUuidUnchecked(bytes: Uint8Array): string {
135
136
  }
136
137
 
137
138
  export function parseUuid(value: string): Uint8Array {
139
+ return parseUuidFor(value, 'uuid')
140
+ }
141
+
142
+ /** Parse a UUID string of any version, attributing errors to `strategy`. */
143
+ export function parseUuidFor(value: string, strategy: IdGenerator): Uint8Array {
138
144
  if (value.length !== UUID_STRING_LENGTH) {
139
145
  throw new ParseError('INVALID_LENGTH', `UUID string must be 36 characters, got ${value.length}`, {
140
- strategy: 'uuid',
146
+ strategy,
141
147
  })
142
148
  }
143
149
 
@@ -146,7 +152,7 @@ export function parseUuid(value: string): Uint8Array {
146
152
  throw new ParseError(
147
153
  'INVALID_FORMAT',
148
154
  `UUID string has invalid separators at positions 8, 13, 18, 23. Received: "${value}"`,
149
- { strategy: 'uuid' },
155
+ { strategy },
150
156
  )
151
157
  }
152
158
 
@@ -161,7 +167,7 @@ export function parseUuid(value: string): Uint8Array {
161
167
  const nibble = hexValue(value.charCodeAt(i))
162
168
  if (nibble === -1) {
163
169
  throw new ParseError('INVALID_CHAR', `UUID string contains invalid hex character at position ${i}`, {
164
- strategy: 'uuid',
170
+ strategy,
165
171
  })
166
172
  }
167
173
 
package/src/uuid/v5.ts ADDED
@@ -0,0 +1,208 @@
1
+ import { isWritableRange } from '../common/validation'
2
+ import { BufferError, InvalidInputError } from '../errors'
3
+ import { sha1Digest128Into, sha1PaddedLength } from './common/sha1'
4
+ import { formatUuid, formatUuidUnchecked, parseUuid } from './common/uuid'
5
+
6
+ const UUID_BYTES = 16
7
+ // Names that fit hash in this reused buffer; larger ones get a one-off allocation.
8
+ const SCRATCH_BYTES = 512
9
+ const scratch = new Uint8Array(SCRATCH_BYTES)
10
+ const scratchName = scratch.subarray(UUID_BYTES)
11
+ const reusableBuf = new Uint8Array(UUID_BYTES)
12
+ const encoder = /*@__PURE__*/ new TextEncoder()
13
+
14
+ /**
15
+ * The input a UUID v5 is derived from: a string, hashed as UTF-8 without Unicode
16
+ * normalization, or raw bytes.
17
+ */
18
+ export type UuidV5Name = string | Uint8Array
19
+
20
+ /** A namespace UUID, as a UUID string or its 16 canonical bytes. */
21
+ export type UuidV5Namespace = string | Uint8Array
22
+
23
+ /** Derives UUID v5 values for one namespace. Returned by `uuidv5.namespace()`. */
24
+ export type UuidV5Deriver = {
25
+ /** Derive the UUID v5 string for a name. */
26
+ (name: UuidV5Name, buf?: undefined, offset?: number): string
27
+ /** Derive the UUID v5 for a name and write its 16 canonical bytes into a caller-owned buffer. */
28
+ <TBuf extends Uint8Array = Uint8Array>(name: UuidV5Name, buf: TBuf, offset?: number): TBuf
29
+ }
30
+
31
+ export type UuidV5 = {
32
+ /** Derive the UUID v5 string for a name within a namespace. */
33
+ (name: UuidV5Name, namespace: UuidV5Namespace, buf?: undefined, offset?: number): string
34
+ /** Derive the UUID v5 for a name within a namespace and write its 16 canonical bytes into a caller-owned buffer. */
35
+ <TBuf extends Uint8Array = Uint8Array>(name: UuidV5Name, namespace: UuidV5Namespace, buf: TBuf, offset?: number): TBuf
36
+ /** Validate and parse a namespace once, and return a function that derives IDs within it. */
37
+ namespace(namespace: UuidV5Namespace): UuidV5Deriver
38
+ /** Convert a UUID v5 string to its canonical 16-byte representation. */
39
+ toBytes(id: string): Uint8Array
40
+ /** Convert 16 canonical UUID bytes to a UUID v5 string. */
41
+ fromBytes(bytes: Uint8Array): string
42
+ /** Return whether a value is a syntactically valid UUID v5 string. */
43
+ isValid(id: unknown): id is string
44
+ /** The RFC 9562 namespace for fully qualified domain names. */
45
+ DNS: string
46
+ /** The RFC 9562 namespace for URLs. */
47
+ URL: string
48
+ /** The RFC 9562 namespace for ISO object identifiers (OIDs). */
49
+ OID: string
50
+ /** The RFC 9562 namespace for X.500 distinguished names. */
51
+ X500: string
52
+ /** The nil UUID (all zeros) */
53
+ NIL: string
54
+ /** The max UUID (all ones) */
55
+ MAX: string
56
+ }
57
+
58
+ const UUID_V5_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-5[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
59
+
60
+ function parseNamespace(namespace: UuidV5Namespace): Uint8Array {
61
+ if (typeof namespace === 'string') {
62
+ return parseUuid(namespace)
63
+ }
64
+
65
+ if (namespace.length !== UUID_BYTES) {
66
+ throw new InvalidInputError(
67
+ 'BYTES_INVALID_LENGTH',
68
+ `UUID v5 namespace must be exactly ${UUID_BYTES} bytes, got ${namespace.length}`,
69
+ { strategy: 'uuid' },
70
+ )
71
+ }
72
+
73
+ return namespace
74
+ }
75
+
76
+ function writeV5BytesUnchecked(namespace: Uint8Array, name: UuidV5Name, out: Uint8Array, offset: number): void {
77
+ let msg: Uint8Array
78
+ let length: number
79
+
80
+ if (typeof name === 'string') {
81
+ // UTF-8 needs at most 3 bytes per UTF-16 code unit.
82
+ const capacity = sha1PaddedLength(UUID_BYTES + name.length * 3)
83
+ msg = capacity <= SCRATCH_BYTES ? scratch : new Uint8Array(capacity)
84
+ const nameRegion = msg === scratch ? scratchName : msg.subarray(UUID_BYTES)
85
+ length = UUID_BYTES + encoder.encodeInto(name, nameRegion).written
86
+ } else {
87
+ length = UUID_BYTES + name.length
88
+ const capacity = sha1PaddedLength(length)
89
+ msg = capacity <= SCRATCH_BYTES ? scratch : new Uint8Array(capacity)
90
+ msg.set(name, UUID_BYTES)
91
+ }
92
+
93
+ // Name and namespace are copied before `out` is written, so either may alias it.
94
+ msg.set(namespace)
95
+ sha1Digest128Into(msg, length, out, offset)
96
+
97
+ // Set RFC 9562 version (5) and variant (10xx) bits.
98
+ out[offset + 6] = (out[offset + 6] & 0x0f) | 0x50
99
+ out[offset + 8] = (out[offset + 8] & 0x3f) | 0x80
100
+ }
101
+
102
+ function derive<TBuf extends Uint8Array = Uint8Array>(
103
+ namespace: Uint8Array,
104
+ name: UuidV5Name,
105
+ buf?: TBuf,
106
+ offset?: number,
107
+ ): string | TBuf {
108
+ if (!buf) {
109
+ writeV5BytesUnchecked(namespace, name, reusableBuf, 0)
110
+ return formatUuidUnchecked(reusableBuf)
111
+ }
112
+
113
+ const outputOffset = offset ?? 0
114
+ if (!isWritableRange(buf, outputOffset, UUID_BYTES)) {
115
+ throw new BufferError(
116
+ 'BUFFER_OUT_OF_BOUNDS',
117
+ `UUID byte range ${outputOffset}:${outputOffset + UUID_BYTES - 1} is out of buffer bounds`,
118
+ { strategy: 'uuid' },
119
+ )
120
+ }
121
+
122
+ writeV5BytesUnchecked(namespace, name, buf, outputOffset)
123
+ return buf
124
+ }
125
+
126
+ /*
127
+ * Overload: no buffer => return a UUID string.
128
+ */
129
+ function v5(name: UuidV5Name, namespace: UuidV5Namespace, buf?: undefined, offset?: number): string
130
+ /*
131
+ * Overload: caller provides a buffer slice to fill with UUID bytes.
132
+ */
133
+ function v5<TBuf extends Uint8Array = Uint8Array>(
134
+ name: UuidV5Name,
135
+ namespace: UuidV5Namespace,
136
+ buf: TBuf,
137
+ offset?: number,
138
+ ): TBuf
139
+ function v5<TBuf extends Uint8Array = Uint8Array>(
140
+ name: UuidV5Name,
141
+ namespace: UuidV5Namespace,
142
+ buf?: TBuf,
143
+ offset?: number,
144
+ ): string | TBuf {
145
+ return derive(parseNamespace(namespace), name, buf, offset)
146
+ }
147
+
148
+ function namespace(namespace: UuidV5Namespace): UuidV5Deriver {
149
+ // Copy caller bytes so later mutation cannot change derived IDs. `new Uint8Array()` always copies,
150
+ // unlike `.slice()` on a Node.js Buffer, which returns a view of the same memory.
151
+ const namespaceBytes =
152
+ typeof namespace === 'string' ? parseUuid(namespace) : new Uint8Array(parseNamespace(namespace))
153
+
154
+ function deriver(name: UuidV5Name, buf?: undefined, offset?: number): string
155
+ function deriver<TBuf extends Uint8Array = Uint8Array>(name: UuidV5Name, buf: TBuf, offset?: number): TBuf
156
+ function deriver<TBuf extends Uint8Array = Uint8Array>(name: UuidV5Name, buf?: TBuf, offset?: number): string | TBuf {
157
+ return derive(namespaceBytes, name, buf, offset)
158
+ }
159
+
160
+ return deriver
161
+ }
162
+
163
+ function isValid(id: unknown): id is string {
164
+ return typeof id === 'string' && UUID_V5_REGEX.test(id)
165
+ }
166
+
167
+ /**
168
+ * Derive a UUID v5 from a name and a namespace, or write its bytes into a buffer.
169
+ *
170
+ * UUID v5 (RFC 9562 section 5.5) is name-based: it hashes the namespace and
171
+ * name with SHA-1, so the same inputs give the same ID in every process, on
172
+ * every runtime, with no state to keep. Use it to derive a stable ID from an
173
+ * identifier you already hold.
174
+ *
175
+ * String names are hashed as UTF-8 without Unicode normalization. A UUID v5 is
176
+ * an identifier, not a secret: anyone who knows or can guess the name and
177
+ * namespace can compute it, and SHA-1 lets someone who controls the names craft
178
+ * two that collide.
179
+ *
180
+ * @example
181
+ * ```ts
182
+ * import { uuidv5 } from 'uniku/uuid/v5'
183
+ *
184
+ * const id = uuidv5('www.example.com', uuidv5.DNS)
185
+ * // => "2ed6657d-e927-568b-95e1-2665a8aea6a2"
186
+ *
187
+ * // Parse a namespace once and derive many IDs within it
188
+ * const sessionId = uuidv5.namespace('1b671a64-40d5-491e-99b0-da01ff1f3341')
189
+ * sessionId('tool-a:3f2c')
190
+ *
191
+ * // Validate
192
+ * uuidv5.isValid(id) // true
193
+ * ```
194
+ */
195
+ export const uuidv5: UuidV5 = Object.assign(v5, {
196
+ namespace,
197
+ toBytes: parseUuid,
198
+ fromBytes: formatUuid,
199
+ isValid,
200
+ DNS: '6ba7b810-9dad-11d1-80b4-00c04fd430c8',
201
+ URL: '6ba7b811-9dad-11d1-80b4-00c04fd430c8',
202
+ OID: '6ba7b812-9dad-11d1-80b4-00c04fd430c8',
203
+ X500: '6ba7b814-9dad-11d1-80b4-00c04fd430c8',
204
+ NIL: '00000000-0000-0000-0000-000000000000',
205
+ MAX: 'ffffffff-ffff-ffff-ffff-ffffffffffff',
206
+ })
207
+
208
+ export { BufferError, InvalidInputError, ParseError, UniqueIdError } from '../errors'
package/src/uuid/v7.ts CHANGED
@@ -19,11 +19,11 @@ export type UuidV7Options = {
19
19
  */
20
20
  counter?: number
21
21
  /**
22
- * Unsigned 32-bit sequence value.
22
+ * Deprecated alias for `counter`; unsigned 32-bit sequence value.
23
23
  *
24
- * @deprecated Use `counter` instead. Will be removed at v1-rc.
24
+ * @deprecated Use `counter` instead. Will be removed in `1.0.0-rc.1`.
25
25
  */
26
- // TODO(v1-rc): remove this alias (tracked in docs/STABILITY.md).
26
+ // TODO(v1-rc.1): remove this alias (tracked in docs/STABILITY.md).
27
27
  seq?: number
28
28
  }
29
29
 
package/src/xid/xid.ts CHANGED
@@ -26,11 +26,11 @@ export type XidOptions = {
26
26
  */
27
27
  msecs?: number
28
28
  /**
29
- * Unix timestamp in seconds.
29
+ * Deprecated alias for `msecs`; Unix timestamp in seconds.
30
30
  *
31
- * @deprecated Use `msecs` instead. Will be removed at v1-rc.
31
+ * @deprecated Use `msecs` instead. Will be removed in `1.0.0-rc.1`.
32
32
  */
33
- // TODO(v1-rc): remove this alias (tracked in docs/STABILITY.md).
33
+ // TODO(v1-rc.1): remove this alias (tracked in docs/STABILITY.md).
34
34
  secs?: number
35
35
  /** 24-bit counter. Explicit values do not consume shared state. */
36
36
  counter?: number
@@ -1,2 +0,0 @@
1
- import{BufferError as e,ParseError as t}from"./errors.mjs";function n(e){return e>=48&&e<=57?e-48:e>=65&&e<=70?e-65+10:e>=97&&e<=102?e-97+10:-1}const r=[0,0,1,1,2,2,3,3,-1,4,4,5,5,-1,6,6,7,7,-1,8,8,9,9,-1,10,10,11,11,12,12,13,13,14,14,15,15],i=[!0,!1,!0,!1,!0,!1,!0,!1,!1,!0,!1,!0,!1,!1,!0,!1,!0,!1,!1,!0,!1,!0,!1,!1,!0,!1,!0,!1,!0,!1,!0,!1,!0,!1,!0,!1],a=Array.from({length:256},(e,t)=>t.toString(16).padStart(2,`0`));function o(t){if(t.length!==16)throw new e(`BYTES_INVALID_LENGTH`,`UUID bytes must be exactly 16 bytes, got ${t.length}`,{strategy:`uuid`});return s(t)}function s(e){return a[e[0]]+a[e[1]]+a[e[2]]+a[e[3]]+`-`+a[e[4]]+a[e[5]]+`-`+a[e[6]]+a[e[7]]+`-`+a[e[8]]+a[e[9]]+`-`+a[e[10]]+a[e[11]]+a[e[12]]+a[e[13]]+a[e[14]]+a[e[15]]}function c(e){if(e.length!==36)throw new t(`INVALID_LENGTH`,`UUID string must be 36 characters, got ${e.length}`,{strategy:`uuid`});if(e[8]!==`-`||e[13]!==`-`||e[18]!==`-`||e[23]!==`-`)throw new t(`INVALID_FORMAT`,`UUID string has invalid separators at positions 8, 13, 18, 23. Received: "${e}"`,{strategy:`uuid`});let a=new Uint8Array(16);for(let o=0;o<36;o+=1){let s=r[o];if(s===-1)continue;let c=n(e.charCodeAt(o));if(c===-1)throw new t(`INVALID_CHAR`,`UUID string contains invalid hex character at position ${o}`,{strategy:`uuid`});i[o]?a[s]=c<<4:a[s]|=c}return a}export{s as n,c as r,o as t};
2
- //# sourceMappingURL=uuid-BPebYihz.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"uuid-BPebYihz.mjs","names":[],"sources":["../src/uuid/common/hex.ts","../src/uuid/common/uuid.ts"],"sourcesContent":["// ASCII code ranges for hex character validation\nconst ASCII_0 = 48 // '0'\nconst ASCII_9 = 57 // '9'\nconst ASCII_A = 65 // 'A'\nconst ASCII_F = 70 // 'F'\nconst ASCII_a = 97 // 'a'\nconst ASCII_f = 102 // 'f'\n\n/**\n * Convert a hex character ASCII code to its numeric value (0-15).\n * Returns -1 for invalid hex characters.\n */\nexport function hexValue(code: number): number {\n if (code >= ASCII_0 && code <= ASCII_9) {\n return code - ASCII_0\n }\n if (code >= ASCII_A && code <= ASCII_F) {\n return code - ASCII_A + 10\n }\n if (code >= ASCII_a && code <= ASCII_f) {\n return code - ASCII_a + 10\n }\n return -1 // Invalid hex character\n}\n","import { BufferError, ParseError } from '../../errors'\nimport { hexValue } from './hex'\n\nconst UUID_BYTE_LENGTH = 16\nconst UUID_STRING_LENGTH = 36\n\n// Mapping from UUID string position to byte index.\n// -1 indicates a dash position that should be skipped.\n// This avoids intermediate string allocations during parsing.\nconst UUID_CHAR_TO_BYTE: number[] = [\n 0,\n 0,\n 1,\n 1,\n 2,\n 2,\n 3,\n 3, // chars 0-7 → bytes 0-3\n -1, // char 8 is '-'\n 4,\n 4,\n 5,\n 5, // chars 9-12 → bytes 4-5\n -1, // char 13 is '-'\n 6,\n 6,\n 7,\n 7, // chars 14-17 → bytes 6-7\n -1, // char 18 is '-'\n 8,\n 8,\n 9,\n 9, // chars 19-22 → bytes 8-9\n -1, // char 23 is '-'\n 10,\n 10,\n 11,\n 11,\n 12,\n 12,\n 13,\n 13,\n 14,\n 14,\n 15,\n 15, // chars 24-35 → bytes 10-15\n]\n\n// Whether each position is the high nibble (true) or low nibble (false)\nconst UUID_CHAR_IS_HIGH: boolean[] = [\n true,\n false,\n true,\n false,\n true,\n false,\n true,\n false, // chars 0-7\n false, // dash (ignored)\n true,\n false,\n true,\n false, // chars 9-12\n false, // dash\n true,\n false,\n true,\n false, // chars 14-17\n false, // dash\n true,\n false,\n true,\n false, // chars 19-22\n false, // dash\n true,\n false,\n true,\n false,\n true,\n false,\n true,\n false,\n true,\n false,\n true,\n false, // chars 24-35\n]\n\n// Pre-computed lookup table for byte-to-hex conversion (0x00 -> \"00\", 0xff -> \"ff\")\n//\n// Note: this table must remain defined in the same module as `formatUuid`, otherwise the v8 optimizer\n// will cause a performance drop of ~36%.\nconst HEX_TABLE: string[] = Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, '0'))\n\nexport function formatUuid(bytes: Uint8Array): string {\n if (bytes.length !== UUID_BYTE_LENGTH) {\n throw new BufferError(\n 'BYTES_INVALID_LENGTH',\n `UUID bytes must be exactly ${UUID_BYTE_LENGTH} bytes, got ${bytes.length}`,\n {\n strategy: 'uuid',\n },\n )\n }\n\n return formatUuidUnchecked(bytes)\n}\n\nexport function formatUuidUnchecked(bytes: Uint8Array): string {\n // Direct string concatenation - optimized for V8's string builder.\n // This approach avoids loop overhead and intermediate allocations.\n // See: https://github.com/uuidjs/uuid/pull/434\n return (\n HEX_TABLE[bytes[0]] +\n HEX_TABLE[bytes[1]] +\n HEX_TABLE[bytes[2]] +\n HEX_TABLE[bytes[3]] +\n '-' +\n HEX_TABLE[bytes[4]] +\n HEX_TABLE[bytes[5]] +\n '-' +\n HEX_TABLE[bytes[6]] +\n HEX_TABLE[bytes[7]] +\n '-' +\n HEX_TABLE[bytes[8]] +\n HEX_TABLE[bytes[9]] +\n '-' +\n HEX_TABLE[bytes[10]] +\n HEX_TABLE[bytes[11]] +\n HEX_TABLE[bytes[12]] +\n HEX_TABLE[bytes[13]] +\n HEX_TABLE[bytes[14]] +\n HEX_TABLE[bytes[15]]\n )\n}\n\nexport function parseUuid(value: string): Uint8Array {\n if (value.length !== UUID_STRING_LENGTH) {\n throw new ParseError('INVALID_LENGTH', `UUID string must be 36 characters, got ${value.length}`, {\n strategy: 'uuid',\n })\n }\n\n // Validate separator positions directly (more efficient than full loop)\n if (value[8] !== '-' || value[13] !== '-' || value[18] !== '-' || value[23] !== '-') {\n throw new ParseError(\n 'INVALID_FORMAT',\n `UUID string has invalid separators at positions 8, 13, 18, 23. Received: \"${value}\"`,\n { strategy: 'uuid' },\n )\n }\n\n // Parse bytes directly from UUID string without intermediate string allocations.\n // This avoids the 9 allocations (5 slices + 4 concatenations) of the naive approach.\n const bytes = new Uint8Array(UUID_BYTE_LENGTH)\n\n for (let i = 0; i < UUID_STRING_LENGTH; i += 1) {\n const byteIdx = UUID_CHAR_TO_BYTE[i]\n if (byteIdx === -1) continue // Skip dash positions\n\n const nibble = hexValue(value.charCodeAt(i))\n if (nibble === -1) {\n throw new ParseError('INVALID_CHAR', `UUID string contains invalid hex character at position ${i}`, {\n strategy: 'uuid',\n })\n }\n\n if (UUID_CHAR_IS_HIGH[i]) {\n bytes[byteIdx] = nibble << 4\n } else {\n bytes[byteIdx] |= nibble\n }\n }\n\n return bytes\n}\n"],"mappings":"2DAYA,SAAgB,EAAS,EAAsB,CAU7C,OATI,GAAQ,IAAW,GAAQ,GACtB,EAAO,GAEZ,GAAQ,IAAW,GAAQ,GACtB,EAAO,GAAU,GAEtB,GAAQ,IAAW,GAAQ,IACtB,EAAO,GAAU,GAEnB,EACT,CCpBA,MAMM,EAA8B,CAClC,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,GACA,EACA,EACA,EACA,EACA,GACA,EACA,EACA,EACA,EACA,GACA,EACA,EACA,EACA,EACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,EACF,EAGM,EAA+B,CACnC,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,EACF,EAMM,EAAsB,MAAM,KAAK,CAAE,OAAQ,GAAI,GAAI,EAAG,IAAM,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAG,GAAG,CAAC,EAEjG,SAAgB,EAAW,EAA2B,CACpD,GAAI,EAAM,SAAW,GACnB,MAAM,IAAI,EACR,uBACA,4CAA6D,EAAM,SACnE,CACE,SAAU,MACZ,CACF,EAGF,OAAO,EAAoB,CAAK,CAClC,CAEA,SAAgB,EAAoB,EAA2B,CAI7D,OACE,EAAU,EAAM,IAChB,EAAU,EAAM,IAChB,EAAU,EAAM,IAChB,EAAU,EAAM,IAChB,IACA,EAAU,EAAM,IAChB,EAAU,EAAM,IAChB,IACA,EAAU,EAAM,IAChB,EAAU,EAAM,IAChB,IACA,EAAU,EAAM,IAChB,EAAU,EAAM,IAChB,IACA,EAAU,EAAM,KAChB,EAAU,EAAM,KAChB,EAAU,EAAM,KAChB,EAAU,EAAM,KAChB,EAAU,EAAM,KAChB,EAAU,EAAM,IAEpB,CAEA,SAAgB,EAAU,EAA2B,CACnD,GAAI,EAAM,SAAW,GACnB,MAAM,IAAI,EAAW,iBAAkB,0CAA0C,EAAM,SAAU,CAC/F,SAAU,MACZ,CAAC,EAIH,GAAI,EAAM,KAAO,KAAO,EAAM,MAAQ,KAAO,EAAM,MAAQ,KAAO,EAAM,MAAQ,IAC9E,MAAM,IAAI,EACR,iBACA,6EAA6E,EAAM,GACnF,CAAE,SAAU,MAAO,CACrB,EAKF,IAAM,EAAQ,IAAI,WAAW,EAAgB,EAE7C,IAAK,IAAI,EAAI,EAAG,EAAI,GAAoB,GAAK,EAAG,CAC9C,IAAM,EAAU,EAAkB,GAClC,GAAI,IAAY,GAAI,SAEpB,IAAM,EAAS,EAAS,EAAM,WAAW,CAAC,CAAC,EAC3C,GAAI,IAAW,GACb,MAAM,IAAI,EAAW,eAAgB,0DAA0D,IAAK,CAClG,SAAU,MACZ,CAAC,EAGC,EAAkB,GACpB,EAAM,GAAW,GAAU,EAE3B,EAAM,IAAY,CAEtB,CAEA,OAAO,CACT"}