/ utils / hash.ts
hash.ts
 1  /**
 2   * djb2 string hash — fast non-cryptographic hash returning a signed 32-bit int.
 3   * Deterministic across runtimes (unlike Bun.hash which uses wyhash). Use as a
 4   * fallback when Bun.hash isn't available, or when you need on-disk-stable
 5   * output (e.g. cache directory names that must survive runtime upgrades).
 6   */
 7  export function djb2Hash(str: string): number {
 8    let hash = 0
 9    for (let i = 0; i < str.length; i++) {
10      hash = ((hash << 5) - hash + str.charCodeAt(i)) | 0
11    }
12    return hash
13  }
14  
15  /**
16   * Hash arbitrary content for change detection. Bun.hash is ~100x faster than
17   * sha256 and collision-resistant enough for diff detection (not crypto-safe).
18   */
19  export function hashContent(content: string): string {
20    if (typeof Bun !== 'undefined') {
21      return Bun.hash(content).toString()
22    }
23    // eslint-disable-next-line @typescript-eslint/no-require-imports
24    const crypto = require('crypto') as typeof import('crypto')
25    return crypto.createHash('sha256').update(content).digest('hex')
26  }
27  
28  /**
29   * Hash two strings without allocating a concatenated temp string. Bun path
30   * seed-chains wyhash (hash(a) feeds as seed to hash(b)); Node path uses
31   * incremental SHA-256 update. Seed-chaining naturally disambiguates
32   * ("ts","code") vs ("tsc","ode") so no separator is needed under Bun.
33   */
34  export function hashPair(a: string, b: string): string {
35    if (typeof Bun !== 'undefined') {
36      return Bun.hash(b, Bun.hash(a)).toString()
37    }
38    // eslint-disable-next-line @typescript-eslint/no-require-imports
39    const crypto = require('crypto') as typeof import('crypto')
40    return crypto
41      .createHash('sha256')
42      .update(a)
43      .update('\0')
44      .update(b)
45      .digest('hex')
46  }