Source

truncate.js

/**
 * Caps an arbitrary JS value down to something safe to log or hand back to
 * a caller — long strings get an ellipsis, long arrays keep only their
 * first items (with a note of how many were dropped), and anything that
 * isn't plain JSON-shaped (BigInt, an Error, a circular reference) is
 * coerced to a readable string instead of throwing.
 *
 * Unlike a flat string truncation (e.g. this file's own `truncateDebug` in
 * `ScenidCloudService.class.js`), the result stays a real object/array —
 * safe for a caller to read `.length` or a field off of, not just print.
 *
 * Used by `InsightsService`'s dry-run write mocks (see
 * `InsightsService.class.js`'s `_guardWrite`) so a script that tries to
 * mock-insert a huge batch doesn't get the whole batch echoed back.
 *
 * @module truncate
 */

const MAX_STRING_LENGTH = 2000
const MAX_ARRAY_ITEMS = 20

const truncateString = (value, maxStringLength) => (
  value.length > maxStringLength ? `${value.slice(0, maxStringLength)}…` : value
)

const truncateValue = (value, { maxStringLength = MAX_STRING_LENGTH, maxArrayItems = MAX_ARRAY_ITEMS, seen = new WeakSet() } = {}) => {
  if (value === undefined || value === null) return value
  if (typeof value === 'bigint') return value.toString()
  if (typeof value === 'string') return truncateString(value, maxStringLength)
  if (typeof value !== 'object') return value
  if (value instanceof Error) return `${value.name}: ${truncateString(value.message, maxStringLength)}`

  if (seen.has(value)) return '[circular]'
  seen.add(value)

  if (Array.isArray(value)) {
    const items = value.slice(0, maxArrayItems).map(item => truncateValue(item, { maxStringLength, maxArrayItems, seen }))
    if (value.length > maxArrayItems) items.push(`…${value.length - maxArrayItems} more`)
    return items
  }

  const result = {}
  Object.entries(value).forEach(([key, entryValue]) => {
    result[key] = truncateValue(entryValue, { maxStringLength, maxArrayItems, seen })
  })
  return result
}

export default truncateValue