@@ -12,9 +12,35 @@ const KSUID_EPOCH = 1_400_000_000;
1212const KSUID_TIMESTAMP_BYTES = 4 ;
1313export const KSUID_PAYLOAD_BYTES = 16 ;
1414const KSUID_TOTAL_BYTES = KSUID_TIMESTAMP_BYTES + KSUID_PAYLOAD_BYTES ;
15- const KSUID_STRING_LENGTH = 27 ;
15+ export const KSUID_STRING_LENGTH = 27 ;
1616const BASE62_ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" ;
1717
18+ // globalThis.crypto is absent on Node 18.20 (a supported engine) without a flag, so fall back to
19+ // node:crypto's webcrypto, loaded only when the global is missing to stay isomorphic.
20+ type RandomFiller = ( array : Uint8Array ) => void ;
21+
22+ function resolveGetRandomValues ( ) : RandomFiller {
23+ const globalCrypto = ( globalThis as { crypto ?: Crypto } ) . crypto ;
24+ if ( globalCrypto ?. getRandomValues ) {
25+ return ( array ) => globalCrypto . getRandomValues ( array ) ;
26+ }
27+ const webcrypto = loadNodeWebCrypto ( ) ;
28+ if ( webcrypto ?. getRandomValues ) {
29+ return ( array ) => webcrypto . getRandomValues ( array ) ;
30+ }
31+ throw new Error ( "No Web Crypto getRandomValues implementation available" ) ;
32+ }
33+
34+ function loadNodeWebCrypto ( ) : Crypto | undefined {
35+ try {
36+ return ( typeof require === "function" ? require ( "node:crypto" ) : undefined ) ?. webcrypto ;
37+ } catch {
38+ return undefined ;
39+ }
40+ }
41+
42+ const getRandomValues : RandomFiller = resolveGetRandomValues ( ) ;
43+
1844/** Encode raw bytes as base62 (big-endian), left-padded to the given length. */
1945function base62Encode ( bytes : Uint8Array , length : number ) : string {
2046 const digits = Array . from ( bytes ) ;
@@ -67,18 +93,18 @@ export function generateKsuidId(payload?: Uint8Array): string {
6793 bytes . set ( payload , KSUID_TIMESTAMP_BYTES ) ;
6894 }
6995 if ( reserved < KSUID_PAYLOAD_BYTES ) {
70- globalThis . crypto . getRandomValues ( bytes . subarray ( KSUID_TIMESTAMP_BYTES + reserved ) ) ;
96+ getRandomValues ( bytes . subarray ( KSUID_TIMESTAMP_BYTES + reserved ) ) ;
7197 }
7298
7399 return base62Encode ( bytes , KSUID_STRING_LENGTH ) ;
74100}
75101
76102/** Decoded parts of a KSUID body: its mint timestamp and 16-byte payload. */
77- export interface DecodedKsuid {
103+ export type DecodedKsuid = {
78104 timestampSeconds : number ;
79105 timestamp : Date ;
80106 payload : Uint8Array ;
81- }
107+ } ;
82108
83109/**
84110 * Decode a KSUID body (or a `prefix_<body>` friendly id) into its timestamp + 16-byte payload.
0 commit comments