{"version":3,"file":"cuid2.mjs","names":["buffer: SharedArrayBuffer | ArrayBuffer","pool","pool: RandomPool | undefined","state: { counter: number | undefined; fingerprint: string | undefined }","chars: string[]","cuid2: Cuid2"],"sources":["../../src/common/random-pool.ts","../../src/cuid2/cuid2.ts"],"sourcesContent":["/**\n * Thread-safe random byte pool utilities using Atomics (when SharedArrayBuffer is available).\n * Falls back to regular pooling in environments without SharedArrayBuffer.\n *\n * This module provides STATELESS utilities - each consumer creates and manages its own pool.\n * Pass the pool state to each function (C-style) for explicit, predictable behavior.\n *\n * @example\n * ```ts\n * // Each module creates its own pool\n * let pool: RandomPool | undefined\n *\n * function getBytes(count: number): number {\n *   if (!pool) pool = createPool(count)\n *   return getPooledBytes(pool, count)\n * }\n * ```\n */\n\nconst POOL_SIZE_MULTIPLIER = 128\nconst POOL_HEADER_SIZE = 4 // 4 bytes for Int32 offset counter\n\n/**\n * Pool state - passed to all pool functions.\n * Each module creates and owns its own pool instance.\n */\nexport type RandomPool = {\n  buffer: SharedArrayBuffer | ArrayBuffer\n  bytes: Uint8Array\n  offset: Int32Array\n  useAtomics: boolean\n}\n\n/**\n * Create a new random pool. Uses SharedArrayBuffer + Atomics when available\n * for thread-safety in Web Worker scenarios.\n *\n * @param minSize - Minimum bytes needed per request (pool = minSize * 128)\n */\nexport function createPool(minSize: number): RandomPool {\n  const poolSize = minSize * POOL_SIZE_MULTIPLIER\n  const totalSize = POOL_HEADER_SIZE + poolSize\n\n  let buffer: SharedArrayBuffer | ArrayBuffer\n  let useAtomics = false\n\n  // Try SharedArrayBuffer first (thread-safe with Atomics)\n  if (typeof SharedArrayBuffer !== 'undefined') {\n    try {\n      buffer = new SharedArrayBuffer(totalSize)\n      useAtomics = true\n    } catch {\n      // SharedArrayBuffer may be disabled (security restrictions)\n      buffer = new ArrayBuffer(totalSize)\n    }\n  } else {\n    buffer = new ArrayBuffer(totalSize)\n  }\n\n  // First 4 bytes = atomic offset counter, rest = random bytes\n  const offset = new Int32Array(buffer, 0, 1)\n  const bytes = new Uint8Array(buffer, POOL_HEADER_SIZE)\n  crypto.getRandomValues(bytes)\n\n  return { buffer, bytes, offset, useAtomics }\n}\n\n/**\n * Get pooled random bytes. Thread-safe when SharedArrayBuffer is available.\n * Returns the starting index in the pool for the requested bytes.\n *\n * @param pool - The pool state (from createPool)\n * @param count - Number of bytes needed\n * @returns Starting offset in pool.bytes\n *\n * @example\n * ```ts\n * const offset = getPooledBytes(pool, 21)\n * for (let i = offset; i < offset + 21; i++) {\n *   // Use pool.bytes[i]\n * }\n * ```\n */\nexport function getPooledBytes(pool: RandomPool, count: number): number {\n  if (pool.useAtomics) {\n    // Thread-safe path using Atomics\n    const startOffset = Atomics.add(pool.offset, 0, count)\n\n    if (startOffset + count <= pool.bytes.length) {\n      return startOffset\n    }\n\n    // Pool exhausted - need to refill\n    // Use compareExchange to ensure only one thread refills\n    const resetResult = Atomics.compareExchange(pool.offset, 0, startOffset + count, count)\n    if (resetResult === startOffset + count) {\n      // We won the race - refill the pool\n      crypto.getRandomValues(pool.bytes)\n      return 0\n    }\n\n    // Another thread is refilling or already refilled - retry\n    return getPooledBytes(pool, count)\n  } else {\n    // Single-threaded path\n    let currentOffset = pool.offset[0]\n\n    if (currentOffset + count > pool.bytes.length) {\n      crypto.getRandomValues(pool.bytes)\n      currentOffset = 0\n    }\n\n    pool.offset[0] = currentOffset + count\n    return currentOffset\n  }\n}\n\n/**\n * Get a Uint8Array slice of random bytes from the pool.\n *\n * Note: Returns a subarray VIEW into the pool. If you need the bytes to persist\n * beyond the current synchronous operation, use getRandomBytesCopy() instead.\n *\n * @param pool - The pool state\n * @param count - Number of bytes needed\n */\nexport function getRandomBytes(pool: RandomPool, count: number): Uint8Array {\n  const offset = getPooledBytes(pool, count)\n  return pool.bytes.subarray(offset, offset + count)\n}\n\n/**\n * Get a COPY of random bytes from the pool.\n * Use this when bytes need to persist (e.g., returned to caller who may use them later).\n *\n * @param pool - The pool state\n * @param count - Number of bytes needed\n */\nexport function getRandomBytesCopy(pool: RandomPool, count: number): Uint8Array {\n  const offset = getPooledBytes(pool, count)\n  return pool.bytes.slice(offset, offset + count)\n}\n\n/**\n * Fill the provided buffer with random bytes from the pool.\n *\n * @param pool - The pool state\n * @param out - Buffer to fill\n */\nexport function fillRandomBytes(pool: RandomPool, out: Uint8Array): void {\n  const offset = getPooledBytes(pool, out.length)\n  out.set(pool.bytes.subarray(offset, offset + out.length))\n}\n","import { sha3_512 } from '@noble/hashes/sha3.js'\nimport { createPool, getPooledBytes, getRandomBytes, type RandomPool } from '../common/random-pool'\nimport { InvalidInputError } from '../errors'\n\nexport type Cuid2Options = {\n  /**\n   * Length of the generated ID (2-32 characters).\n   * Default: 24\n   */\n  length?: number\n  /**\n   * Custom random bytes for deterministic testing.\n   * Must be at least 1 byte. For adequate entropy, use at least 16 bytes.\n   * Note: The fingerprint always uses cryptographically secure random bytes,\n   * regardless of this option.\n   */\n  random?: Uint8Array\n}\n\nexport type Cuid2 = {\n  (options?: Cuid2Options): string\n  isValid(id: unknown): id is string\n}\n\nconst DEFAULT_LENGTH = 24\nconst MAX_LENGTH = 32\nconst MIN_LENGTH = 2\n\n// Maximum initial counter value from cuid2 spec - provides ~29 bits of\n// initial entropy to prevent cross-process collisions at startup\nconst INITIAL_COUNT_MAX = 476782367\n\n// Validation regex: first char must be a-z, rest can be a-z or 0-9\nconst CUID2_REGEX = /^[a-z][0-9a-z]+$/\n\n// Base36 alphabet\nconst ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyz'\nconst LETTER_ALPHABET = 'abcdefghijklmnopqrstuvwxyz'\n\n// Reusable TextEncoder instance (stateless, safe to share)\nconst textEncoder = new TextEncoder()\n\n// CUID2's own pool - lazily initialized on first use\n// Uses 4 bytes per random() call\nlet pool: RandomPool | undefined\n\nfunction ensurePool(): RandomPool {\n  if (!pool) pool = createPool(4)\n  return pool\n}\n\n/**\n * Module-level state for counter and fingerprint.\n * Counter is initialized lazily on first call to prevent unnecessary crypto operations.\n * Fingerprint is also generated lazily on first call.\n */\nconst state: { counter: number | undefined; fingerprint: string | undefined } = {\n  counter: undefined,\n  fingerprint: undefined,\n}\n\n/**\n * Initialize counter using crypto for consistency with other entropy sources.\n */\nfunction initializeCounter(): number {\n  const buffer = getRandomBytes(ensurePool(), 4)\n  return (((buffer[0] << 24) | (buffer[1] << 16) | (buffer[2] << 8) | buffer[3]) >>> 0) % (INITIAL_COUNT_MAX + 1)\n}\n\n// --- Base36 utilities ---\n\nfunction bufToBigInt(buf: Uint8Array): bigint {\n  let value = 0n\n  for (const byte of buf) {\n    value = value * 256n + BigInt(byte)\n  }\n  return value\n}\n\nfunction bigIntToBase36(value: bigint): string {\n  if (value === 0n) return '0'\n  const chars: string[] = []\n  while (value > 0n) {\n    chars.push(ALPHABET[Number(value % 36n)])\n    value = value / 36n\n  }\n  return chars.reverse().join('')\n}\n\nfunction randomLetter(random: () => number): string {\n  return LETTER_ALPHABET[Math.floor(random() * 26)]\n}\n\nfunction createEntropy(length: number, random: () => number): string {\n  const chars = new Array<string>(length)\n  for (let i = 0; i < length; i++) {\n    chars[i] = ALPHABET[Math.floor(random() * 36)]\n  }\n  return chars.join('')\n}\n\n// --- SHA3 hash wrapper ---\n\nfunction hash(input: string): Uint8Array {\n  return sha3_512(textEncoder.encode(input))\n}\n\n// --- Fingerprint generation ---\n\nconst BIG_LENGTH = 32\n\nfunction createFingerprint(): string {\n  // Always use CSPRNG for fingerprint to ensure security regardless of custom random option\n  const random = getCryptoRandom\n  const globals = Object.keys(globalThis).toString()\n  const sourceString = globals + createEntropy(BIG_LENGTH, random)\n  const hashed = hash(sourceString)\n  return bigIntToBase36(bufToBigInt(hashed)).slice(1, BIG_LENGTH + 1)\n}\n\n// --- Random function factory ---\n\n/**\n * Get a random number in [0, 1) using CUID2's own random pool.\n * Thread-safe when SharedArrayBuffer is available.\n */\nfunction getCryptoRandom(): number {\n  const p = ensurePool()\n  const offset = getPooledBytes(p, 4)\n  const bytes = p.bytes\n  const value =\n    (bytes[offset] * 0x1000000 + bytes[offset + 1] * 0x10000 + bytes[offset + 2] * 0x100 + bytes[offset + 3]) /\n    0x100000000\n  return value\n}\n\nfunction getRandomFn(random?: Uint8Array): () => number {\n  if (random) {\n    if (random.length === 0) {\n      throw new InvalidInputError('CUID2_RANDOM_BYTES_EMPTY', 'Random byte array cannot be empty')\n    }\n    let index = 0\n    return () => {\n      const value = random[index % random.length] / 256\n      index += 1\n      return value\n    }\n  }\n  return getCryptoRandom\n}\n\n// --- Main generator ---\n\nfunction cuid2Fn(options?: Cuid2Options): string {\n  const length = options?.length ?? DEFAULT_LENGTH\n\n  if (length < MIN_LENGTH || length > MAX_LENGTH) {\n    throw new InvalidInputError(\n      'CUID2_LENGTH_OUT_OF_RANGE',\n      `CUID2 length must be between ${MIN_LENGTH} and ${MAX_LENGTH}. Received: ${length}`,\n    )\n  }\n\n  const random = getRandomFn(options?.random)\n\n  // Initialize counter lazily on first call\n  if (state.counter === undefined) {\n    state.counter = initializeCounter()\n  }\n\n  // Initialize fingerprint lazily on first call (always uses CSPRNG)\n  if (state.fingerprint === undefined) {\n    state.fingerprint = createFingerprint()\n  }\n\n  const firstLetter = randomLetter(random)\n  const time = Date.now().toString(36)\n  state.counter += 1\n  const count = state.counter.toString(36)\n  const salt = createEntropy(length, random)\n\n  const hashInput = time + salt + count + state.fingerprint\n  const hashed = hash(hashInput)\n  const base36Hash = bigIntToBase36(bufToBigInt(hashed))\n\n  // Drop first char of hash to avoid histogram bias, prepend random letter\n  return firstLetter + base36Hash.slice(1, length)\n}\n\n// --- Validation (type guard) ---\n\nfunction isValid(id: unknown): id is string {\n  return typeof id === 'string' && id.length >= MIN_LENGTH && id.length <= MAX_LENGTH && CUID2_REGEX.test(id)\n}\n\n/**\n * Generate a CUID2 string.\n *\n * CUID2 is a secure, collision-resistant identifier that hashes multiple\n * entropy sources using SHA3-512. Unlike time-ordered IDs (ULID, UUID v7),\n * CUID2 prevents enumeration attacks by making IDs non-predictable.\n *\n * Note: CUID2 does not provide toBytes/fromBytes because it is a string-native\n * format with no canonical binary representation (unlike UUID's 16-byte format).\n *\n * @example\n * ```ts\n * import { cuid2 } from 'uniku/cuid2'\n *\n * const id = cuid2()\n * // => \"pfh0haxfpzowht3oi213cqos\"\n *\n * // Custom length\n * const shortId = cuid2({ length: 10 })\n * // => \"tz4a98xxat\"\n *\n * // Validation (type guard)\n * const maybeId: unknown = getUserInput()\n * if (cuid2.isValid(maybeId)) {\n *   console.log(maybeId.length) // TypeScript knows maybeId is string\n * }\n * ```\n */\nexport const cuid2: Cuid2 = Object.assign(cuid2Fn, {\n  isValid,\n})\n\nexport { InvalidInputError, UniqueIdError } from '../errors'\n"],"mappings":"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"}