Skip to main content

Overview

The Core Package provides a comprehensive set of utility functions for common operations including data transformation, validation, formatting, and logging.

Core Utilities

Import

import {
  clamp,
  indonesianPhoneNumberFormat,
  toCamelCase,
  toSnakeCase,
  removeLeadingZeros,
  removeLeadingWhitespace,
  objectToFormData,
  objectToFormDataArrayWithComma,
  deepReadObject
} from '@workspace/core/utils/core';

clamp

Clamps a numeric value to a specified range.
options.value
number
required
The value to clamp
options.min
number
required
The minimum allowed value
options.max
number
required
The maximum allowed value
result
number
The clamped value
clamp({ value: 12, min: 0, max: 10 }); // 10
clamp({ value: -5, min: 0, max: 10 }); // 0
clamp({ value: 5, min: 0, max: 10 });  // 5

// Use for input validation
const opacity = clamp({ value: userInput, min: 0, max: 1 });
const volume = clamp({ value: slider.value, min: 0, max: 100 });

indonesianPhoneNumberFormat

Formats Indonesian phone numbers with dashes for better readability.
phoneNumber
string
required
Phone number including “+62” prefix (11-15 characters total)
result
string
Formatted phone number with dashes (e.g., “+62-812-7363-6365”)
indonesianPhoneNumberFormat('+628127363636');   // '+62-812-736-3636'
indonesianPhoneNumberFormat('+6281273636365');  // '+62-812-7363-6365'
indonesianPhoneNumberFormat('+62812736363654'); // '+62-812-73636-3654'

// Use in display components
const displayPhone = indonesianPhoneNumberFormat(user.phoneNumber);

toCamelCase

Converts deep nested object keys from snake_case or kebab-case to camelCase.
object
unknown
required
Object, array, or primitive to convert
result
T
Transformed object with camelCase keys
const snakeCase = {
  user_name: 'John',
  user_email: '[email protected]',
  user_settings: {
    email_notifications: true,
    push_notifications: false
  },
  user_tags: ['admin', 'verified']
};

const camelCase = toCamelCase(snakeCase);
// {
//   userName: 'John',
//   userEmail: '[email protected]',
//   userSettings: {
//     emailNotifications: true,
//     pushNotifications: false
//   },
//   userTags: ['admin', 'verified']
// }

// Use with API responses
const response = await fetch('/api/users');
const data = await response.json();
const users = toCamelCase<User[]>(data);

toSnakeCase

Converts deep nested object keys from camelCase to snake_case.
object
unknown
required
Object, array, or primitive to convert
result
T
Transformed object with snake_case keys
const camelCase = {
  userName: 'John',
  userEmail: '[email protected]',
  userSettings: {
    emailNotifications: true,
    pushNotifications: false
  }
};

const snakeCase = toSnakeCase(camelCase);
// {
//   user_name: 'John',
//   user_email: '[email protected]',
//   user_settings: {
//     email_notifications: true,
//     push_notifications: false
//   }
// }

// Use with API requests
const payload = toSnakeCase(formData);
await fetch('/api/users', {
  method: 'POST',
  body: JSON.stringify(payload)
});

removeLeadingZeros

Removes leading zeros from a string while preserving valid values.
value
string
required
String potentially containing leading zeros
result
string
String with leading zeros removed
removeLeadingZeros('0123');   // '123'
removeLeadingZeros('0000');   // '0'
removeLeadingZeros('00001');  // '1'
removeLeadingZeros('123');    // '123'
removeLeadingZeros('0');      // '0'

// Use in input handlers
function handleInput(e: ChangeEvent<HTMLInputElement>) {
  const cleaned = removeLeadingZeros(e.target.value);
  setValue(cleaned);
}

removeLeadingWhitespace

Removes leading whitespace from a string.
value
string | undefined
String potentially containing leading whitespace
result
string
String with leading whitespace removed, or empty string if input is undefined
removeLeadingWhitespace('  hello');    // 'hello'
removeLeadingWhitespace('\thello');    // 'hello'
removeLeadingWhitespace('   ');        // ''
removeLeadingWhitespace(undefined);    // ''
removeLeadingWhitespace('hello');      // 'hello'

// Use in form validation
const trimmed = removeLeadingWhitespace(input.value);
if (trimmed.length === 0) {
  setError('Field cannot be empty');
}

objectToFormData

