@bubblesjs/utils

Utility function collection containing common helper methods and type utilities.

Installation

pnpm
npm
yarn
bun
pnpm add @bubblesjs/utils

Quick Usage

import { deepMergeObject, isReadableStream } from '@bubblesjs/utils'

// Deep merge objects
const merged = deepMergeObject(
  { a: 1, b: { c: 2 } },
  { b: { d: 3 }, e: 4 }
)
// Result: { a: 1, b: { c: 2, d: 3 }, e: 4 }

// Check if it's a readable stream
const isStream = isReadableStream(response.body)

API Documentation

Utility Functions

deepMergeObject(target, source)

Deep merge two objects, supporting recursive merging of nested objects.

Parameters:

  • target: object - Target object
  • source: object - Source object

Return Value:

  • object - New merged object

Example:

import { deepMergeObject } from '@bubblesjs/utils'

const target = {
  user: {
    name: 'Alice',
    settings: {
      theme: 'light'
    }
  },
  version: '1.0.0'
}

const source = {
  user: {
    age: 25,
    settings: {
      language: 'en-US'
    }
  },
  features: ['dark-mode']
}

const result = deepMergeObject(target, source)
// Result:
// {
//   user: {
//     name: 'Alice',
//     age: 25,
//     settings: {
//       theme: 'light',
//       language: 'en-US'
//     }
//   },
//   version: '1.0.0',
//   features: ['dark-mode']
// }

Notes:

  • Arrays are replaced entirely, not merged at element level
  • Special objects like functions, Date, RegExp are assigned directly
  • Source object properties override target object properties with the same name

isReadableStream(obj)

Checks if an object is a ReadableStream.

Parameters:

  • obj: any - Object to check

Return Value:

  • boolean - Returns true if it's a readable stream, false otherwise

Example:

import { isReadableStream } from '@bubblesjs/utils'

// Check fetch response body
const response = await fetch('/api/data')
if (isReadableStream(response.body)) {
  // Handle stream response
  const data = await response.json()
} else {
  // Handle regular response
  const data = response.body
}

// Check custom stream
const customStream = new ReadableStream({
  start(controller) {
    controller.enqueue('Hello')
    controller.close()
  }
})

console.log(isReadableStream(customStream)) // true
console.log(isReadableStream({})) // false
console.log(isReadableStream(null)) // false

Use Cases:

  • Distinguish different types of response bodies
  • Conditionally handle stream data
  • Adapter compatibility checks

Use Cases

1. Configuration Object Merging

import { deepMergeObject } from '@bubblesjs/utils'

// Default configuration
const defaultConfig = {
  api: {
    baseUrl: '/api',
    timeout: 5000
  },
  ui: {
    theme: 'light',
    language: 'en'
  }
}

// User configuration
const userConfig = {
  api: {
    timeout: 10000
  },
  ui: {
    theme: 'dark'
  }
}

// Merge configuration
const finalConfig = deepMergeObject(defaultConfig, userConfig)
// Result preserves all default settings while applying user overrides

2. Response Handling Adaptation

import { isReadableStream } from '@bubblesjs/utils'

const handleResponse = async (response) => {
  // Handle different response body types
  if (response?.body && isReadableStream(response.body)) {
    // Fetch API stream response
    return await response.json()
  } else {
    // Other adapter direct response
    return response.data
  }
}

3. Component Property Merging

import { deepMergeObject } from '@bubblesjs/utils'

// React component example
const Button = ({ className, style, ...props }) => {
  const defaultStyle = {
    padding: '8px 16px',
    border: 'none',
    borderRadius: '4px'
  }
  
  const mergedStyle = deepMergeObject(defaultStyle, style || {})
  
  return (
    <button 
      className={`btn ${className || ''}`}
      style={mergedStyle}
      {...props}
    />
  )
}

Type Definitions

// Deep merge function type
function deepMergeObject<T extends object, U extends object>(
  target: T,
  source: U
): T & U

// Readable stream check function type
function isReadableStream(obj: any): obj is ReadableStream

Best Practices

1. Configuration Management

import { deepMergeObject } from '@bubblesjs/utils'

class ConfigManager {
  private defaultConfig: Record<string, any>
  
  constructor(defaultConfig: Record<string, any>) {
    this.defaultConfig = defaultConfig
  }
  
  merge(userConfig: Record<string, any>) {
    return deepMergeObject(this.defaultConfig, userConfig)
  }
  
  // Support multi-layer merging
  mergeMultiple(...configs: Record<string, any>[]) {
    return configs.reduce(
      (acc, config) => deepMergeObject(acc, config),
      this.defaultConfig
    )
  }
}

2. Response Adapter

import { isReadableStream } from '@bubblesjs/utils'

class ResponseAdapter {
  static async parse(response: any) {
    // Unified response parsing logic
    if (response?.body && isReadableStream(response.body)) {
      try {
        return await response.json()
      } catch (error) {
        return await response.text()
      }
    }
    
    return response.data || response
  }
}

3. Utility Function Combination

import { deepMergeObject, isReadableStream } from '@bubblesjs/utils'

// Create a general data processing tool
const createDataProcessor = (defaultOptions = {}) => {
  return {
    process: async (data: any, options = {}) => {
      const mergedOptions = deepMergeObject(defaultOptions, options)
      
      if (isReadableStream(data)) {
        // Handle stream data
        const reader = data.getReader()
        // ... stream processing logic
      }
      
      // Handle regular data
      return processWithOptions(data, mergedOptions)
    }
  }
}

FAQ

Q: Does deepMergeObject modify the original objects?

A: No. deepMergeObject creates a new object and doesn't modify the input objects.

const original = { a: 1, b: { c: 2 } }
const source = { b: { d: 3 } }
const result = deepMergeObject(original, source)

console.log(original) // { a: 1, b: { c: 2 } } - unchanged
console.log(result)   // { a: 1, b: { c: 2, d: 3 } } - new object

Q: How to handle array merging?

A: The current implementation replaces arrays entirely. If you need element-level array merging, you can customize the handling:

const customMerge = (target, source) => {
  const result = deepMergeObject(target, source)
  
  // Custom array merging logic
  if (Array.isArray(target.items) && Array.isArray(source.items)) {
    result.items = [...target.items, ...source.items]
  }
  
  return result
}

Q: Which environments does isReadableStream support?

A: Supports ReadableStream in modern browsers and Node.js environments. For unsupported environments, it safely returns false.

Summary

@bubblesjs/utils provides practical utility functions, particularly suitable for:

  • Configuration Management: Use deepMergeObject for flexible configuration merging
  • Response Handling: Use isReadableStream to adapt different response types
  • Data Processing: Provide reliable tool support in various data manipulation scenarios

These utility functions are thoroughly tested and can be safely used in production environments.