uniku 0.0.12 → 0.0.13

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,184 @@
1
+ import { incrementBytesInPlace, writeTimestamp48 } from '../common/bytes'
2
+ import { rng } from '../common/random'
3
+ import { BufferError, InvalidInputError } from '../errors'
4
+ import { bytesToUlid, decodeToBytes, decodeUlidTime, encodeRandom, encodeTime } from './crockford'
5
+
6
+ export type UlidOptions = {
7
+ /**
8
+ * 16 bytes of random data to use for ULID generation.
9
+ * Only the first 10 bytes are used.
10
+ */
11
+ random?: Uint8Array
12
+ /**
13
+ * Timestamp in milliseconds since Unix epoch.
14
+ * Defaults to Date.now().
15
+ */
16
+ msecs?: number
17
+ }
18
+
19
+ export type Ulid = {
20
+ (): string
21
+ <TBuf extends Uint8Array = Uint8Array>(options: UlidOptions | undefined, buf: TBuf, offset?: number): TBuf
22
+ (options?: UlidOptions, buf?: undefined, offset?: number): string
23
+ toBytes(id: string): Uint8Array
24
+ fromBytes(bytes: Uint8Array): string
25
+ timestamp(id: string): number
26
+ isValid(id: unknown): id is string
27
+ /** The nil ULID (all zeros) */
28
+ NIL: string
29
+ /** The max ULID (maximum valid value) */
30
+ MAX: string
31
+ }
32
+
33
+ // Validation regex: first char [0-7] to prevent overflow, rest from Crockford alphabet
34
+ const ULID_REGEX = /^[0-7][0-9A-HJKMNP-TV-Z]{25}$/i
35
+
36
+ type UlidState = {
37
+ msecs: number
38
+ lastRandom: Uint8Array
39
+ }
40
+
41
+ /**
42
+ * Module-level state for maintaining monotonic ordering within the same millisecond.
43
+ *
44
+ * IMPORTANT: This state persists across all ulid() calls in the module's lifetime.
45
+ * - In serverless/edge functions with warm starts, state persists between invocations.
46
+ * - For isolated state, pass explicit `msecs` and `random` via options.
47
+ * - Tests should mock Date.now() or provide explicit options for deterministic behavior.
48
+ */
49
+ const state: UlidState = {
50
+ msecs: -Infinity,
51
+ lastRandom: new Uint8Array(10),
52
+ }
53
+
54
+ function ulidBytes(time: number, random: Uint8Array, buf?: Uint8Array, offset = 0): Uint8Array {
55
+ if (!buf) {
56
+ buf = new Uint8Array(16)
57
+ offset = 0
58
+ } else if (offset < 0 || offset + 16 > buf.length) {
59
+ throw new BufferError(
60
+ 'ULID_BUFFER_OUT_OF_BOUNDS',
61
+ `ULID byte range ${offset}:${offset + 15} is out of buffer bounds`,
62
+ )
63
+ }
64
+
65
+ // Timestamp (48-bit big-endian milliseconds since Unix epoch) -> bytes 0-5
66
+ writeTimestamp48(buf, offset, time)
67
+
68
+ // Random (80 bits) -> bytes 6-15
69
+ for (let i = 0; i < 10; i += 1) {
70
+ buf[offset + 6 + i] = random[i]
71
+ }
72
+
73
+ return buf
74
+ }
75
+
76
+ /*
77
+ * Overload: no buffer => return a ULID string.
78
+ */
79
+ function ulidFn(options?: UlidOptions, buf?: undefined, offset?: number): string
80
+ /*
81
+ * Overload: caller provides a buffer slice to fill with ULID bytes.
82
+ */
83
+ function ulidFn<TBuf extends Uint8Array = Uint8Array>(
84
+ options: UlidOptions | undefined,
85
+ buf: TBuf,
86
+ offset?: number,
87
+ ): TBuf
88
+ function ulidFn<TBuf extends Uint8Array = Uint8Array>(options?: UlidOptions, buf?: TBuf, offset = 0): string | TBuf {
89
+ let time: number
90
+ let random: Uint8Array
91
+
92
+ /**
93
+ * Note: by default, Cloudflare Workers "freezes" time during request handling to prevent
94
+ * side-channel attacks. This means that Date.now() will return the same value for the entire
95
+ * duration of a request.
96
+ * Implications:
97
+ * - all ULIDs generated within a single request will have the same timestamp.
98
+ * - the monotonic ordering will rely entirely on incrementing the random portion.
99
+ */
100
+ const defaultTime = Date.now()
101
+
102
+ if (options) {
103
+ // Explicit options provided - use them directly without monotonic state
104
+ time = options.msecs ?? defaultTime
105
+ if (options.random) {
106
+ if (options.random.length < 10) {
107
+ throw new InvalidInputError('ULID_RANDOM_BYTES_TOO_SHORT', 'Random bytes length must be >= 10 for ULID')
108
+ }
109
+ random = options.random
110
+ } else {
111
+ random = rng()
112
+ }
113
+ } else {
114
+ time = defaultTime
115
+
116
+ if (time > state.msecs) {
117
+ // New millisecond: generate fresh random
118
+ random = rng()
119
+ state.msecs = time
120
+ state.lastRandom.set(random.subarray(0, 10))
121
+ } else {
122
+ // Same millisecond or clock rollback: preserve last timestamp and increment random portion.
123
+ time = state.msecs
124
+ if (!incrementBytesInPlace(state.lastRandom)) {
125
+ state.lastRandom.fill(0xff)
126
+ throw new InvalidInputError(
127
+ 'ULID_RANDOM_OVERFLOW',
128
+ 'ULID random component overflowed while preserving monotonic order',
129
+ )
130
+ }
131
+ random = state.lastRandom
132
+ }
133
+ }
134
+
135
+ if (buf) {
136
+ ulidBytes(time, random, buf, offset)
137
+ return buf
138
+ }
139
+
140
+ // String mode: encode directly without buffer allocation
141
+ return encodeTime(time) + encodeRandom(random)
142
+ }
143
+
144
+ /**
145
+ * Generate a ULID string or write the bytes into a buffer.
146
+ *
147
+ * ULID (Universally Unique Lexicographically Sortable Identifier) is a 128-bit
148
+ * identifier with millisecond timestamp precision and 80 bits of randomness.
149
+ * ULIDs are URL-safe, use Crockford's Base32 encoding, and sort lexicographically
150
+ * by creation time.
151
+ *
152
+ * @example
153
+ * ```ts
154
+ * import { ulid } from 'uniku/ulid'
155
+ *
156
+ * const id = ulid()
157
+ * // => "01HW9T2W9W9YJ3JZ1H4P4M2T8Q"
158
+ *
159
+ * // Extract timestamp
160
+ * const ts = ulid.timestamp(id)
161
+ * console.log(new Date(ts))
162
+ *
163
+ * // Validate
164
+ * ulid.isValid(id) // true
165
+ *
166
+ * // Convert to/from bytes (16 bytes)
167
+ * const bytes = ulid.toBytes(id)
168
+ * const restored = ulid.fromBytes(bytes)
169
+ * ```
170
+ */
171
+ function isValid(id: unknown): id is string {
172
+ return typeof id === 'string' && ULID_REGEX.test(id)
173
+ }
174
+
175
+ export const ulid: Ulid = Object.assign(ulidFn, {
176
+ toBytes: (id: string) => decodeToBytes(id),
177
+ fromBytes: (bytes: Uint8Array) => bytesToUlid(bytes),
178
+ timestamp: (id: string) => decodeUlidTime(id),
179
+ isValid,
180
+ NIL: '00000000000000000000000000',
181
+ MAX: '7ZZZZZZZZZZZZZZZZZZZZZZZZZ',
182
+ })
183
+
184
+ export { BufferError, InvalidInputError, ParseError, UniqueIdError } from '../errors'
@@ -0,0 +1,24 @@
1
+ // ASCII code ranges for hex character validation
2
+ const ASCII_0 = 48 // '0'
3
+ const ASCII_9 = 57 // '9'
4
+ const ASCII_A = 65 // 'A'
5
+ const ASCII_F = 70 // 'F'
6
+ const ASCII_a = 97 // 'a'
7
+ const ASCII_f = 102 // 'f'
8
+
9
+ /**
10
+ * Convert a hex character ASCII code to its numeric value (0-15).
11
+ * Returns -1 for invalid hex characters.
12
+ */
13
+ export function hexValue(code: number): number {
14
+ if (code >= ASCII_0 && code <= ASCII_9) {
15
+ return code - ASCII_0
16
+ }
17
+ if (code >= ASCII_A && code <= ASCII_F) {
18
+ return code - ASCII_A + 10
19
+ }
20
+ if (code >= ASCII_a && code <= ASCII_f) {
21
+ return code - ASCII_a + 10
22
+ }
23
+ return -1 // Invalid hex character
24
+ }
@@ -0,0 +1,157 @@
1
+ import { ParseError } from '../../errors'
2
+ import { hexValue } from './hex'
3
+
4
+ const UUID_BYTE_LENGTH = 16
5
+ const UUID_STRING_LENGTH = 36
6
+
7
+ // Mapping from UUID string position to byte index.
8
+ // -1 indicates a dash position that should be skipped.
9
+ // This avoids intermediate string allocations during parsing.
10
+ const UUID_CHAR_TO_BYTE: number[] = [
11
+ 0,
12
+ 0,
13
+ 1,
14
+ 1,
15
+ 2,
16
+ 2,
17
+ 3,
18
+ 3, // chars 0-7 → bytes 0-3
19
+ -1, // char 8 is '-'
20
+ 4,
21
+ 4,
22
+ 5,
23
+ 5, // chars 9-12 → bytes 4-5
24
+ -1, // char 13 is '-'
25
+ 6,
26
+ 6,
27
+ 7,
28
+ 7, // chars 14-17 → bytes 6-7
29
+ -1, // char 18 is '-'
30
+ 8,
31
+ 8,
32
+ 9,
33
+ 9, // chars 19-22 → bytes 8-9
34
+ -1, // char 23 is '-'
35
+ 10,
36
+ 10,
37
+ 11,
38
+ 11,
39
+ 12,
40
+ 12,
41
+ 13,
42
+ 13,
43
+ 14,
44
+ 14,
45
+ 15,
46
+ 15, // chars 24-35 → bytes 10-15
47
+ ]
48
+
49
+ // Whether each position is the high nibble (true) or low nibble (false)
50
+ const UUID_CHAR_IS_HIGH: boolean[] = [
51
+ true,
52
+ false,
53
+ true,
54
+ false,
55
+ true,
56
+ false,
57
+ true,
58
+ false, // chars 0-7
59
+ false, // dash (ignored)
60
+ true,
61
+ false,
62
+ true,
63
+ false, // chars 9-12
64
+ false, // dash
65
+ true,
66
+ false,
67
+ true,
68
+ false, // chars 14-17
69
+ false, // dash
70
+ true,
71
+ false,
72
+ true,
73
+ false, // chars 19-22
74
+ false, // dash
75
+ true,
76
+ false,
77
+ true,
78
+ false,
79
+ true,
80
+ false,
81
+ true,
82
+ false,
83
+ true,
84
+ false,
85
+ true,
86
+ false, // chars 24-35
87
+ ]
88
+
89
+ // Pre-computed lookup table for byte-to-hex conversion (0x00 -> "00", 0xff -> "ff")
90
+ //
91
+ // Note: this table must remain defined in the same module as `formatUuid`, otherwise the v8 optimizer
92
+ // will cause a performance drop of ~36%.
93
+ const HEX_TABLE: string[] = Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, '0'))
94
+
95
+ export function formatUuid(bytes: Uint8Array): string {
96
+ // Direct string concatenation - optimized for V8's string builder.
97
+ // This approach avoids loop overhead and intermediate allocations.
98
+ // See: https://github.com/uuidjs/uuid/pull/434
99
+ return (
100
+ HEX_TABLE[bytes[0]] +
101
+ HEX_TABLE[bytes[1]] +
102
+ HEX_TABLE[bytes[2]] +
103
+ HEX_TABLE[bytes[3]] +
104
+ '-' +
105
+ HEX_TABLE[bytes[4]] +
106
+ HEX_TABLE[bytes[5]] +
107
+ '-' +
108
+ HEX_TABLE[bytes[6]] +
109
+ HEX_TABLE[bytes[7]] +
110
+ '-' +
111
+ HEX_TABLE[bytes[8]] +
112
+ HEX_TABLE[bytes[9]] +
113
+ '-' +
114
+ HEX_TABLE[bytes[10]] +
115
+ HEX_TABLE[bytes[11]] +
116
+ HEX_TABLE[bytes[12]] +
117
+ HEX_TABLE[bytes[13]] +
118
+ HEX_TABLE[bytes[14]] +
119
+ HEX_TABLE[bytes[15]]
120
+ )
121
+ }
122
+
123
+ export function parseUuid(value: string): Uint8Array {
124
+ if (value.length !== UUID_STRING_LENGTH) {
125
+ throw new ParseError('UUID_INVALID_LENGTH', `UUID string must be 36 characters, got ${value.length}`)
126
+ }
127
+
128
+ // Validate separator positions directly (more efficient than full loop)
129
+ if (value[8] !== '-' || value[13] !== '-' || value[18] !== '-' || value[23] !== '-') {
130
+ throw new ParseError(
131
+ 'UUID_INVALID_SEPARATORS',
132
+ `UUID string has invalid separators at positions 8, 13, 18, 23. Received: "${value}"`,
133
+ )
134
+ }
135
+
136
+ // Parse bytes directly from UUID string without intermediate string allocations.
137
+ // This avoids the 9 allocations (5 slices + 4 concatenations) of the naive approach.
138
+ const bytes = new Uint8Array(UUID_BYTE_LENGTH)
139
+
140
+ for (let i = 0; i < UUID_STRING_LENGTH; i += 1) {
141
+ const byteIdx = UUID_CHAR_TO_BYTE[i]
142
+ if (byteIdx === -1) continue // Skip dash positions
143
+
144
+ const nibble = hexValue(value.charCodeAt(i))
145
+ if (nibble === -1) {
146
+ throw new ParseError('UUID_INVALID_HEX_CHAR', `UUID string contains invalid hex character at position ${i}`)
147
+ }
148
+
149
+ if (UUID_CHAR_IS_HIGH[i]) {
150
+ bytes[byteIdx] = nibble << 4
151
+ } else {
152
+ bytes[byteIdx] |= nibble
153
+ }
154
+ }
155
+
156
+ return bytes
157
+ }
package/src/uuid/v4.ts ADDED
@@ -0,0 +1,119 @@
1
+ import { rng } from '../common/random'
2
+ import { BufferError, InvalidInputError } from '../errors'
3
+ import { formatUuid, parseUuid } from './common/uuid'
4
+
5
+ const randomUUID = /*@__PURE__*/ globalThis.crypto.randomUUID.bind(globalThis.crypto)
6
+
7
+ export type UuidV4Options = {
8
+ /**
9
+ * 16 bytes of random data to use for UUID generation.
10
+ * Note: Bytes at index 6 and 8 will be modified in-place to set version/variant bits.
11
+ */
12
+ random?: Uint8Array
13
+ }
14
+
15
+ export type UuidV4 = {
16
+ (): string
17
+ <TBuf extends Uint8Array = Uint8Array>(options: UuidV4Options | undefined, buf: TBuf, offset?: number): TBuf
18
+ (options?: UuidV4Options, buf?: undefined, offset?: number): string
19
+ toBytes(id: string): Uint8Array
20
+ fromBytes(bytes: Uint8Array): string
21
+ isValid(id: unknown): id is string
22
+ /** The nil UUID (all zeros) */
23
+ NIL: string
24
+ /** The max UUID (all ones) */
25
+ MAX: string
26
+ }
27
+
28
+ const UUID_V4_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
29
+
30
+ function v4Bytes(rnds: Uint8Array, buf?: Uint8Array, offset = 0): Uint8Array {
31
+ if (rnds.length < 16) {
32
+ throw new InvalidInputError('UUID_RANDOM_BYTES_TOO_SHORT', 'Random bytes length must be >= 16')
33
+ }
34
+
35
+ // Set RFC 4122 version (4) and variant (10xx) bits.
36
+ // Note: This modifies the input array in-place.
37
+ rnds[6] = (rnds[6] & 0x0f) | 0x40
38
+ rnds[8] = (rnds[8] & 0x3f) | 0x80
39
+
40
+ if (!buf) {
41
+ // No output buffer provided - return the modified random bytes directly
42
+ return rnds
43
+ }
44
+
45
+ if (offset < 0 || offset + 16 > buf.length) {
46
+ throw new BufferError(
47
+ 'UUID_BUFFER_OUT_OF_BOUNDS',
48
+ `UUID byte range ${offset}:${offset + 15} is out of buffer bounds`,
49
+ )
50
+ }
51
+
52
+ // Copy 16 UUID bytes into the provided buffer slice.
53
+ for (let i = 0; i < 16; i += 1) {
54
+ buf[offset + i] = rnds[i]
55
+ }
56
+
57
+ return buf
58
+ }
59
+
60
+ /*
61
+ * Overload: no buffer => return a UUID string.
62
+ */
63
+ function v4(options?: UuidV4Options, buf?: undefined, offset?: number): string
64
+ /*
65
+ * Overload: caller provides a buffer slice to fill with UUID bytes.
66
+ */
67
+ function v4<TBuf extends Uint8Array = Uint8Array>(options: UuidV4Options | undefined, buf: TBuf, offset?: number): TBuf
68
+ function v4<TBuf extends Uint8Array = Uint8Array>(options?: UuidV4Options, buf?: TBuf, offset?: number): string | TBuf {
69
+ if (!buf && !options) {
70
+ return randomUUID()
71
+ }
72
+
73
+ return _v4(options, buf, offset)
74
+ }
75
+
76
+ function _v4<TBuf extends Uint8Array = Uint8Array>(
77
+ options?: UuidV4Options,
78
+ buf?: TBuf,
79
+ offset?: number,
80
+ ): string | TBuf {
81
+ const bytes = v4Bytes(options?.random ?? rng(), buf, offset)
82
+ return buf ?? formatUuid(bytes)
83
+ }
84
+
85
+ function isValid(id: unknown): id is string {
86
+ return typeof id === 'string' && UUID_V4_REGEX.test(id)
87
+ }
88
+
89
+ /**
90
+ * Generate a UUID v4 string or write the bytes into a buffer.
91
+ *
92
+ * UUID v4 is a purely random UUID with 122 bits of entropy. It's the most
93
+ * widely compatible UUID format, supported by virtually all databases and systems.
94
+ * Use when you need maximum compatibility and don't require time-ordering.
95
+ *
96
+ * @example
97
+ * ```ts
98
+ * import { uuidv4 } from 'uniku/uuid/v4'
99
+ *
100
+ * const id = uuidv4()
101
+ * // => "550e8400-e29b-41d4-a716-446655440000"
102
+ *
103
+ * // Validate
104
+ * uuidv4.isValid(id) // true
105
+ *
106
+ * // Convert to/from bytes (16 bytes)
107
+ * const bytes = uuidv4.toBytes(id)
108
+ * const restored = uuidv4.fromBytes(bytes)
109
+ * ```
110
+ */
111
+ export const uuidv4: UuidV4 = Object.assign(v4, {
112
+ toBytes: parseUuid,
113
+ fromBytes: formatUuid,
114
+ isValid,
115
+ NIL: '00000000-0000-0000-0000-000000000000',
116
+ MAX: 'ffffffff-ffff-ffff-ffff-ffffffffffff',
117
+ })
118
+
119
+ export { BufferError, InvalidInputError, ParseError, UniqueIdError } from '../errors'
package/src/uuid/v7.ts ADDED
@@ -0,0 +1,182 @@
1
+ import { rng } from '../common/random'
2
+ import { BufferError, InvalidInputError } from '../errors'
3
+ import { formatUuid, parseUuid } from './common/uuid'
4
+
5
+ export type UuidV7Options = {
6
+ /**
7
+ * 16 bytes of random data to use for UUID generation.
8
+ * Note: Several bytes will be overwritten with timestamp, version, and variant data.
9
+ */
10
+ random?: Uint8Array
11
+ msecs?: number
12
+ seq?: number
13
+ }
14
+
15
+ export type UuidV7 = {
16
+ (): string
17
+ <TBuf extends Uint8Array = Uint8Array>(options: UuidV7Options | undefined, buf: TBuf, offset?: number): TBuf
18
+ (options?: UuidV7Options, buf?: undefined, offset?: number): string
19
+ toBytes(id: string): Uint8Array
20
+ fromBytes(bytes: Uint8Array): string
21
+ timestamp(id: string): number
22
+ isValid(id: unknown): id is string
23
+ /** The nil UUID (all zeros) */
24
+ NIL: string
25
+ /** The max UUID (all ones) */
26
+ MAX: string
27
+ }
28
+
29
+ const UUID_V7_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
30
+
31
+ // Reusable buffer for string output path - avoids allocation per call.
32
+ // Safe because bytes are consumed synchronously by formatUuid().
33
+ const reusableBuf = new Uint8Array(16)
34
+
35
+ type V7State = {
36
+ msecs: number
37
+ seq: number
38
+ }
39
+
40
+ /**
41
+ * Module-level state for maintaining monotonic ordering within the same millisecond.
42
+ *
43
+ * IMPORTANT: This state persists across all uuidv7() calls in the module's lifetime.
44
+ * - In serverless/edge functions with warm starts, state persists between invocations.
45
+ * - For isolated state, pass explicit `msecs` and `seq` via options.
46
+ * - Tests should mock Date.now() or provide explicit options for deterministic behavior.
47
+ */
48
+ const state: V7State = { msecs: -Infinity, seq: 0 }
49
+
50
+ function v7Bytes(
51
+ rnds: Uint8Array,
52
+ msecs: number | undefined,
53
+ seq: number | undefined,
54
+ buf: Uint8Array,
55
+ offset = 0,
56
+ ): Uint8Array {
57
+ if (rnds.length < 16) {
58
+ throw new InvalidInputError('UUID_RANDOM_BYTES_TOO_SHORT', 'Random bytes length must be >= 16')
59
+ }
60
+
61
+ if (offset < 0 || offset + 16 > buf.length) {
62
+ throw new BufferError(
63
+ 'UUID_BUFFER_OUT_OF_BOUNDS',
64
+ `UUID byte range ${offset}:${offset + 15} is out of buffer bounds`,
65
+ )
66
+ }
67
+
68
+ msecs ??= Date.now()
69
+ // Derive a 31-bit sequence if not provided by the caller.
70
+ // Uses the same formula as the hot path for consistency.
71
+ seq ??= (rnds[6] << 23) | (rnds[7] << 16) | (rnds[8] << 8) | rnds[9]
72
+
73
+ // Keep this inline on the UUID v7 hot path. Using writeTimestamp48() here
74
+ // benchmarked slower than the direct byte writes.
75
+ buf[offset++] = (msecs / 0x10000000000) & 0xff
76
+ buf[offset++] = (msecs / 0x100000000) & 0xff
77
+ buf[offset++] = (msecs / 0x1000000) & 0xff
78
+ buf[offset++] = (msecs / 0x10000) & 0xff
79
+ buf[offset++] = (msecs / 0x100) & 0xff
80
+ buf[offset++] = msecs & 0xff
81
+
82
+ // Set version (7) and variant (10xx), then pack sequence and random tail bytes.
83
+ buf[offset++] = 0x70 | ((seq >>> 28) & 0x0f)
84
+ buf[offset++] = (seq >>> 20) & 0xff
85
+ buf[offset++] = 0x80 | ((seq >>> 14) & 0x3f)
86
+ buf[offset++] = (seq >>> 6) & 0xff
87
+ // Lower seq bits plus 2 random bits to complete the 128-bit payload.
88
+ buf[offset++] = ((seq << 2) & 0xff) | (rnds[10] & 0x03)
89
+ buf[offset++] = rnds[11]
90
+ buf[offset++] = rnds[12]
91
+ buf[offset++] = rnds[13]
92
+ buf[offset++] = rnds[14]
93
+ buf[offset++] = rnds[15]
94
+
95
+ return buf
96
+ }
97
+
98
+ /*
99
+ * Overload: no buffer => return a UUID string.
100
+ */
101
+ function v7(options?: UuidV7Options, buf?: undefined, offset?: number): string
102
+ /*
103
+ * Overload: caller provides a buffer slice to fill with UUID bytes.
104
+ */
105
+ function v7<TBuf extends Uint8Array = Uint8Array>(options: UuidV7Options | undefined, buf: TBuf, offset?: number): TBuf
106
+ function v7<TBuf extends Uint8Array = Uint8Array>(options?: UuidV7Options, buf?: TBuf, offset?: number): string | TBuf {
107
+ let bytes: Uint8Array
108
+
109
+ if (options) {
110
+ bytes = v7Bytes(options.random ?? rng(), options.msecs, options.seq, buf ?? reusableBuf, buf ? offset : 0)
111
+ } else {
112
+ // HOT PATH: Inline state management and byte generation for best performance
113
+ const now = Date.now()
114
+ const rnds = rng()
115
+
116
+ // Update state (inlined for performance)
117
+ if (now > state.msecs) {
118
+ state.seq = (rnds[6] << 23) | (rnds[7] << 16) | (rnds[8] << 8) | rnds[9]
119
+ state.msecs = now
120
+ } else {
121
+ state.seq = (state.seq + 1) | 0
122
+ if (state.seq < 0) {
123
+ state.seq = 0
124
+ state.msecs++
125
+ }
126
+ }
127
+
128
+ bytes = v7Bytes(rnds, state.msecs, state.seq, buf ?? reusableBuf, buf ? offset : 0)
129
+ }
130
+
131
+ return buf ? (bytes as TBuf) : formatUuid(bytes)
132
+ }
133
+
134
+ function timestamp(id: string): number {
135
+ const bytes = parseUuid(id)
136
+ let msecs = 0
137
+ for (let i = 0; i < 6; i += 1) {
138
+ msecs = msecs * 256 + bytes[i]
139
+ }
140
+ return msecs
141
+ }
142
+
143
+ function isValid(id: unknown): id is string {
144
+ return typeof id === 'string' && UUID_V7_REGEX.test(id)
145
+ }
146
+
147
+ /**
148
+ * Generate a UUID v7 string or write the bytes into a buffer.
149
+ *
150
+ * UUID v7 is a time-ordered UUID that embeds a Unix timestamp in milliseconds,
151
+ * making IDs naturally sortable by creation time. Ideal for database primary keys
152
+ * where chronological ordering improves index performance.
153
+ *
154
+ * @example
155
+ * ```ts
156
+ * import { uuidv7 } from 'uniku/uuid/v7'
157
+ *
158
+ * const id = uuidv7()
159
+ * // => "018e5e5c-7c8a-7000-8000-000000000000"
160
+ *
161
+ * // Extract timestamp
162
+ * const ts = uuidv7.timestamp(id)
163
+ * console.log(new Date(ts))
164
+ *
165
+ * // Validate
166
+ * uuidv7.isValid(id) // true
167
+ *
168
+ * // Convert to/from bytes
169
+ * const bytes = uuidv7.toBytes(id)
170
+ * const restored = uuidv7.fromBytes(bytes)
171
+ * ```
172
+ */
173
+ export const uuidv7: UuidV7 = Object.assign(v7, {
174
+ toBytes: parseUuid,
175
+ fromBytes: formatUuid,
176
+ timestamp,
177
+ isValid,
178
+ NIL: '00000000-0000-0000-0000-000000000000',
179
+ MAX: 'ffffffff-ffff-ffff-ffff-ffffffffffff',
180
+ })
181
+
182
+ export { BufferError, InvalidInputError, ParseError, UniqueIdError } from '../errors'
@@ -1,2 +0,0 @@
1
- const e=globalThis.crypto.getRandomValues.bind(globalThis.crypto),t=new Uint8Array(256);let n=256;function r(){n>240&&(e(t),n=0);let r=n;return n+=16,t.subarray(r,n)}export{r as t};
2
- //# sourceMappingURL=random-DcKA4vF7.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"random-DcKA4vF7.mjs","names":[],"sources":["../src/common/random.ts"],"sourcesContent":["const randomFill = /*@__PURE__*/ globalThis.crypto.getRandomValues.bind(globalThis.crypto)\n\n/**\n * Simple, fast random byte pool for ID generation.\n * Uses plain JavaScript for maximum performance - no Atomics overhead.\n * Thread safety is not needed for typical ID generation use cases.\n */\n\nconst POOL_SIZE = 256\nconst rnds8Pool = new Uint8Array(POOL_SIZE)\nlet poolPtr = POOL_SIZE // Start exhausted to trigger first fill\n\n/**\n * Generate 16 bytes of cryptographically strong random data.\n * Uses a pre-filled pool to minimize crypto API calls.\n */\nexport function rng(): Uint8Array {\n if (poolPtr > POOL_SIZE - 16) {\n randomFill(rnds8Pool)\n poolPtr = 0\n }\n // Return a view - consumed synchronously within the same call\n const start = poolPtr\n poolPtr += 16\n return rnds8Pool.subarray(start, poolPtr)\n}\n"],"mappings":"AAAA,MAAM,EAA2B,WAAW,OAAO,gBAAgB,KAAK,WAAW,OAAO,CASpF,EAAY,IAAI,WAAW,IAAU,CAC3C,IAAI,EAAU,IAMd,SAAgB,GAAkB,CAC5B,EAAU,MACZ,EAAW,EAAU,CACrB,EAAU,GAGZ,IAAM,EAAQ,EAEd,MADA,IAAW,GACJ,EAAU,SAAS,EAAO,EAAQ"}