{"version":3,"file":"hash.cjs","sources":["../../../src/hashing/hash.ts"],"sourcesContent":["import { MurmurHashStream, randomHash } from './murmur.js'\nimport type { Hasher } from './murmur.js'\n\n/*\n * Implementation of structural hashing based on the Composites polyfill implementation:\n * https://github.com/tc39/proposal-composites\n */\n\nconst TRUE = randomHash()\nconst FALSE = randomHash()\nconst NULL = randomHash()\nconst UNDEFINED = randomHash()\nconst KEY = randomHash()\nconst FUNCTIONS = randomHash()\nconst DATE_MARKER = randomHash()\nconst OBJECT_MARKER = randomHash()\nconst ARRAY_MARKER = randomHash()\nconst MAP_MARKER = randomHash()\nconst SET_MARKER = randomHash()\nconst UINT8ARRAY_MARKER = randomHash()\nconst TEMPORAL_MARKER = randomHash()\n\nconst temporalTypes = new Set([\n  `Temporal.Duration`,\n  `Temporal.Instant`,\n  `Temporal.PlainDate`,\n  `Temporal.PlainDateTime`,\n  `Temporal.PlainMonthDay`,\n  `Temporal.PlainTime`,\n  `Temporal.PlainYearMonth`,\n  `Temporal.ZonedDateTime`,\n])\n\ninterface TemporalLike {\n  [Symbol.toStringTag]: string\n  toString: () => string\n}\n\nfunction isTemporal(input: object): input is TemporalLike {\n  const tag = (input as Record<symbol, unknown>)[Symbol.toStringTag]\n  return typeof tag === `string` && temporalTypes.has(tag)\n}\n\n// Maximum byte length for Uint8Arrays to hash by content instead of reference\n// Arrays smaller than this will be hashed by content, allowing proper equality comparisons\n// for small arrays like ULIDs (16 bytes) while still avoiding performance costs for large arrays\nconst UINT8ARRAY_CONTENT_HASH_THRESHOLD = 128\n\nconst hashCache = new WeakMap<object, number>()\n\nexport function hash(input: any): number {\n  const hasher = new MurmurHashStream()\n  updateHasher(hasher, input)\n  return hasher.digest()\n}\n\nfunction hashObject(input: object): number {\n  const cachedHash = hashCache.get(input)\n  if (cachedHash !== undefined) {\n    return cachedHash\n  }\n\n  let valueHash: number | undefined\n  if (input instanceof Date) {\n    valueHash = hashDate(input)\n  } else if (\n    // Check if input is a Uint8Array or Buffer\n    (typeof Buffer !== `undefined` && input instanceof Buffer) ||\n    input instanceof Uint8Array\n  ) {\n    // For small Uint8Arrays/Buffers (e.g., ULIDs, UUIDs), hash by content\n    // to enable proper equality comparisons. For large arrays, hash by reference\n    // to avoid performance costs.\n    if (input.byteLength <= UINT8ARRAY_CONTENT_HASH_THRESHOLD) {\n      valueHash = hashUint8Array(input)\n    } else {\n      // Deeply hashing large arrays would be too costly\n      // so we track them by reference and cache them in a weak map\n      return cachedReferenceHash(input)\n    }\n  } else if (input instanceof File) {\n    // Files are always hashed by reference due to their potentially large size\n    return cachedReferenceHash(input)\n  } else if (isTemporal(input)) {\n    valueHash = hashTemporal(input)\n  } else {\n    let plainObjectInput = input\n    let marker = OBJECT_MARKER\n\n    if (input instanceof Array) {\n      marker = ARRAY_MARKER\n    }\n\n    if (input instanceof Map) {\n      marker = MAP_MARKER\n      plainObjectInput = [...input.entries()]\n    }\n\n    if (input instanceof Set) {\n      marker = SET_MARKER\n      plainObjectInput = [...input.entries()]\n    }\n\n    valueHash = hashPlainObject(plainObjectInput, marker)\n  }\n\n  hashCache.set(input, valueHash)\n  return valueHash\n}\n\nfunction hashDate(input: Date): number {\n  const hasher = new MurmurHashStream()\n  hasher.update(DATE_MARKER)\n  hasher.update(input.getTime())\n  return hasher.digest()\n}\n\nfunction hashUint8Array(input: Uint8Array): number {\n  const hasher = new MurmurHashStream()\n  hasher.update(UINT8ARRAY_MARKER)\n  // Hash the byte length first to differentiate arrays of different sizes\n  hasher.update(input.byteLength)\n  // Hash each byte in the array\n  for (let i = 0; i < input.byteLength; i++) {\n    hasher.writeByte(input[i]!)\n  }\n  return hasher.digest()\n}\n\nfunction hashTemporal(input: TemporalLike): number {\n  const hasher = new MurmurHashStream()\n  hasher.update(TEMPORAL_MARKER)\n  hasher.update(input[Symbol.toStringTag])\n  hasher.update(input.toString())\n  return hasher.digest()\n}\n\nfunction hashPlainObject(input: object, marker: number): number {\n  const hasher = new MurmurHashStream()\n\n  // Mark the type of the input\n  hasher.update(marker)\n  const keys = Object.keys(input)\n  keys.sort(keySort)\n  for (const key of keys) {\n    hasher.update(KEY)\n    hasher.update(key)\n    updateHasher(hasher, input[key as keyof typeof input])\n  }\n\n  return hasher.digest()\n}\n\nfunction updateHasher(hasher: Hasher, input: unknown): void {\n  if (input === null) {\n    hasher.update(NULL)\n    return\n  }\n  switch (typeof input) {\n    case `undefined`:\n      hasher.update(UNDEFINED)\n      return\n    case `boolean`:\n      hasher.update(input ? TRUE : FALSE)\n      return\n    case `number`:\n      // Normalize NaNs and -0\n      hasher.update(isNaN(input) ? NaN : input === 0 ? 0 : input)\n      return\n    case `bigint`:\n    case `string`:\n    case `symbol`:\n      hasher.update(input)\n      return\n    case `object`:\n      hasher.update(getCachedHash(input))\n      return\n    case `function`:\n      // Functions are assigned a globally unique ID\n      // and that ID is cached in the weak map\n      hasher.update(cachedReferenceHash(input))\n      return\n    default:\n      console.warn(\n        `Ignored input during hashing because it is of type ${typeof input} which is not supported`,\n      )\n  }\n}\n\nfunction getCachedHash(input: object): number {\n  let valueHash = hashCache.get(input)\n  if (valueHash === undefined) {\n    valueHash = hashObject(input)\n  }\n  return valueHash\n}\n\nlet nextRefId = 1\nfunction cachedReferenceHash(fn: object): number {\n  let valueHash = hashCache.get(fn)\n  if (valueHash === undefined) {\n    valueHash = nextRefId ^ FUNCTIONS\n    nextRefId++\n    hashCache.set(fn, valueHash)\n  }\n  return valueHash\n}\n\n/**\n * Strings sorted lexicographically.\n */\nfunction keySort(a: string, b: string): number {\n  return a.localeCompare(b)\n}\n"],"names":["randomHash","MurmurHashStream"],"mappings":"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"}