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.
Files changed (45) hide show
  1. package/README.md +63 -13
  2. package/build/{bytes-BBjKLOBR.mjs → bytes-DzcOKIx1.mjs} +1 -1
  3. package/build/{bytes-BBjKLOBR.mjs.map → bytes-DzcOKIx1.mjs.map} +1 -1
  4. package/build/cuid2/cuid2.d.mts.map +1 -1
  5. package/build/cuid2/cuid2.mjs +1 -1
  6. package/build/cuid2/cuid2.mjs.map +1 -1
  7. package/build/ksuid/ksuid.d.mts.map +1 -1
  8. package/build/ksuid/ksuid.mjs +1 -1
  9. package/build/ksuid/ksuid.mjs.map +1 -1
  10. package/build/nanoid/nanoid.d.mts.map +1 -1
  11. package/build/nanoid/nanoid.mjs +1 -1
  12. package/build/nanoid/nanoid.mjs.map +1 -1
  13. package/build/random-CGMm8owG.mjs +2 -0
  14. package/build/random-CGMm8owG.mjs.map +1 -0
  15. package/build/tsconfig.tsbuildinfo +1 -1
  16. package/build/typeid/typeid.d.mts +38 -0
  17. package/build/typeid/typeid.d.mts.map +1 -0
  18. package/build/typeid/typeid.mjs +2 -0
  19. package/build/typeid/typeid.mjs.map +1 -0
  20. package/build/ulid/ulid.d.mts.map +1 -1
  21. package/build/ulid/ulid.mjs +1 -1
  22. package/build/ulid/ulid.mjs.map +1 -1
  23. package/build/uuid/v4.mjs +1 -1
  24. package/build/uuid/v7.d.mts.map +1 -1
  25. package/build/uuid/v7.mjs +1 -1
  26. package/build/uuid/v7.mjs.map +1 -1
  27. package/build/{uuid-QysI8pWE.mjs → uuid-Ub6ladEr.mjs} +1 -1
  28. package/build/{uuid-QysI8pWE.mjs.map → uuid-Ub6ladEr.mjs.map} +1 -1
  29. package/package.json +21 -31
  30. package/src/common/bytes.ts +59 -0
  31. package/src/common/random.ts +66 -0
  32. package/src/cuid2/cuid2.ts +211 -0
  33. package/src/errors.ts +56 -0
  34. package/src/ksuid/base62.ts +103 -0
  35. package/src/ksuid/ksuid.ts +195 -0
  36. package/src/nanoid/nanoid.ts +257 -0
  37. package/src/typeid/typeid.ts +283 -0
  38. package/src/ulid/crockford.ts +249 -0
  39. package/src/ulid/ulid.ts +184 -0
  40. package/src/uuid/common/hex.ts +24 -0
  41. package/src/uuid/common/uuid.ts +157 -0
  42. package/src/uuid/v4.ts +119 -0
  43. package/src/uuid/v7.ts +182 -0
  44. package/build/random-DcKA4vF7.mjs +0 -2
  45. 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,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"}