Skip to main content

Overview

@empathyco/x-utils is a utility package providing helper functions for working with objects, types, storage, and sessions in Empathy search applications. These utilities are used throughout the Interface X ecosystem.

Installation

npm install @empathyco/x-utils

Main Exports

Object Utilities

Powerful functions for working with objects.
forEach
function
Iterate over object properties with a callback
map
function
Transform object values while preserving keys
reduce
function
Reduce object to a single value
cleanUndefined
function
Remove undefined properties from objects
cleanEmpty
function
Remove empty, null, undefined properties from objects
objectFilter
function
Filter object properties by condition
flatObject
function
Flatten nested objects to single level
rename
function
Rename object keys with prefix/suffix
deepEqual
function
Deep equality comparison for objects
getNewAndUpdatedKeys
function
Get keys that changed between two objects

Type Guards

Type checking utilities.
isObject
function
Check if value is a plain object
isArray
function
Check if value is an array

Storage Services

Browser and in-memory storage implementations.
browserStorageService
class
Browser localStorage/sessionStorage wrapper
inMemoryStorageService
class
In-memory storage implementation

Session Management

sessionService
object
Session ID generation and management

Deep Merge

deepMerge
function
Deep merge multiple objects

Path Utilities

getSafePropertyChain
function
Safely access nested object properties

Object Utilities

forEach

Iterate over non-undefined object properties:
import { forEach } from '@empathyco/x-utils'

const obj = { a: 1, b: 2, c: undefined }

forEach(obj, (key, value, index) => {
  console.log(`${key}: ${value} at index ${index}`)
})
// Output:
// a: 1 at index 0
// b: 2 at index 1
// (c is skipped because it's undefined)

map

Transform object values:
import { map } from '@empathyco/x-utils'

const numbers = { a: 1, b: 2, c: 3 }
const doubled = map(numbers, (key, value) => value * 2)
// Result: { a: 2, b: 4, c: 6 }

reduce

Reduce object to a value:
import { reduce } from '@empathyco/x-utils'

const numbers = { a: 1, b: 2, c: 3 }
const sum = reduce(numbers, (acc, key, value) => acc + value, 0)
// Result: 6

cleanUndefined

Remove undefined properties:
import { cleanUndefined } from '@empathyco/x-utils'

const obj = {
  name: 'Product',
  price: 29.99,
  description: undefined,
  category: null
}

const cleaned = cleanUndefined(obj)
// Result: { name: 'Product', price: 29.99, category: null }

cleanEmpty

Remove empty values:
import { cleanEmpty } from '@empathyco/x-utils'

const obj = {
  name: 'Product',
  description: '',
  tags: [],
  metadata: {},
  price: 0,
  available: null
}

const cleaned = cleanEmpty(obj)
// Result: { name: 'Product', price: 0 }
// Removes: empty strings, empty arrays, empty objects, null

objectFilter

Filter object properties:
import { objectFilter } from '@empathyco/x-utils'

const products = {
  apple: 1.99,
  banana: 0.99,
  cherry: 3.99
}

const expensive = objectFilter(products, (key, value) => value > 2)
// Result: { cherry: 3.99 }

flatObject

Flatten nested objects:
import { flatObject } from '@empathyco/x-utils'

const nested = {
  user: {
    name: 'John',
    address: {
      city: 'NYC'
    }
  }
}

const flat = flatObject(nested)
// Result: { name: 'John', city: 'NYC' }

rename

Rename object keys:
import { rename } from '@empathyco/x-utils'

const obj = { name: 'John', age: 30 }

const prefixed = rename(obj, { prefix: 'user_' })
// Result: { user_name: 'John', user_age: 30 }

const suffixed = rename(obj, { suffix: '_value' })
// Result: { name_value: 'John', age_value: 30 }

deepEqual

Deep equality check:
import { deepEqual } from '@empathyco/x-utils'

const obj1 = { a: 1, b: { c: 2 } }
const obj2 = { a: 1, b: { c: 2 } }
const obj3 = { a: 1, b: { c: 3 } }

deepEqual(obj1, obj2) // true
deepEqual(obj1, obj3) // false

getNewAndUpdatedKeys

Find changed keys:
import { getNewAndUpdatedKeys } from '@empathyco/x-utils'

const oldObj = { a: 1, b: 2 }
const newObj = { a: 1, b: 3, c: 4 }

