uniku 0.0.13 → 0.2.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 +113 -14
- package/build/{bytes-NRtBOHK5.mjs → bytes-CbbFzz-v.mjs} +1 -1
- package/build/{bytes-NRtBOHK5.mjs.map → bytes-CbbFzz-v.mjs.map} +1 -1
- package/build/cuid2/cuid2.mjs +1 -1
- package/build/ksuid/ksuid.mjs +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/objectid/objectid.d.mts +62 -0
- package/build/objectid/objectid.d.mts.map +1 -0
- package/build/objectid/objectid.mjs +2 -0
- package/build/objectid/objectid.mjs.map +1 -0
- package/build/random-C_nRoaUe.mjs +2 -0
- package/build/{random-CWCVvNLb.mjs.map → random-C_nRoaUe.mjs.map} +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.mjs +1 -1
- package/build/uuid/v4.mjs +1 -1
- package/build/uuid/v7.mjs +1 -1
- package/build/{uuid-QysI8pWE.mjs → uuid-C9VcXh5N.mjs} +1 -1
- package/build/{uuid-QysI8pWE.mjs.map → uuid-C9VcXh5N.mjs.map} +1 -1
- package/package.json +17 -2
- package/src/nanoid/nanoid.ts +16 -7
- package/src/objectid/hex.ts +89 -0
- package/src/objectid/objectid.ts +266 -0
- package/src/typeid/typeid.ts +283 -0
- package/build/random-CWCVvNLb.mjs +0 -2
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import { InvalidInputError, ParseError } from '../errors'
|
|
2
|
+
import type { UuidV7Options } from '../uuid/v7'
|
|
3
|
+
import { uuidv7 } from '../uuid/v7'
|
|
4
|
+
|
|
5
|
+
export type TypeidOptions = UuidV7Options
|
|
6
|
+
|
|
7
|
+
export type Typeid = {
|
|
8
|
+
(prefix: string, options?: TypeidOptions): string
|
|
9
|
+
toBytes(id: string): Uint8Array
|
|
10
|
+
fromBytes(prefix: string, bytes: Uint8Array): string
|
|
11
|
+
toUuid(id: string): string
|
|
12
|
+
fromUuid(prefix: string, uuid: string): string
|
|
13
|
+
timestamp(id: string): number
|
|
14
|
+
prefix(id: string): string
|
|
15
|
+
suffix(id: string): string
|
|
16
|
+
isValid(id: unknown): id is string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const TYPEID_SUFFIX_LENGTH = 26
|
|
20
|
+
const TYPEID_UUID_BYTE_LENGTH = 16
|
|
21
|
+
const TYPEID_ALPHABET = '0123456789abcdefghjkmnpqrstvwxyz'
|
|
22
|
+
|
|
23
|
+
const BASE32_DECODE: number[] = Array.from({ length: 128 }, () => -1)
|
|
24
|
+
for (let i = 0; i < TYPEID_ALPHABET.length; i += 1) {
|
|
25
|
+
BASE32_DECODE[TYPEID_ALPHABET.charCodeAt(i)] = i
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
type ParsedTypeid = {
|
|
29
|
+
prefix: string
|
|
30
|
+
suffix: string
|
|
31
|
+
bytes: Uint8Array
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function assertValidPrefix(prefix: string): void {
|
|
35
|
+
if (prefix.length === 0) {
|
|
36
|
+
return
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (prefix.length > 63) {
|
|
40
|
+
throw new InvalidInputError('TYPEID_PREFIX_TOO_LONG', 'TypeID prefix must be at most 63 characters')
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
for (let i = 0; i < prefix.length; i += 1) {
|
|
44
|
+
const code = prefix.charCodeAt(i)
|
|
45
|
+
const isLowercaseLetter = code >= 97 && code <= 122
|
|
46
|
+
const isUnderscore = code === 95
|
|
47
|
+
|
|
48
|
+
if (!isLowercaseLetter && !isUnderscore) {
|
|
49
|
+
throw new InvalidInputError(
|
|
50
|
+
'TYPEID_PREFIX_INVALID_CHARACTER',
|
|
51
|
+
'TypeID prefix must contain only lowercase ASCII letters and underscores',
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if ((i === 0 || i === prefix.length - 1) && !isLowercaseLetter) {
|
|
56
|
+
throw new InvalidInputError('TYPEID_PREFIX_INVALID_BOUNDARY', 'TypeID prefix must start and end with a-z')
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function decodeSuffixValue(suffix: string, index: number): number {
|
|
62
|
+
const code = suffix.charCodeAt(index)
|
|
63
|
+
const value = code < BASE32_DECODE.length ? BASE32_DECODE[code] : -1
|
|
64
|
+
|
|
65
|
+
if (value === -1) {
|
|
66
|
+
throw new ParseError(
|
|
67
|
+
'TYPEID_SUFFIX_INVALID_CHARACTER',
|
|
68
|
+
`TypeID suffix contains invalid character at position ${index}`,
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return value
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function assertValidSuffixShape(suffix: string): void {
|
|
76
|
+
if (suffix.length !== TYPEID_SUFFIX_LENGTH) {
|
|
77
|
+
throw new ParseError('TYPEID_SUFFIX_INVALID_LENGTH', `TypeID suffix must be 26 characters, got ${suffix.length}`)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (suffix[0] > '7') {
|
|
81
|
+
throw new ParseError('TYPEID_SUFFIX_OVERFLOW', 'TypeID suffix must encode a 128-bit value')
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function encodeBytesToSuffix(bytes: Uint8Array): string {
|
|
86
|
+
if (bytes.length !== TYPEID_UUID_BYTE_LENGTH) {
|
|
87
|
+
throw new InvalidInputError('TYPEID_UUID_BYTES_INVALID_LENGTH', `UUID bytes must be 16 bytes, got ${bytes.length}`)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
TYPEID_ALPHABET[(bytes[0] & 0xe0) >> 5] +
|
|
92
|
+
TYPEID_ALPHABET[bytes[0] & 0x1f] +
|
|
93
|
+
TYPEID_ALPHABET[(bytes[1] & 0xf8) >> 3] +
|
|
94
|
+
TYPEID_ALPHABET[((bytes[1] & 0x07) << 2) | ((bytes[2] & 0xc0) >> 6)] +
|
|
95
|
+
TYPEID_ALPHABET[(bytes[2] & 0x3e) >> 1] +
|
|
96
|
+
TYPEID_ALPHABET[((bytes[2] & 0x01) << 4) | ((bytes[3] & 0xf0) >> 4)] +
|
|
97
|
+
TYPEID_ALPHABET[((bytes[3] & 0x0f) << 1) | ((bytes[4] & 0x80) >> 7)] +
|
|
98
|
+
TYPEID_ALPHABET[(bytes[4] & 0x7c) >> 2] +
|
|
99
|
+
TYPEID_ALPHABET[((bytes[4] & 0x03) << 3) | ((bytes[5] & 0xe0) >> 5)] +
|
|
100
|
+
TYPEID_ALPHABET[bytes[5] & 0x1f] +
|
|
101
|
+
TYPEID_ALPHABET[(bytes[6] & 0xf8) >> 3] +
|
|
102
|
+
TYPEID_ALPHABET[((bytes[6] & 0x07) << 2) | ((bytes[7] & 0xc0) >> 6)] +
|
|
103
|
+
TYPEID_ALPHABET[(bytes[7] & 0x3e) >> 1] +
|
|
104
|
+
TYPEID_ALPHABET[((bytes[7] & 0x01) << 4) | ((bytes[8] & 0xf0) >> 4)] +
|
|
105
|
+
TYPEID_ALPHABET[((bytes[8] & 0x0f) << 1) | ((bytes[9] & 0x80) >> 7)] +
|
|
106
|
+
TYPEID_ALPHABET[(bytes[9] & 0x7c) >> 2] +
|
|
107
|
+
TYPEID_ALPHABET[((bytes[9] & 0x03) << 3) | ((bytes[10] & 0xe0) >> 5)] +
|
|
108
|
+
TYPEID_ALPHABET[bytes[10] & 0x1f] +
|
|
109
|
+
TYPEID_ALPHABET[(bytes[11] & 0xf8) >> 3] +
|
|
110
|
+
TYPEID_ALPHABET[((bytes[11] & 0x07) << 2) | ((bytes[12] & 0xc0) >> 6)] +
|
|
111
|
+
TYPEID_ALPHABET[(bytes[12] & 0x3e) >> 1] +
|
|
112
|
+
TYPEID_ALPHABET[((bytes[12] & 0x01) << 4) | ((bytes[13] & 0xf0) >> 4)] +
|
|
113
|
+
TYPEID_ALPHABET[((bytes[13] & 0x0f) << 1) | ((bytes[14] & 0x80) >> 7)] +
|
|
114
|
+
TYPEID_ALPHABET[(bytes[14] & 0x7c) >> 2] +
|
|
115
|
+
TYPEID_ALPHABET[((bytes[14] & 0x03) << 3) | ((bytes[15] & 0xe0) >> 5)] +
|
|
116
|
+
TYPEID_ALPHABET[bytes[15] & 0x1f]
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function decodeSuffixToBytes(suffix: string): Uint8Array {
|
|
121
|
+
assertValidSuffixShape(suffix)
|
|
122
|
+
|
|
123
|
+
const values = new Array<number>(TYPEID_SUFFIX_LENGTH)
|
|
124
|
+
for (let i = 0; i < TYPEID_SUFFIX_LENGTH; i += 1) {
|
|
125
|
+
values[i] = decodeSuffixValue(suffix, i)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const bytes = new Uint8Array(TYPEID_UUID_BYTE_LENGTH)
|
|
129
|
+
bytes[0] = (values[0] << 5) | values[1]
|
|
130
|
+
bytes[1] = (values[2] << 3) | (values[3] >> 2)
|
|
131
|
+
bytes[2] = ((values[3] & 0x03) << 6) | (values[4] << 1) | (values[5] >> 4)
|
|
132
|
+
bytes[3] = ((values[5] & 0x0f) << 4) | (values[6] >> 1)
|
|
133
|
+
bytes[4] = ((values[6] & 0x01) << 7) | (values[7] << 2) | (values[8] >> 3)
|
|
134
|
+
bytes[5] = ((values[8] & 0x07) << 5) | values[9]
|
|
135
|
+
bytes[6] = (values[10] << 3) | (values[11] >> 2)
|
|
136
|
+
bytes[7] = ((values[11] & 0x03) << 6) | (values[12] << 1) | (values[13] >> 4)
|
|
137
|
+
bytes[8] = ((values[13] & 0x0f) << 4) | (values[14] >> 1)
|
|
138
|
+
bytes[9] = ((values[14] & 0x01) << 7) | (values[15] << 2) | (values[16] >> 3)
|
|
139
|
+
bytes[10] = ((values[16] & 0x07) << 5) | values[17]
|
|
140
|
+
bytes[11] = (values[18] << 3) | (values[19] >> 2)
|
|
141
|
+
bytes[12] = ((values[19] & 0x03) << 6) | (values[20] << 1) | (values[21] >> 4)
|
|
142
|
+
bytes[13] = ((values[21] & 0x0f) << 4) | (values[22] >> 1)
|
|
143
|
+
bytes[14] = ((values[22] & 0x01) << 7) | (values[23] << 2) | (values[24] >> 3)
|
|
144
|
+
bytes[15] = ((values[24] & 0x07) << 5) | values[25]
|
|
145
|
+
|
|
146
|
+
return bytes
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function assertUuidV7Bytes(bytes: Uint8Array): void {
|
|
150
|
+
if (bytes.length !== TYPEID_UUID_BYTE_LENGTH) {
|
|
151
|
+
throw new InvalidInputError('TYPEID_UUID_BYTES_INVALID_LENGTH', `UUID bytes must be 16 bytes, got ${bytes.length}`)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (bytes[6] >> 4 !== 7 || (bytes[8] & 0xc0) !== 0x80) {
|
|
155
|
+
throw new InvalidInputError('TYPEID_UUID_NOT_V7', 'TypeID UUID bytes must encode a UUID v7 value')
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function uuidV7BytesFromSuffix(suffix: string): Uint8Array {
|
|
160
|
+
const bytes = decodeSuffixToBytes(suffix)
|
|
161
|
+
assertUuidV7Bytes(bytes)
|
|
162
|
+
return bytes
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function uuidV7BytesFromUuid(uuid: string): Uint8Array {
|
|
166
|
+
if (!uuidv7.isValid(uuid)) {
|
|
167
|
+
throw new InvalidInputError('TYPEID_UUID_NOT_V7', 'TypeID can only wrap UUID v7 values')
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return uuidv7.toBytes(uuid)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function parseTypeid(id: string): ParsedTypeid {
|
|
174
|
+
const separatorIndex = id.lastIndexOf('_')
|
|
175
|
+
|
|
176
|
+
if (separatorIndex === 0) {
|
|
177
|
+
throw new ParseError('TYPEID_INVALID_FORMAT', 'TypeID must not start with "_"')
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const prefix = separatorIndex === -1 ? '' : id.slice(0, separatorIndex)
|
|
181
|
+
const suffix = separatorIndex === -1 ? id : id.slice(separatorIndex + 1)
|
|
182
|
+
|
|
183
|
+
assertValidPrefix(prefix)
|
|
184
|
+
const bytes = uuidV7BytesFromSuffix(suffix)
|
|
185
|
+
|
|
186
|
+
return { prefix, suffix, bytes }
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function joinPrefixAndSuffix(prefix: string, suffix: string): string {
|
|
190
|
+
return prefix === '' ? suffix : `${prefix}_${suffix}`
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function formatTypeidFromUuidV7Bytes(prefix: string, bytes: Uint8Array): string {
|
|
194
|
+
assertValidPrefix(prefix)
|
|
195
|
+
assertUuidV7Bytes(bytes)
|
|
196
|
+
return joinPrefixAndSuffix(prefix, encodeBytesToSuffix(bytes))
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function timestampFromUuidV7Bytes(bytes: Uint8Array): number {
|
|
200
|
+
let msecs = 0
|
|
201
|
+
for (let i = 0; i < 6; i += 1) {
|
|
202
|
+
msecs = msecs * 256 + bytes[i]
|
|
203
|
+
}
|
|
204
|
+
return msecs
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function typeidFn(prefix: string, options?: TypeidOptions): string {
|
|
208
|
+
const bytes = uuidv7(options, new Uint8Array(TYPEID_UUID_BYTE_LENGTH))
|
|
209
|
+
return formatTypeidFromUuidV7Bytes(prefix, bytes)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function typeidToBytes(id: string): Uint8Array {
|
|
213
|
+
return parseTypeid(id).bytes
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function typeidFromBytes(prefix: string, bytes: Uint8Array): string {
|
|
217
|
+
return formatTypeidFromUuidV7Bytes(prefix, bytes)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function typeidToUuid(id: string): string {
|
|
221
|
+
return uuidv7.fromBytes(typeidToBytes(id))
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function typeidFromUuid(prefix: string, uuid: string): string {
|
|
225
|
+
return formatTypeidFromUuidV7Bytes(prefix, uuidV7BytesFromUuid(uuid))
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function timestamp(id: string): number {
|
|
229
|
+
return timestampFromUuidV7Bytes(parseTypeid(id).bytes)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function prefix(id: string): string {
|
|
233
|
+
return parseTypeid(id).prefix
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function suffix(id: string): string {
|
|
237
|
+
return parseTypeid(id).suffix
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function isValid(id: unknown): id is string {
|
|
241
|
+
if (typeof id !== 'string') {
|
|
242
|
+
return false
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
try {
|
|
246
|
+
parseTypeid(id)
|
|
247
|
+
} catch {
|
|
248
|
+
return false
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return true
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Generate a TypeID string backed by UUID v7.
|
|
256
|
+
*
|
|
257
|
+
* TypeID combines a lowercase snake_case type prefix with a 26-character
|
|
258
|
+
* modified base32 encoding of a UUID v7, producing sortable domain identifiers
|
|
259
|
+
* such as `user_01h2xcejqtf2nbrexx3vqjhp41`.
|
|
260
|
+
*
|
|
261
|
+
* @example
|
|
262
|
+
* ```ts
|
|
263
|
+
* import { typeid } from 'uniku/typeid'
|
|
264
|
+
*
|
|
265
|
+
* const id = typeid('user')
|
|
266
|
+
* // => "user_01h2xcejqtf2nbrexx3vqjhp41"
|
|
267
|
+
*
|
|
268
|
+
* const uuid = typeid.toUuid(id)
|
|
269
|
+
* const restored = typeid.fromUuid('user', uuid)
|
|
270
|
+
* ```
|
|
271
|
+
*/
|
|
272
|
+
export const typeid: Typeid = Object.assign(typeidFn, {
|
|
273
|
+
toBytes: typeidToBytes,
|
|
274
|
+
fromBytes: typeidFromBytes,
|
|
275
|
+
toUuid: typeidToUuid,
|
|
276
|
+
fromUuid: typeidFromUuid,
|
|
277
|
+
timestamp,
|
|
278
|
+
prefix,
|
|
279
|
+
suffix,
|
|
280
|
+
isValid,
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
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(){e(t),n=0}function i(){n>252&&r();let e=t[n]*16777216+t[n+1]*65536+t[n+2]*256+t[n+3]>>>0;return n+=4,e}function a(){n>240&&r();let e=n;return n+=16,t.subarray(e,n)}export{a as n,i as t};
|
|
2
|
-
//# sourceMappingURL=random-CWCVvNLb.mjs.map
|