uniku 0.3.1 → 0.4.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 (48) hide show
  1. package/README.md +32 -0
  2. package/build/cuid2/cuid2.d.mts +5 -5
  3. package/build/cuid2/cuid2.d.mts.map +1 -1
  4. package/build/cuid2/cuid2.mjs.map +1 -1
  5. package/build/generators.d.mts +1 -1
  6. package/build/generators.d.mts.map +1 -1
  7. package/build/generators.mjs +1 -1
  8. package/build/generators.mjs.map +1 -1
  9. package/build/ksuid/ksuid.d.mts +6 -6
  10. package/build/ksuid/ksuid.d.mts.map +1 -1
  11. package/build/ksuid/ksuid.mjs.map +1 -1
  12. package/build/nanoid/nanoid.d.mts +2 -2
  13. package/build/nanoid/nanoid.mjs.map +1 -1
  14. package/build/objectid/objectid.d.mts +6 -6
  15. package/build/objectid/objectid.d.mts.map +1 -1
  16. package/build/objectid/objectid.mjs.map +1 -1
  17. package/build/tsid/tsid.d.mts +8 -8
  18. package/build/tsid/tsid.d.mts.map +1 -1
  19. package/build/tsid/tsid.mjs.map +1 -1
  20. package/build/typeid/typeid.d.mts +8 -8
  21. package/build/typeid/typeid.d.mts.map +1 -1
  22. package/build/typeid/typeid.mjs.map +1 -1
  23. package/build/ulid/ulid.d.mts +10 -6
  24. package/build/ulid/ulid.d.mts.map +1 -1
  25. package/build/ulid/ulid.mjs.map +1 -1
  26. package/build/uuid/v4.d.mts +5 -5
  27. package/build/uuid/v4.d.mts.map +1 -1
  28. package/build/uuid/v4.mjs.map +1 -1
  29. package/build/uuid/v7.d.mts +10 -6
  30. package/build/uuid/v7.d.mts.map +1 -1
  31. package/build/uuid/v7.mjs.map +1 -1
  32. package/build/xid/xid.d.mts +31 -0
  33. package/build/xid/xid.d.mts.map +1 -0
  34. package/build/xid/xid.mjs +2 -0
  35. package/build/xid/xid.mjs.map +1 -0
  36. package/package.json +11 -3
  37. package/src/cuid2/cuid2.ts +6 -4
  38. package/src/generators.ts +1 -1
  39. package/src/ksuid/ksuid.ts +7 -0
  40. package/src/nanoid/nanoid.ts +3 -3
  41. package/src/objectid/objectid.ts +7 -0
  42. package/src/tsid/tsid.ts +9 -0
  43. package/src/typeid/typeid.ts +9 -0
  44. package/src/ulid/ulid.ts +11 -0
  45. package/src/uuid/v4.ts +6 -0
  46. package/src/uuid/v7.ts +11 -0
  47. package/src/xid/base32hex.ts +79 -0
  48. package/src/xid/xid.ts +158 -0
package/src/tsid/tsid.ts CHANGED
@@ -62,14 +62,23 @@ export type TsidOptions = {
62
62
  }
63
63
 