Converts a deep nested object to FormData with indexed array notation.
obj
T extends UnknownRecord
required
Object to convert to FormData
options.rootName
string
Root name prefix for all form data keys
options.ignoreList
Array<keyof T>
Array of keys to exclude from the FormData
result
FormData
FormData instance containing the object data
const data = {
  num: 1,
  falseBool: false,
  trueBool: true,
  empty: '',
  name: 'John',
  file: new File(['content'], 'doc.txt', { type: 'text/plain' }),
  nested: {
    key: 'value',
    deep: { prop: 'data' }
  },
  items: [
    { id: 1, name: 'Item 1' },
    { id: 2, name: 'Item 2' }
  ]
};

const formData = objectToFormData(data);

// Results in FormData entries:
// ['num', '1']
// ['falseBool', 'false']
// ['trueBool', 'true']
// ['empty', '']
// ['name', 'John']
// ['file', File]
// ['nested.key', 'value']
// ['nested.deep.prop', 'data']
// ['items[0].id', '1']
// ['items[0].name', 'Item 1']
// ['items[1].id', '2']
// ['items[1].name', 'Item 2']

// Send with fetch or HTTP client
await fetch('/api/upload', {
  method: 'POST',
  body: formData
});

// With ignore list
const formData = objectToFormData(data, {
  ignoreList: ['password', 'token']
});

objectToFormDataArrayWithComma

Converts a deep nested object to FormData with comma-separated array values.
obj
T extends UnknownRecord
required
Object to convert to FormData
options.rootName
string
Root name prefix for all form data keys
options.ignoreList
Array<keyof T>
Array of keys to exclude from the FormData
result
FormData
FormData instance with arrays as comma-separated strings
const data = {
  name: 'John',
  tags: ['javascript', 'typescript', 'react'],
  categories: ['frontend', 'backend'],
  nested: {
    items: ['a', 'b', 'c']
  }
};

const formData = objectToFormDataArrayWithComma(data);

// Results in FormData entries:
// ['name', 'John']
// ['tags', 'javascript,typescript,react']
// ['categories', 'frontend,backend']
// ['nested.items', 'a,b,c']

// Useful for APIs that expect comma-separated values
await fetch('/api/filter', {
  method: 'POST',
  body: formData
});

deepReadObject

Safely access deeply nested values in an object using a dot-notation path string.
obj
Record<string, unknown>
required
The object to read from
path
string
required
Dot-separated path to the property (e.g., “user.address.city”)
defaultValue
unknown
Default value to return if path doesn’t exist
result
T
The value at the path, or defaultValue if not found
const obj = {
  user: {
    name: 'John',
    address: {
      city: 'Jakarta',
      country: 'Indonesia'
    },
    tags: ['admin', 'verified']
  }
};

// Access nested properties
deepReadObject(obj, 'user.name');              // 'John'
deepReadObject(obj, 'user.address.city');      // 'Jakarta'
deepReadObject(obj, 'user.tags');              // ['admin', 'verified']

// Non-existent paths
deepReadObject(obj, 'user.phone');             // undefined
deepReadObject(obj, 'user.address.zip');       // undefined

// With default value
deepReadObject(obj, 'user.phone', 'N/A');      // 'N/A'
deepReadObject(obj, 'user.age', 0);            // 0

// Type-safe with generics
const city = deepReadObject<string>(obj, 'user.address.city');
const tags = deepReadObject<string[]>(obj, 'user.tags', []);

// Use in configuration access
function getConfig<T>(path: string, defaultValue: T): T {
  return deepReadObject<T>(appConfig, path, defaultValue);
}

const apiUrl = getConfig('api.baseUrl', 'https://api.example.com');
const timeout = getConfig('api.timeout', 30000);

Date Utilities

Import

import {
  isValidTimezoneIANAString,
  getLocalTimeZone
} from '@workspace/core/utils/date';

isValidTimezoneIANAString

Validates if a string is a valid IANA timezone identifier. Results are cached for performance.
timeZoneString
string
required
Timezone string to validate (e.g., “Asia/Jakarta”)
result
boolean
True if the timezone is valid, false otherwise
isValidTimezoneIANAString('Asia/Jakarta');        // true
isValidTimezoneIANAString('America/New_York');    // true
isValidTimezoneIANAString('Europe/London');       // true
isValidTimezoneIANAString('Invalid/Timezone');    // false
isValidTimezoneIANAString('GMT+7');               // false

