uniku 0.0.12 → 0.1.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.
- package/README.md +63 -13
- package/build/{bytes-BBjKLOBR.mjs → bytes-DzcOKIx1.mjs} +1 -1
- package/build/{bytes-BBjKLOBR.mjs.map → bytes-DzcOKIx1.mjs.map} +1 -1
- package/build/cuid2/cuid2.d.mts.map +1 -1
- package/build/cuid2/cuid2.mjs +1 -1
- package/build/cuid2/cuid2.mjs.map +1 -1
- package/build/ksuid/ksuid.d.mts.map +1 -1
- package/build/ksuid/ksuid.mjs +1 -1
- package/build/ksuid/ksuid.mjs.map +1 -1
- package/build/nanoid/nanoid.d.mts.map +1 -1
- package/build/nanoid/nanoid.mjs +1 -1
- package/build/nanoid/nanoid.mjs.map +1 -1
- package/build/random-CGMm8owG.mjs +2 -0
- package/build/random-CGMm8owG.mjs.map +1 -0
- package/build/tsconfig.tsbuildinfo +1 -1
- package/build/typeid/typeid.d.mts +38 -0
- package/build/typeid/typeid.d.mts.map +1 -0
- package/build/typeid/typeid.mjs +2 -0
- package/build/typeid/typeid.mjs.map +1 -0
- package/build/ulid/ulid.d.mts.map +1 -1
- package/build/ulid/ulid.mjs +1 -1
- package/build/ulid/ulid.mjs.map +1 -1
- package/build/uuid/v4.mjs +1 -1
- package/build/uuid/v7.d.mts.map +1 -1
- package/build/uuid/v7.mjs +1 -1
- package/build/uuid/v7.mjs.map +1 -1
- package/build/{uuid-QysI8pWE.mjs → uuid-Ub6ladEr.mjs} +1 -1
- package/build/{uuid-QysI8pWE.mjs.map → uuid-Ub6ladEr.mjs.map} +1 -1
- package/package.json +21 -31
- package/src/common/bytes.ts +59 -0
- package/src/common/random.ts +66 -0
- package/src/cuid2/cuid2.ts +211 -0
- package/src/errors.ts +56 -0
- package/src/ksuid/base62.ts +103 -0
- package/src/ksuid/ksuid.ts +195 -0
- package/src/nanoid/nanoid.ts +257 -0
- package/src/typeid/typeid.ts +283 -0
- package/src/ulid/crockford.ts +249 -0
- package/src/ulid/ulid.ts +184 -0
- package/src/uuid/common/hex.ts +24 -0
- package/src/uuid/common/uuid.ts +157 -0
- package/src/uuid/v4.ts +119 -0
- package/src/uuid/v7.ts +182 -0
- package/build/random-DcKA4vF7.mjs +0 -2
- package/build/random-DcKA4vF7.mjs.map +0 -1
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 +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"}
|