64
64
  export type Tsid = {
65
+ /** Generate a time-sorted TSID bigint. */
65
66
  (): bigint
67
+ /** Generate a TSID with explicit options or write its 8 canonical bytes into a caller-owned buffer. */
66
68
  <TBuf extends Uint8Array = Uint8Array>(options: TsidOptions | undefined, buf: TBuf, offset?: number): TBuf
69
+ /** Generate a TSID bigint with optional timestamp, epoch, node, or counter controls. */
67
70
  (options?: TsidOptions, buf?: undefined, offset?: number): bigint
71
+ /** Convert a TSID bigint to its canonical 8-byte representation. */
68
72
  toBytes(id: bigint): Uint8Array
73
+ /** Convert 8 canonical TSID bytes to a TSID bigint. */
69
74
  fromBytes(bytes: Uint8Array): bigint
75
+ /** Convert a TSID bigint to its canonical 13-character string. */
70
76
  toString(id: bigint): string
77
+ /** Convert a canonical TSID string to a bigint. */
71
78
  fromString(str: string): bigint
79
+ /** Read a TSID timestamp in milliseconds, using the supplied or default epoch. */
72
80
  timestamp(id: bigint, epoch?: number): number
81
+ /** Return whether a value is a valid 64-bit TSID bigint. */
73
82
  isValid(id: unknown): id is bigint
74
83
  /** The nil TSID (all zeros) */
75
84
  NIL: bigint
@@ -5,14 +5,23 @@ import { uuidv7 } from '../uuid/v7'
5
5
  export type TypeidOptions = UuidV7Options
6
6
 
7
7
  export type Typeid = {
8
+ /** Generate a TypeID from a lowercase entity prefix and a UUID v7 suffix. */
8
9
  (prefix: string, options?: TypeidOptions): string
10
+ /** Convert a TypeID's UUID v7 suffix to its canonical 16-byte representation. */
9
11
  toBytes(id: string): Uint8Array
12
+ /** Build a TypeID from a prefix and canonical UUID v7 bytes. */
10
13
  fromBytes(prefix: string, bytes: Uint8Array): string
14
+ /** Convert a TypeID to its UUID v7 string. */
11
15
  toUuid(id: string): string
16
+ /** Build a TypeID from a prefix and UUID v7 string. */
12
17
  fromUuid(prefix: string, uuid: string): string
18
+ /** Read the UUID v7 timestamp embedded in a TypeID, in milliseconds. */
13
19
  timestamp(id: string): number
20
+ /** Read a TypeID's entity prefix. */
14
21
  prefix(id: string): string
22
+ /** Read a TypeID's UUID v7 suffix. */
15
23
  suffix(id: string): string
24
+ /** Return whether a value is a syntactically valid TypeID. */
16
25
  isValid(id: unknown): id is string
17
26
  }
18
27
 
package/src/ulid/ulid.ts CHANGED
@@ -18,12 +18,19 @@ export type UlidOptions = {
18
18
  }
19
19
 
20
20
  export type Ulid = {
21
+ /** Generate a time-ordered ULID string. */
21
22
  (): string
23
+ /** Generate a ULID with explicit options or write its 16 canonical bytes into a caller-owned buffer. */
22
24
  <TBuf extends Uint8Array = Uint8Array>(options: UlidOptions | undefined, buf: TBuf, offset?: number): TBuf
25
+ /** Generate a ULID string with optional timestamp or random bytes. */
23
26
  (options?: UlidOptions, buf?: undefined, offset?: number): string
27
+ /** Convert a ULID string to its canonical 16-byte representation. */
24
28
  toBytes(id: string): Uint8Array
29
+ /** Convert 16 canonical ULID bytes to a ULID string. */
25
30
  fromBytes(bytes: Uint8Array): string
31
+ /** Read the embedded Unix timestamp in milliseconds. */
26
32
  timestamp(id: string): number
33
+ /** Return whether a value is a syntactically valid ULID string. */
27
34
  isValid(id: unknown): id is string
28
35
  /** The nil ULID (all zeros) */
29
36
  NIL: string
@@ -183,6 +190,10 @@ function isValid(id: unknown): id is string {
183
190
  return typeof id === 'string' && ULID_REGEX.test(id)
184
191
  }
185
192
 
193
+ /**
194
+ * Generate a ULID string or write its 16 canonical bytes into a buffer.
195
+ * ULIDs are URL-safe, carry a millisecond timestamp, and sort by creation time.
196
+ */
186
197
  export const ulid: Ulid = Object.assign(ulidFn, {
187
198
  toBytes: (id: string) => decodeToBytes(id),
188
199
  fromBytes: (bytes: Uint8Array) => bytesToUlid(bytes),
package/src/uuid/v4.ts CHANGED
@@ -15,11 +15,17 @@ export type UuidV4Options = {
15
15
  }
16
16
 
17
17
  export type UuidV4 = {
18
+ /** Generate a random UUID v4 string. */
18
19
  (): string
20
+ /** Generate a UUID v4 with explicit options or write its 16 canonical bytes into a caller-owned buffer. */
19
21
  <TBuf extends Uint8Array = Uint8Array>(options: UuidV4Options | undefined, buf: TBuf, offset?: number): TBuf
22
+ /** Generate a UUID v4 string with optional deterministic random bytes. */
20
23
  (options?: UuidV4Options, buf?: undefined, offset?: number): string
24
+ /** Convert a UUID v4 string to its canonical 16-byte representation. */
21
25
  toBytes(id: string): Uint8Array
26
+ /** Convert 16 canonical UUID bytes to a UUID v4 string. */
22
27
  fromBytes(bytes: Uint8Array): string
28
+ /** Return whether a value is a syntactically valid UUID v4 string. */
23
29
  isValid(id: unknown): id is string
24
30
  /** The nil UUID (all zeros) */
25
31
  NIL: string
package/src/uuid/v7.ts CHANGED
@@ -9,6 +9,10 @@ export type UuidV7Options = {
9
9
  * Note: Several bytes will be overwritten with timestamp, version, and variant data.
10
10
  */
11
11
  random?: Uint8Array
12
+ /**
13
+ * Timestamp in milliseconds since Unix epoch.
14
+ * Defaults to Date.now().
15
+ */
12
16
  msecs?: number
13
17
  /**
14
18
  * Unsigned 32-bit sequence value.
@@ -17,12 +21,19 @@ export type UuidV7Options = {
17
21
  }
18
22
 
19
23
  export type UuidV7 = {
24
+ /** Generate a time-ordered UUID v7 string. */
20
25
  (): string
26
+ /** Generate a UUID v7 with explicit options or write its 16 canonical bytes into a caller-owned buffer. */
21
27
  <TBuf extends Uint8Array = Uint8Array>(options: UuidV7Options | undefined, buf: TBuf, offset?: number): TBuf
28
+ /** Generate a UUID v7 string with optional timestamp, sequence, or random bytes. */
22
29
  (options?: UuidV7Options, buf?: undefined, offset?: number): string
30
+ /** Convert a UUID v7 string to its canonical 16-byte representation. */
23
31
  toBytes(id: string): Uint8Array
32
+ /** Convert 16 canonical UUID bytes to a UUID v7 string. */
24
33
  fromBytes(bytes: Uint8Array): string
34
+ /** Read the embedded Unix timestamp in milliseconds. */
25
35
  timestamp(id: string): number
36
+ /** Return whether a value is a syntactically valid UUID v7 string. */
26
37
  isValid(id: unknown): id is string
27
38
  /** The nil UUID (all zeros) */
28
39
  NIL: string
@@ -0,0 +1,79 @@
1
+ import { BufferError, ParseError } from '../errors'
2
+
3
+ const ENCODING = '0123456789abcdefghijklmnopqrstuv'
4
+ const XID_BYTES = 12
5
+ const XID_LENGTH = 20
6
+
7
+ const DECODING = new Uint8Array(65536)
8
+ DECODING.fill(255)
9
+ for (let i = 0; i < ENCODING.length; i += 1) {
10
+ DECODING[ENCODING.charCodeAt(i)] = i
11
+ }
12
+
13
+ /** Encode the 12 canonical XID bytes as lowercase base32hex. */
14
+ export function encodeBase32Hex(bytes: Uint8Array): string {
15
+ if (bytes.length !== XID_BYTES) {
16
+ throw new BufferError(
17
+ 'XID_BYTES_INVALID_LENGTH',
18
+ `XID bytes must be exactly ${XID_BYTES} bytes, got ${bytes.length}`,
19
+ )
20
+ }
21
+
22
+ return (
23
+ ENCODING[bytes[0] >> 3] +
24
+ ENCODING[((bytes[0] << 2) | (bytes[1] >> 6)) & 0x1f] +
25
+ ENCODING[(bytes[1] >> 1) & 0x1f] +
26
+ ENCODING[((bytes[1] << 4) | (bytes[2] >> 4)) & 0x1f] +
27
+ ENCODING[((bytes[2] << 1) | (bytes[3] >> 7)) & 0x1f] +
28
+ ENCODING[(bytes[3] >> 2) & 0x1f] +
29
+ ENCODING[((bytes[3] << 3) | (bytes[4] >> 5)) & 0x1f] +
30
+ ENCODING[bytes[4] & 0x1f] +
31
+ ENCODING[bytes[5] >> 3] +
32
+ ENCODING[((bytes[5] << 2) | (bytes[6] >> 6)) & 0x1f] +
33
+ ENCODING[(bytes[6] >> 1) & 0x1f] +
34
+ ENCODING[((bytes[6] << 4) | (bytes[7] >> 4)) & 0x1f] +
35
+ ENCODING[((bytes[7] << 1) | (bytes[8] >> 7)) & 0x1f] +
36
+ ENCODING[(bytes[8] >> 2) & 0x1f] +
37
+ ENCODING[((bytes[8] << 3) | (bytes[9] >> 5)) & 0x1f] +
38
+ ENCODING[bytes[9] & 0x1f] +
39
+ ENCODING[bytes[10] >> 3] +
40
+ ENCODING[((bytes[10] << 2) | (bytes[11] >> 6)) & 0x1f] +
41
+ ENCODING[(bytes[11] >> 1) & 0x1f] +
42
+ ENCODING[(bytes[11] << 4) & 0x1f]
43
+ )
44
+ }
45
+
46
+ /** Decode a canonical lowercase base32hex XID string to its 12 bytes. */
47
+ export function decodeBase32Hex(id: string): Uint8Array {
48
+ if (id.length !== XID_LENGTH) {
49
+ throw new ParseError('XID_INVALID_LENGTH', `XID string must be ${XID_LENGTH} characters, got ${id.length}`)
50
+ }
51
+
52
+ const values = new Uint8Array(XID_LENGTH)
53
+ for (let i = 0; i < XID_LENGTH; i += 1) {
54
+ const value = DECODING[id.charCodeAt(i)]
55
+ if (value === 255) {
56
+ throw new ParseError('XID_INVALID_CHAR', `Invalid XID character: ${id[i]}`)
57
+ }
58
+ values[i] = value
59
+ }
60
+
61
+ if (values[19] !== 0 && values[19] !== 16) {
62
+ throw new ParseError('XID_NON_CANONICAL', 'XID trailing bits must be canonically encoded')
63
+ }
64
+
65
+ return new Uint8Array([
66
+ (values[0] << 3) | (values[1] >> 2),
67
+ (values[1] << 6) | (values[2] << 1) | (values[3] >> 4),
68
+ (values[3] << 4) | (values[4] >> 1),
69
+ (values[4] << 7) | (values[5] << 2) | (values[6] >> 3),
70
+ (values[6] << 5) | values[7],
71
+ (values[8] << 3) | (values[9] >> 2),
72
+ (values[9] << 6) | (values[10] << 1) | (values[11] >> 4),
73
+ (values[11] << 4) | (values[12] >> 1),
74
+ (values[12] << 7) | (values[13] << 2) | (values[14] >> 3),
75
+ (values[14] << 5) | values[15],
76
+ (values[16] << 3) | (values[17] >> 2),
77
+ (values[17] << 6) | (values[18] << 1) | (values[19] >> 4),
78
+ ])
79
+ }
package/src/xid/xid.ts ADDED
@@ -0,0 +1,158 @@
1
+ import { writeTimestamp32 } from '../common/bytes'
2
+ import { randomBytes, randomUint32 } from '../common/random'
3
+ import { isIntegerInRange, isWritableRange } from '../common/validation'
4
+ import { BufferError, InvalidInputError } from '../errors'
5
+ import { decodeBase32Hex, encodeBase32Hex } from './base32hex'
6
+
7
+ const XID_BYTES = 12
8
+ const MACHINE_ID_BYTES = 3
9
+ const MAX_SECS = 0xffffffff
10
+ const MAX_PROCESS_ID = 0xffff
11
+ const MAX_COUNTER = 0xffffff
12
+ const XID_REGEX = /^[0-9a-v]{19}[0g]$/
13
+
14
+ export type XidOptions = {
15
+ /** First three bytes used as the XID machine identity. */
16
+ machineId?: Uint8Array
17
+ /** 16-bit process identity. */
18
+ processId?: number
19
+ /** Unix timestamp in seconds. Defaults to the current second. */
20
+ secs?: number
21
+ /** 24-bit counter. Explicit values do not consume shared state. */
22
+ counter?: number
23
+ }
24
+
25
+ export type Xid = {
26
+ (): string
27
+ <TBuf extends Uint8Array = Uint8Array>(options: XidOptions | undefined, buf: TBuf, offset?: number): TBuf
28
+ (options?: XidOptions, buf?: undefined, offset?: number): string
29
+ toBytes(id: string): Uint8Array
30
+ fromBytes(bytes: Uint8Array): string
31
+ timestamp(id: string): number
32
+ isValid(id: unknown): id is string
33
+ NIL: string
34
+ MAX: string
35
+ }
36
+
37
+ type XidState = {
38
+ machineId: Uint8Array | undefined
39
+ processId: number | undefined
40
+ counter: number | undefined
41
+ }
42
+
43
+ const state: XidState = { machineId: undefined, processId: undefined, counter: undefined }
44
+
45
+ /** Copy pooled bytes before another random draw can refill their backing pool. */
46
+ function freshRandom(count: number): Uint8Array {
47
+ return randomBytes(count).slice()
48
+ }
49
+
50
+ function initializeIdentity(): void {
51
+ if (state.machineId === undefined) state.machineId = freshRandom(MACHINE_ID_BYTES)
52
+ if (state.processId === undefined) state.processId = randomUint32() & MAX_PROCESS_ID
53
+ }
54
+
55
+ function nextCounter(): number {
56
+ if (state.counter === undefined) state.counter = randomUint32() & MAX_COUNTER
57
+ state.counter = (state.counter + 1) & MAX_COUNTER
58
+ return state.counter
59
+ }
60
+
61
+ function writeXidBytesUnchecked(
62
+ secs: number,
63
+ machineId: Uint8Array,
64
+ processId: number,
65
+ counter: number,
66
+ buf: Uint8Array,
67
+ offset: number,
68
+ ): void {
69
+ writeTimestamp32(buf, offset, secs)
70
+ buf.set(machineId.subarray(0, MACHINE_ID_BYTES), offset + 4)
71
+ buf[offset + 7] = processId >>> 8
72
+ buf[offset + 8] = processId & 0xff
73
+ buf[offset + 9] = counter >>> 16
74
+ buf[offset + 10] = (counter >>> 8) & 0xff
75
+ buf[offset + 11] = counter & 0xff
76
+ }
77
+
78
+ function validateOptions(options: XidOptions): void {
79
+ if (options.machineId !== undefined && options.machineId.length < MACHINE_ID_BYTES) {
80
+ throw new InvalidInputError(
81
+ 'XID_MACHINE_ID_BYTES_TOO_SHORT',
82
+ `Machine ID bytes length must be >= ${MACHINE_ID_BYTES} for XID`,
83
+ )
84
+ }
85
+ if (options.processId !== undefined && !isIntegerInRange(options.processId, 0, MAX_PROCESS_ID)) {
86
+ throw new InvalidInputError('XID_PROCESS_ID_OUT_OF_RANGE', `Process ID must be between 0 and ${MAX_PROCESS_ID}`)
87
+ }
88
+ if (options.secs !== undefined && !isIntegerInRange(options.secs, 0, MAX_SECS)) {
89
+ throw new InvalidInputError('XID_TIMESTAMP_OUT_OF_RANGE', `Timestamp must be between 0 and ${MAX_SECS}`)
90
+ }
91
+ if (options.counter !== undefined && !isIntegerInRange(options.counter, 0, MAX_COUNTER)) {
92
+ throw new InvalidInputError('XID_COUNTER_OUT_OF_RANGE', `Counter must be between 0 and ${MAX_COUNTER}`)
93
+ }
94
+ }
95
+
96
+ function xidFn(options?: XidOptions, buf?: undefined, offset?: number): string
97
+ function xidFn<TBuf extends Uint8Array = Uint8Array>(options: XidOptions | undefined, buf: TBuf, offset?: number): TBuf
98
+ function xidFn<TBuf extends Uint8Array = Uint8Array>(options?: XidOptions, buf?: TBuf, offset = 0): string | TBuf {
99
+ if (options !== undefined) validateOptions(options)
100
+
101
+ const secs = options?.secs ?? Math.floor(Date.now() / 1000)
102
+ if (options?.machineId === undefined || options.processId === undefined) {
103
+ initializeIdentity()
104
+ }
105
+ const machineId = options?.machineId ?? state.machineId!
106
+ const processId = options?.processId ?? state.processId!
107
+ const counter = options?.counter ?? nextCounter()
108
+
109
+ if (buf !== undefined) {
110
+ if (!isWritableRange(buf, offset, XID_BYTES)) {
111
+ throw new BufferError(
112
+ 'XID_BUFFER_OUT_OF_BOUNDS',
113
+ `XID byte range ${offset}:${offset + 11} is out of buffer bounds`,
114
+ )
115
+ }
116
+ writeXidBytesUnchecked(secs, machineId, processId, counter, buf, offset)
117
+ return buf
118
+ }
119
+
120
+ const bytes = new Uint8Array(XID_BYTES)
121
+ writeXidBytesUnchecked(secs, machineId, processId, counter, bytes, 0)
122
+ return encodeBase32Hex(bytes)
123
+ }
124
+
125
+ function toBytes(id: string): Uint8Array {
126
+ return decodeBase32Hex(id)
127
+ }
128
+
129
+ function fromBytes(bytes: Uint8Array): string {
130
+ return encodeBase32Hex(bytes)
131
+ }
132
+
133
+ function timestamp(id: string): number {
134
+ const bytes = decodeBase32Hex(id)
135
+ return (((bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]) >>> 0) * 1000
136
+ }
137
+
138
+ function isValid(id: unknown): id is string {
139
+ return typeof id === 'string' && XID_REGEX.test(id)
140
+ }
141
+
142
+ /**
143
+ * Generate a 20-character rs/xid-compatible identifier.
144
+ *
145
+ * XID embeds seconds, a lazily-random per-runtime identity, and a shared
146
+ * always-incrementing counter. In Cloudflare Workers, time is frozen during a
147
+ * request, so the counter preserves ordering for IDs made in that request.
148
+ */
149
+ export const xid: Xid = Object.assign(xidFn, {
150
+ toBytes,
151
+ fromBytes,
152
+ timestamp,
153
+ isValid,
154
+ NIL: '0'.repeat(20),
155
+ MAX: encodeBase32Hex(new Uint8Array(12).fill(0xff)),
156
+ })
157
+
158
+ export { BufferError, InvalidInputError, ParseError, UniqueIdError } from '../errors'