// Use in timezone selection
function setUserTimezone(timezone: string) {
  if (isValidTimezoneIANAString(timezone)) {
    localStorage.setItem('timezone', timezone);
  } else {
    console.error('Invalid timezone:', timezone);
  }
}

// Validate user input
function handleTimezoneInput(value: string) {
  if (isValidTimezoneIANAString(value)) {
    setTimezone(value);
  } else {
    setError('Please enter a valid timezone');
  }
}

getLocalTimeZone

Gets the local timezone in IANA format. Provides a workaround for Chrome 118 issues with @internationalized/date.
result
string
The local timezone identifier (e.g., “Asia/Jakarta”)
const timezone = getLocalTimeZone();
console.log(timezone); // 'Asia/Jakarta' (or user's local timezone)

// Use with date libraries
import { ZonedDateTime } from '@internationalized/date';

const now = new ZonedDateTime(
  Date.now(),
  getLocalTimeZone()
);

// Display local time
function formatLocalTime(date: Date): string {
  return date.toLocaleString('en-US', {
    timeZone: getLocalTimeZone()
  });
}

// Convert to user's timezone
function toUserTimezone(utcDate: string): Date {
  const date = new Date(utcDate);
  const timezone = getLocalTimeZone();
  // ... conversion logic
  return date;
}

Invariant Utility

Import

import { invariant } from '@workspace/core/utils/invariant';

invariant

Asserts that a condition is truthy. Throws an error if the condition is falsy. Messages are stripped in production builds to reduce bundle size.
condition
any
required
The condition to assert is truthy
message
string | (() => string)
Error message or function that returns a message (only in development)
const value: Person | null = getPerson();
invariant(value, 'Expected value to be a person');
// Type of `value` is now narrowed to `Person`

// Basic usage
invariant(userId, 'User ID is required');
invariant(response.ok, 'Request failed');

// With type narrowing
function processUser(user: User | null) {
  invariant(user, 'User must be defined');
  // user is now typed as User (not null)
  console.log(user.name);
}

// Lazy message evaluation
invariant(
  config.apiKey,
  () => `API key is required. Available keys: ${Object.keys(config).join(', ')}`
);

// Guard against null/undefined
function divide(a: number, b: number): number {
  invariant(b !== 0, 'Cannot divide by zero');
  return a / b;
}

// Array bounds checking
function getItem<T>(array: T[], index: number): T {
  invariant(index >= 0 && index < array.length, 'Index out of bounds');
  return array[index];
}

// Configuration validation
function initializeApp(config: AppConfig) {
  invariant(config.apiUrl, 'API URL must be configured');
  invariant(config.apiKey, 'API key must be configured');
  // ... proceed with initialization
}

Logger Utility

Import

import { logger } from '@workspace/core/utils/logger';
Colorized console logger with timestamp formatting.

logger.debug

Logs debug-level messages in green.
logger.debug('User action', { userId: 123, action: 'click' });
// [14:23:45.123] DEBUG: User action { userId: 123, action: 'click' }

logger.debug('API request', {
  method: 'GET',
  url: '/api/users',
  params: { page: 1 }
});

logger.log

Logs info-level messages in blue.
logger.log('Application started');
// [14:23:45.123] INFO: Application started

logger.log('User logged in', { userId: 123, timestamp: Date.now() });

logger.warn

Logs warning messages in yellow.
logger.warn('Deprecated API usage', {
  api: 'getUserData',
  alternative: 'fetchUser'
});
// [14:23:45.123] WARN: Deprecated API usage { ... }

logger.warn('Rate limit approaching', {
  current: 95,
  limit: 100
});

logger.error

Logs error messages in red.
logger.error('Failed to fetch data', error);
// [14:23:45.123] ERROR: Failed to fetch data Error: ...

try {
  await riskyOperation();
} catch (error) {
  logger.error('Operation failed', {
    error,
    context: 'user-profile',
    userId: 123
  });
}

Complete Example

import { logger } from '@workspace/core/utils/logger';

class UserService {
  async fetchUser(id: string) {
    logger.debug('Fetching user', { id });
    
    try {
      const response = await fetch(`/api/users/${id}`);
      
      if (!response.ok) {
        logger.warn('User fetch returned non-OK status', {
          id,
          status: response.status
        });
      }
      
      const user = await response.json();
      logger.log('User fetched successfully', { id, name: user.name });
      
      return user;
    } catch (error) {
      logger.error('Failed to fetch user', { id, error });
      throw error;
    }
  }
}

Build docs developers (and LLMs) love