const changed = getNewAndUpdatedKeys(newObj, oldObj)
// Result: ['b', 'c']
// 'a' is unchanged, 'b' is updated, 'c' is new

Type Guards

import { isObject, isArray } from '@empathyco/x-utils'

isObject({}) // true
isObject([]) // false
isObject(null) // false

isArray([]) // true
isArray({}) // false

Storage Services

Browser Storage

import { browserStorageService } from '@empathyco/x-utils'

// Use localStorage
const localStorage = browserStorageService('local')

localStorage.setItem('key', { data: 'value' })
const value = localStorage.getItem<{ data: string }>('key')
localStorage.removeItem('key')
localStorage.clear()

// Use sessionStorage
const sessionStorage = browserStorageService('session')

In-Memory Storage

import { inMemoryStorageService } from '@empathyco/x-utils'

const storage = inMemoryStorageService()

storage.setItem('key', { data: 'value' })
const value = storage.getItem('key')
storage.removeItem('key')
storage.clear()

Session Service

import { sessionService } from '@empathyco/x-utils'

// Generate a unique session ID
const sessionId = sessionService.getSessionId()

// Check if session is expired
const isExpired = sessionService.isExpired(timestamp, ttlMs)

// Refresh session timestamp
sessionService.refreshSession()

Deep Merge

import { deepMerge } from '@empathyco/x-utils'

const obj1 = {
  a: 1,
  b: { c: 2, d: 3 }
}

const obj2 = {
  b: { c: 4, e: 5 },
  f: 6
}

const merged = deepMerge(obj1, obj2)
// Result: {
//   a: 1,
//   b: { c: 4, d: 3, e: 5 },
//   f: 6
// }

Safe Property Access

import { getSafePropertyChain } from '@empathyco/x-utils'

const obj = {
  user: {
    profile: {
      name: 'John'
    }
  }
}

// Safe access to nested properties
const name = getSafePropertyChain(obj, 'user.profile.name')
// Result: 'John'

const missing = getSafePropertyChain(obj, 'user.settings.theme')
// Result: undefined (no error thrown)

Type Definitions

Dictionary

type Dictionary<T = any> = Record<string, T>

Paths

Type-safe path utilities for nested objects:
import type { Path, PathValue } from '@empathyco/x-utils'

interface User {
  name: string
  address: {
    city: string
    zip: number
  }
}

type UserPaths = Path<User>
// 'name' | 'address' | 'address.city' | 'address.zip'

type CityValue = PathValue<User, 'address.city'>
// string

Usage with X Components

import { defineComponent } from 'vue'
import { cleanEmpty, deepEqual } from '@empathyco/x-utils'

export default defineComponent({
  methods: {
    updateFilters(newFilters: Record<string, any>) {
      // Clean empty filter values
      const cleanFilters = cleanEmpty(newFilters)
      
      // Only update if changed
      if (!deepEqual(cleanFilters, this.currentFilters)) {
        this.$store.commit('setFilters', cleanFilters)
      }
    }
  }
})

Common Patterns

Request Parameter Cleaning

import { cleanEmpty, cleanUndefined } from '@empathyco/x-utils'

function prepareSearchParams(params: SearchParams) {
  return cleanEmpty({
    q: params.query,
    size: params.pageSize,
    filters: params.filters?.length ? params.filters : undefined,
    sort: params.sort
  })
}

State Comparison

import { deepEqual, getNewAndUpdatedKeys } from '@empathyco/x-utils'

function onStateChange(newState: State, oldState: State) {
  if (deepEqual(newState, oldState)) {
    return // No changes
  }
  
  const changedKeys = getNewAndUpdatedKeys(newState, oldState)
  changedKeys.forEach(key => {
    console.log(`${key} changed from`, oldState[key], 'to', newState[key])
  })
}

Object Transformation

import { map, rename } from '@empathyco/x-utils'

function normalizeAPIResponse(response: APIResponse) {
  // Transform values
  const normalized = map(response, (key, value) => {
    if (typeof value === 'string') {
      return value.toLowerCase()
    }
    return value
  })
  
  // Rename keys
  return rename(normalized, { prefix: 'api_' })
}

Resources

GitHub Repository

View source code and examples

x-components API

See how utilities are used in components

Build docs developers (and LLMs) love