or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

api

features

charts

charts.mdconditional-formatting.mdvisualizations.md
authorization.mdchangesets.mdcharts-as-code.mdcompiler.mddashboards.mddbt.mdee-features.mdformatting.mdparameters.mdpivot.mdprojects-spaces.mdsql-runner.mdtemplating.mdwarehouse.md
index.md
tile.json

formatting.mddocs/api/utilities/

Display and Formatting Utilities

This module provides utilities for formatting, display, and presentation of data including string transformations, color operations, time/date formatting, internationalization (i18n), and result value formatting for charts and visualizations.

String Utilities

function snakeCaseName(text: string): string;
function hasSpecialCharacters(text: string): boolean;
function friendlyName(text: string): string;
function capitalize(text: string): string;

Example:

import { snakeCaseName, friendlyName, capitalize } from '@lightdash/common';

const snake = snakeCaseName('Customer Name');
// Returns: "customer_name"

const friendly = friendlyName('customer_id');
// Returns: "Customer id"

const capitalized = capitalize('hello world');
// Returns: "Hello world"

Color Utilities

function hexToRGB(hex: string, alpha: number | undefined): string;

function isHexCodeColor(color: string): boolean;

function getInvalidHexColors(colors: string[]): string[];

function cleanColorArray(colors: string[]): string[];

function getReadableTextColor(backgroundColor: string): string;

Example:

import {
  hexToRGB,
  isHexCodeColor,
  getInvalidHexColors,
  cleanColorArray,
  getReadableTextColor
} from '@lightdash/common';

// Convert hex to RGB
const rgb = hexToRGB('#4C8BF5');
// Returns: "rgb(76, 139, 245)"

const rgba = hexToRGB('#4C8BF5', 0.5);
// Returns: "rgba(76, 139, 245, 0.5)"

// Validate hex color
if (isHexCodeColor('#FF5733')) {
  // Valid hex color
}

// Get invalid colors from array
const invalid = getInvalidHexColors(['#FF5733', 'not-a-color', '#123']);
// Returns: ['not-a-color']

// Clean color array
const cleaned = cleanColorArray(['#FF5733 ', ' #123456', '']);
// Returns: ['#FF5733', '#123456']

// Get readable text color for background
const textColor = getReadableTextColor('#000000');
// Returns: 'white'

Value Formatting Functions

Core formatting functions for numbers, dates, timestamps, booleans, and custom formats.

/**
 * Array of supported currency codes for currency formatting
 */
const currencies: string[];

/**
 * Formats boolean values to "True" or "False" strings
 * @param v - Value to format (accepts various truthy representations)
 * @returns "True" or "False"
 */
function formatBoolean<T>(v: T): string;

/**
 * Type guard checking if value is a valid moment/dayjs input
 * @param value - Value to check
 * @returns true if value can be used with moment/dayjs
 */
function isMomentInput(value: unknown): value is MomentInput;

/**
 * Gets date format string for a time interval
 * @param timeInterval - Time interval (default: DAY)
 * @returns Date format string (e.g., "YYYY-MM-DD")
 */
function getDateFormat(timeInterval?: TimeFrames): string;

/**
 * Formats a date value according to time interval
 * @param date - Date to format (string, number, Date, or moment)
 * @param timeInterval - Time interval for formatting (default: DAY)
 * @param convertToUTC - Whether to convert to UTC (default: false)
 * @returns Formatted date string or "NaT" if invalid
 */
function formatDate(
  date: MomentInput,
  timeInterval?: TimeFrames,
  convertToUTC?: boolean
): string;

/**
 * Formats a timestamp value with time component
 * @param value - Timestamp to format
 * @param timeInterval - Time interval (default: MILLISECOND)
 * @param convertToUTC - Whether to convert to UTC (default: false)
 * @returns Formatted timestamp string or "NaT" if invalid
 */
function formatTimestamp(
  value: MomentInput,
  timeInterval?: TimeFrames,
  convertToUTC?: boolean
): string;

/**
 * Gets local time display with timezone
 * @param value - Time value to display
 * @param showTimezone - Whether to show timezone (default: true)
 * @returns Formatted local time string with timezone
 */
function getLocalTimeDisplay(
  value: MomentInput,
  showTimezone?: boolean
): string;

/**
 * Checks if value is NaN when converted to number
 * @param value - Value to check
 * @returns true if value is NaN or boolean
 */
function valueIsNaN(value: unknown): boolean;

/**
 * Type guard for number values
 * @param value - Value to check
 * @returns true if value is a valid number
 */
function isNumber(value: unknown): value is number;

/**
 * Formats a number value with optional custom format
 * @param value - Number to format
 * @param format - Optional custom format configuration
 * @returns Formatted number string
 */
function formatNumberValue(value: number, format?: CustomFormat): string;

/**
 * Applies default formatting to any value
 * @param value - Value to format
 * @returns Formatted string (null → "∅", undefined → "-", numbers formatted)
 */
function applyDefaultFormat(value: unknown): string;

/**
 * Applies custom format configuration to a value
 * Main formatting function that handles all custom format types
 * @param value - Value to format
 * @param format - Custom format configuration
 * @returns Formatted string according to format type
 */
function applyCustomFormat(
  value: unknown,
  format?: CustomFormat
): string;

/**
 * Formats raw value based on field type
 * Primarily handles date/timestamp UTC conversion
 * @param field - Field definition (dimension, metric, table calculation, or custom dimension)
 * @param value - Raw value from database
 * @returns Formatted value (dates/timestamps converted to UTC strings)
 */
function formatRawValue(
  field: Field | Metric | TableCalculation | CustomDimension | undefined,
  value: AnyType
): AnyType;

/**
 * Converts legacy format options to CustomFormat
 * @param format - Legacy format type
 * @param compact - Compact notation
 * @param round - Decimal rounding
 * @returns CustomFormat object
 */
function getCustomFormatFromLegacy(options: {
  format?: Format | string;
  compact?: CompactOrAlias;
  round?: number;
}): CustomFormat;

/**
 * Checks if item has any formatting options
 * @param item - Field, metric, table calculation, or custom dimension
 * @returns true if item has formatting
 */
function hasFormatting(
  item: Field | AdditionalMetric | TableCalculation | CustomDimension | undefined
): boolean;

/**
 * Gets custom format configuration from item
 * @param item - Field, metric, table calculation, or custom dimension
 * @returns CustomFormat or undefined
 */
function getCustomFormat(
  item: Field | AdditionalMetric | TableCalculation | CustomDimension | undefined
): CustomFormat | undefined;

/**
 * Formats value using ECMA-376 format expression
 * @param expression - Format expression (e.g., "0,0.00", "#,##0.00 \"USD\"")
 * @param value - Value to format
 * @returns Formatted string
 */
function formatValueWithExpression(expression: string, value: unknown): string;

/**
 * Checks if item has valid format expression
 * @param item - Field or metric to check
 * @returns true if item has valid format expression
 */
function hasValidFormatExpression<
  T extends Field | AdditionalMetric | TableCalculation | CustomDimension | Dimension
>(item: T | undefined): item is T & { format: string };

/**
 * Converts CustomFormat to ECMA-376 format expression
 * @param format - Custom format configuration
 * @returns Format expression string or null
 */
function convertCustomFormatToFormatExpression(format: CustomFormat): string | null;

/**
 * Gets format expression for item
 * @param item - Field or additional metric
 * @returns Format expression string or undefined
 */
function getFormatExpression(item: Item | AdditionalMetric): string | undefined;

Example:

import {
  currencies,
  formatBoolean,
  formatDate,
  formatTimestamp,
  formatNumberValue,
  getCustomFormat,
  applyCustomFormat,
  formatValueWithExpression,
  convertCustomFormatToFormatExpression,
  CustomFormatType,
  type CustomFormat,
} from '@lightdash/common';

// Supported currencies
console.log(currencies);
// ['USD', 'EUR', 'GBP', 'JPY', 'DKK', ...]

// Boolean formatting
formatBoolean('true');   // "True"
formatBoolean('1');      // "True"
formatBoolean('false');  // "False"
formatBoolean(0);        // "False"

// Date formatting
formatDate('2024-01-15', TimeFrames.DAY);
// "2024-01-15"

formatDate('2024-01-15', TimeFrames.MONTH);
// "2024-01"

formatTimestamp('2024-01-15T10:30:45', TimeFrames.MINUTE);
// "2024-01-15, 10:30 (Z)"

// Number formatting with custom format
const currencyFormat: CustomFormat = {
  type: CustomFormatType.CURRENCY,
  currency: 'USD',
  round: 2,
  separator: NumberSeparator.COMMA_PERIOD,
};

formatNumberValue(1234.56, currencyFormat);
// "$1,234.56"

// Percentage formatting
const percentFormat: CustomFormat = {
  type: CustomFormatType.PERCENT,
  round: 1,
};

applyCustomFormat(0.1234, percentFormat);
// "12.3%"

// Note: prefix, suffix, and compact properties are defined in CustomFormat
// but are not currently implemented in the formatting functions.
// Use the 'currency' property for currency symbols, and values will be
// formatted with full precision according to round and separator settings.

// Format expressions (ECMA-376/Excel format strings)
formatValueWithExpression('0,0.00', 1234.56);
// "1,234.56"

formatValueWithExpression('"Revenue: $"#,##0', 5000);
// "Revenue: $5,000"

// Convert custom format to expression
const format: CustomFormat = {
  type: CustomFormatType.CURRENCY,
  currency: 'EUR',
  round: 2,
};

const expression = convertCustomFormatToFormatExpression(format);
// "[$€]#,##0.00"

// Get format from field
const field = {
  name: 'revenue',
  type: DimensionType.NUMBER,
  formatOptions: currencyFormat,
};

const fieldFormat = getCustomFormat(field);
// Returns currencyFormat

// Check if field has formatting
if (hasFormatting(field)) {
  const formatted = applyCustomFormat(value, getCustomFormat(field));
}

// Apply custom format directly
const percentValue = applyCustomFormat(0.1234, {
  type: CustomFormatType.PERCENT,
  round: 2,
});
// Returns: "12.34%"

const currencyValue = applyCustomFormat(1234.56, {
  type: CustomFormatType.CURRENCY,
  currency: 'USD',
  round: 2,
});
// Returns: "$1,234.56"

// Format raw values (handles date UTC conversion)
const dateField: Dimension = {
  fieldType: FieldType.DIMENSION,
  type: DimensionType.DATE,
  name: 'created_date',
  // ... other properties
};

const rawDate = new Date('2024-01-15T10:30:00Z');
const formattedRaw = formatRawValue(dateField, rawDate);
// Returns: "2024-01-15T10:30:00.000Z" (UTC string)

Date/Time Parsing

Functions for parsing date and time strings into Date objects, and utilities for working with custom week start days.

/**
 * Parses date string according to time interval format
 * @param str - Date string to parse (format depends on timeInterval)
 * @param timeInterval - Time interval for parsing (default: DAY)
 * @returns Parsed Date object
 */
function parseDate(str: string, timeInterval?: TimeFrames): Date;

/**
 * Parses timestamp string according to time interval format
 * @param str - Timestamp string to parse (format depends on timeInterval)
 * @param timeInterval - Time interval for parsing (default: MILLISECOND)
 * @returns Parsed Date object
 */
function parseTimestamp(str: string, timeInterval?: TimeFrames): Date;

/**
 * Gets local time display with timezone information
 * @param value - Time value to display (string, number, Date, or moment)
 * @param showTimezone - Whether to show timezone (default: true)
 * @returns Formatted local time string in "YYYY-MM-DD HH:mm (Timezone)" format
 */
function getLocalTimeDisplay(
  value: MomentInput,
  showTimezone?: boolean
): string;

/**
 * Converts WeekDay enum to moment.js week day format
 * Lightdash uses 0-6 (Monday-Sunday), moment.js uses 0-6 (Sunday-Saturday)
 * @param weekDay - WeekDay enum value (0=Monday to 6=Sunday)
 * @returns Moment.js week day number (0=Sunday to 6=Saturday)
 */
function convertWeekDayToMomentWeekDay(weekDay: WeekDay): number;

/**
 * Creates moment.Moment with custom week start day
 * Used for accurate week-based date operations with custom start of week
 * @param startOfWeek - Custom start of week day (WeekDay enum, null, or undefined)
 * @param inp - Optional moment input (date string, number, Date, etc.)
 * @returns moment.Moment instance with custom locale if startOfWeek is provided
 */
function getMomentDateWithCustomStartOfWeek(
  startOfWeek: WeekDay | null | undefined,
  inp?: moment.MomentInput
): moment.Moment;

Example:

import {
  parseDate,
  parseTimestamp,
  getLocalTimeDisplay,
  convertWeekDayToMomentWeekDay,
  getMomentDateWithCustomStartOfWeek,
  TimeFrames,
  WeekDay,
} from '@lightdash/common';

// Parse date strings with different time intervals
const dailyDate = parseDate('2024-01-15', TimeFrames.DAY);
// Parses "2024-01-15" format -> Date object

const monthlyDate = parseDate('2024-01', TimeFrames.MONTH);
// Parses "2024-01" format -> Date object

const yearlyDate = parseDate('2024', TimeFrames.YEAR);
// Parses "YYYY" format -> Date object

const quarterDate = parseDate('2024-Q1', TimeFrames.QUARTER);
// Parses "YYYY-[Q]Q" format -> Date object

// Parse timestamp strings with time components
const timestampWithMinutes = parseTimestamp(
  '2024-01-15, 10:30 (Z)',
  TimeFrames.MINUTE
);
// Parses "YYYY-MM-DD, HH:mm (Z)" format -> Date object

const timestampWithSeconds = parseTimestamp(
  '2024-01-15, 10:30:45 (Z)',
  TimeFrames.SECOND
);
// Parses "YYYY-MM-DD, HH:mm:ss (Z)" format -> Date object

const timestampWithMillis = parseTimestamp(
  '2024-01-15, 10:30:45:123 (Z)',
  TimeFrames.MILLISECOND
);
// Parses "YYYY-MM-DD, HH:mm:ss:SSS (Z)" format -> Date object

// Display local time with timezone
const localTime = getLocalTimeDisplay(new Date());
// Returns: "2024-01-15 10:30 (America/New_York)"

const localTimeWithoutTz = getLocalTimeDisplay(new Date(), false);
// Returns: "2024-01-15 10:30 "

// Convert WeekDay to moment.js format
const mondayInMoment = convertWeekDayToMomentWeekDay(WeekDay.MONDAY);
// Returns: 1 (Monday in moment.js is 1, not 0)

const sundayInMoment = convertWeekDayToMomentWeekDay(WeekDay.SUNDAY);
// Returns: 0 (Sunday in moment.js is 0)

// Create moment with custom week start for accurate week operations
const dateStr = '2024-01-15';

// Default moment (week starts on Sunday)
const defaultMoment = getMomentDateWithCustomStartOfWeek(null, dateStr);
const weekStartDefault = defaultMoment.startOf('week');
// Week starts on Sunday

// Custom week start on Monday
const mondayMoment = getMomentDateWithCustomStartOfWeek(WeekDay.MONDAY, dateStr);
const weekStartMonday = mondayMoment.startOf('week');
// Week starts on Monday

// Custom week start on Wednesday
const wednesdayMoment = getMomentDateWithCustomStartOfWeek(WeekDay.WEDNESDAY, dateStr);
const weekStartWednesday = wednesdayMoment.startOf('week');
// Week starts on Wednesday

// Use for week-based calculations
const customWeekMoment = getMomentDateWithCustomStartOfWeek(WeekDay.MONDAY, '2024-06-15');
const weekStart = customWeekMoment.startOf('week'); // Gets Monday of that week
const weekEnd = customWeekMoment.endOf('week');     // Gets Sunday of that week
const isoWeek = customWeekMoment.isoWeek();         // Gets ISO week number

Format Item Value

Complete signature for formatting field values with all options.

/**
 * Format a value according to field configuration
 * Main formatting function used throughout the application
 * Handles custom formats, format expressions, dates, timestamps, and parameters
 * @param item - Field, dimension, metric, table calculation, or custom dimension
 * @param value - Value to format
 * @param convertToUTC - Whether to convert timestamps to UTC (default: undefined)
 * @param parameters - Optional parameters for template substitution in format expressions
 * @returns Formatted string value
 */
function formatItemValue(
  item: Field | Dimension | AdditionalMetric | TableCalculation | CustomDimension | undefined,
  value: unknown,
  convertToUTC?: boolean,
  parameters?: Record<string, unknown>
): string;

Example:

import { formatItemValue } from '@lightdash/common';

// Format with parameters for conditional formatting
const item = {
  name: 'revenue',
  type: DimensionType.NUMBER,
  format: '${p.currency_format}', // Uses parameter
  formatOptions: { type: CustomFormatType.CURRENCY, currency: 'USD' }
};

const formatted1 = formatItemValue(
  item,
  1234.56,
  true,
  { 'p.currency_format': '"$"#,##0.00' }
);
// Returns: "$1,234.56"

// Format timestamp with UTC conversion
const timestampField = {
  name: 'created_at',
  type: DimensionType.TIMESTAMP,
  timeInterval: TimeFrames.DAY,
};

const formatted2 = formatItemValue(
  timestampField,
  new Date('2024-01-15T10:30:00Z'),
  true
);
// Returns: "2024-01-15"

// Format without field (uses default formatting)
const formatted3 = formatItemValue(undefined, 1234.56);
// Returns: "1234.56"

Time Utilities

Functions for working with time and week configuration.

function convertWeekDayToMomentWeekDay(weekDay: WeekDay): number;

function getMomentDateWithCustomStartOfWeek(
  startOfWeek: WeekDay | null | undefined,
  inp?: moment.MomentInput
): moment.Moment;

Example:

import {
  convertWeekDayToMomentWeekDay,
  getMomentDateWithCustomStartOfWeek,
  WeekDay
} from '@lightdash/common';

// Convert Lightdash WeekDay (0=Monday to 6=Sunday) to moment.js format (0=Sunday to 6=Saturday)
const momentDay = convertWeekDayToMomentWeekDay(WeekDay.MONDAY);
// Returns: 1

// Create moment with custom week start for accurate week calculations
const date = '2024-01-15';
const momentWithCustomWeek = getMomentDateWithCustomStartOfWeek(WeekDay.MONDAY, date);
// moment.Moment with Monday as first day of week

// Use for accurate startOf/endOf week operations
const weekStart = getMomentDateWithCustomStartOfWeek(WeekDay.SUNDAY, date).startOf('week');
const weekEnd = getMomentDateWithCustomStartOfWeek(WeekDay.SUNDAY, date).endOf('week');

Time Frame Utilities

const timeFrameConfigs: Record<TimeFrames, TimeFrameConfig>;

interface TimeFrameConfig {
  label: string;
  format: string;
  getStartOfPeriod(date: moment.Moment): moment.Moment;
  getEndOfPeriod(date: moment.Moment): moment.Moment;
}

Utilities for working with time frames and date granularity.

/**
 * Enum for days of the week (0-6, where 0 = Monday)
 */
enum WeekDay {
  MONDAY = 0,
  TUESDAY = 1,
  WEDNESDAY = 2,
  THURSDAY = 3,
  FRIDAY = 4,
  SATURDAY = 5,
  SUNDAY = 6,
}

/**
 * Type guard to check if a value is a valid WeekDay
 * @param value - Value to check
 * @returns true if value is a WeekDay
 */
function isWeekDay(value: unknown): value is WeekDay;

/**
 * Configuration for time frame operations
 * Maps time frames to their SQL generation functions, labels, and axis formatting
 */
const timeFrameConfigs: Record<TimeFrames, TimeFrameConfig>;

/**
 * Gets the default time frames for a dimension type
 * @param type - The dimension type (DATE or TIMESTAMP)
 * @returns Array of default time frames
 */
function getDefaultTimeFrames(type: DimensionType): TimeFrames[];

/**
 * Type guard to check if a string is a valid TimeFrames value
 * @param value - String to check
 * @returns true if value is a valid TimeFrames
 */
function isTimeInterval(value: string): value is TimeFrames;

/**
 * Validates and converts an array of strings to TimeFrames
 * @param values - Array of strings to validate
 * @returns Array of valid TimeFrames values
 */
function validateTimeFrames(values: string[]): TimeFrames[];

/**
 * Sorts time frames in chronological order
 * @param a - First time frame
 * @param b - Second time frame
 * @returns Sort comparison result
 */
function sortTimeFrames(a: TimeFrames, b: TimeFrames): number;

/**
 * Extracts the base dimension ID and time frame from a dimension ID
 * @param dimensionId - The dimension ID (e.g., "orders_created_day")
 * @returns Object with baseDimensionId and newTimeFrame, or empty object if not a date dimension
 */
function getDateDimension(dimensionId: string): {
  baseDimensionId?: string;
  newTimeFrame?: TimeFrames;
};

/**
 * Gets warehouse-specific SQL for truncating dates
 * @param adapterType - The warehouse adapter type
 * @param timeFrame - The time frame to truncate to
 * @param originalSql - The original SQL expression
 * @param type - The dimension type
 * @param startOfWeek - Optional start of week day
 * @returns SQL expression for date truncation
 */
function getSqlForTruncatedDate(
  adapterType: SupportedDbtAdapter,
  timeFrame: TimeFrames,
  originalSql: string,
  type: DimensionType,
  startOfWeek?: WeekDay | null
): string;

Example:

import {
  WeekDay,
  isWeekDay,
  getDefaultTimeFrames,
  validateTimeFrames,
  sortTimeFrames,
  getDateDimension,
} from '@lightdash/common';

// Get default time frames for a timestamp dimension
const timeFrames = getDefaultTimeFrames(DimensionType.TIMESTAMP);
// Returns: [RAW, DAY, WEEK, MONTH, QUARTER, YEAR]

// Validate time frame strings
const validated = validateTimeFrames(['day', 'WEEK', 'invalid']);
// Returns: [TimeFrames.DAY, TimeFrames.WEEK]

// Sort time frames chronologically
const sorted = [TimeFrames.YEAR, TimeFrames.DAY, TimeFrames.MONTH].sort(sortTimeFrames);
// Returns: [TimeFrames.DAY, TimeFrames.MONTH, TimeFrames.YEAR]

// Parse dimension with time frame
const parsed = getDateDimension('orders_created_day');
// Returns: { baseDimensionId: 'orders_created', newTimeFrame: TimeFrames.DAY }

// Check week day
if (isWeekDay(0)) {
  console.log('Monday is a valid week day');
}

Result Formatting and Axis Configuration

Functions for formatting query result values and configuring chart axes.

/**
 * Extracts result value arrays from result rows with optional min/max calculation.
 * @param rows - Array of result rows
 * @param preferRaw - Whether to prefer raw over formatted values
 * @param calculateMinAndMax - Whether to calculate min/max for numeric values
 * @param excludeNulls - Whether to exclude null values
 * @returns Object with results array and optional min/max map
 */
function getResultValueArray(
  rows: ResultRow[],
  preferRaw?: boolean,
  calculateMinAndMax?: boolean,
  excludeNulls?: boolean
): {
  results: Record<string, unknown>[];
  minsAndMaxes?: Record<string, { min: number; max: number }>;
};

/**
 * Gets display label for date-grouped dimensions, removing timeframe suffixes.
 * @param axisItem - Item from items map
 * @returns Cleaned label without timeframe (e.g., "Order created" instead of "Order created day")
 */
function getDateGroupLabel(axisItem: ItemsMap[string]): string | undefined;

/**
 * Gets axis name for chart configuration based on series and axis index.
 * @param isAxisTheSameForAllSeries - Whether all series share the same axis
 * @param selectedAxisIndex - Currently selected axis index
 * @param axisReference - Axis reference ('xRef' or 'yRef')
 * @param axisIndex - Current axis index
 * @param axisName - Optional explicit axis name
 * @param series - Array of chart series
 * @param itemsMap - Map of items for field lookup
 * @returns Axis name or undefined
 */
function getAxisName(args: {
  isAxisTheSameForAllSeries: boolean;
  selectedAxisIndex: number;
  axisReference: 'yRef' | 'xRef';
  axisIndex: number;
  axisName?: string;
  series?: Series[];
  itemsMap: ItemsMap | undefined;
}): string | undefined;

/**
 * Formats a single row from raw database values to result row format.
 * @param row - Raw row data from database
 * @param itemsMap - Map of items for field lookup
 * @param pivotValuesColumns - Optional pivot column mapping
 * @param parameters - Optional parameter values for templating
 * @returns Result row with formatted and raw values
 */
function formatRow(
  row: { [col: string]: AnyType },
  itemsMap: ItemsMap,
  pivotValuesColumns?: Record<string, PivotValuesColumn> | null,
  parameters?: Record<string, unknown>
): ResultRow;

/**
 * Formats multiple rows from raw database values to result row format.
 * @param rows - Array of raw rows from database
 * @param itemsMap - Map of items for field lookup
 * @param pivotValuesColumns - Optional pivot column mapping
 * @param parameters - Optional parameter values for templating
 * @returns Array of result rows with formatted and raw values
 */
function formatRows(
  rows: { [col: string]: AnyType }[],
  itemsMap: ItemsMap,
  pivotValuesColumns?: Record<string, PivotValuesColumn> | null,
  parameters?: Record<string, unknown>
): ResultRow[];

/**
 * Formats raw value for a field (handles date/timestamp UTC conversion).
 * @param field - Field or metric definition
 * @param value - Raw value from database
 * @returns Formatted raw value (dates converted to UTC)
 */
function formatRawValue(
  field: Field | Metric | TableCalculation | CustomDimension | undefined,
  value: AnyType
): AnyType;

/**
 * Formats multiple raw rows for comparison (dates to UTC).
 * @param rows - Array of raw rows
 * @param itemsMap - Map of items for field lookup
 * @returns Array of rows with formatted raw values
 */
function formatRawRows(
  rows: { [col: string]: AnyType }[],
  itemsMap: ItemsMap
): Record<string, unknown>[];

/**
 * Gets field IDs from a metric query.
 * @param metricQuery - The metric query
 * @returns Array of field IDs (metrics + dimensions + table calculations)
 */
function itemsInMetricQuery(metricQuery: MetricQuery | undefined): string[];

Example:

import {
  getResultValueArray,
  getDateGroupLabel,
  getAxisName,
  formatRow,
  formatRows,
  itemsInMetricQuery
} from '@lightdash/common';

// Extract values with min/max calculation
const { results, minsAndMaxes } = getResultValueArray(
  rows,
  true, // prefer raw values
  true, // calculate min/max
  false // don't exclude nulls
);

// Use min/max for conditional formatting
if (minsAndMaxes?.revenue) {
  console.log(`Revenue range: ${minsAndMaxes.revenue.min} - ${minsAndMaxes.revenue.max}`);
}

// Get clean axis labels
const axisItem = itemsMap['orders_created_day'];
const label = getDateGroupLabel(axisItem);
// Returns: "Orders created" (without "day" suffix)

// Configure axis names
const axisName = getAxisName({
  isAxisTheSameForAllSeries: false,
  selectedAxisIndex: 0,
  axisReference: 'yRef',
  axisIndex: 0,
  axisName: 'Revenue',
  series: chartSeries,
  itemsMap
});

// Format database rows
const formattedRows = formatRows(rawRows, itemsMap, pivotColumns, parameters);

// Get field IDs from query
const fieldIds = itemsInMetricQuery(metricQuery);
// Returns: ['customers_customer_id', 'orders_total', 'calc_profit']

Internationalization (i18n) Utilities

Utilities for internationalization of chart-as-code and dashboard-as-code content, enabling multi-language support for analytics configurations.

/**
 * Base abstract class for internationalizing as-code content
 * Provides common patterns for chart and dashboard internationalization
 */
abstract class AsCodeInternalization<
  T extends { type: 'chart' | 'dashboard'; content: ChartAsCode | DashboardAsCode },
  Z extends z.AnyZodObject
> {
  protected abstract schema: Z;

  /**
   * Extracts translatable content from as-code configuration
   * @param asCode - The chart or dashboard as-code configuration
   * @returns Language map with translatable fields organized by slug
   */
  public abstract getLanguageMap(asCode: T['content']): {
    [typeKey in T['type']]: {
      [slugKey in T['content']['slug']]: PartialDeep<
        T['content'],
        { recurseIntoArrays: true }
      >;
    };
  };

  /**
   * Merges translations back into the as-code content
   * @param internalizationMap - Partial translations to merge
   * @param content - Original as-code content
   * @returns Merged content with translations applied
   */
  public abstract merge(
    internalizationMap: ReturnType<this['getLanguageMap']>[T['type']][string],
    content: T['content']
  ): T['content'];
}

/**
 * Internationalization handler for chart-as-code configurations
 * Supports all chart types: cartesian, pie, funnel, big number, table, custom
 */
class ChartAsCodeInternalization extends AsCodeInternalization<
  { type: 'chart'; content: ChartAsCode },
  typeof chartAsCodeSchema
> {
  constructor(protected schema?: typeof chartAsCodeSchema);

  /**
   * Extracts translatable fields from chart configuration
   * Includes name, description, axis labels, series names, mark lines, etc.
   * @param chartAsCode - The chart as-code configuration
   * @returns Language map with chart slug as key
   */
  public getLanguageMap(chartAsCode: ChartAsCode): ChartAsCodeLanguageMap;

  /**
   * Merges translations into chart configuration
   * @param internalizationMap - Translations to apply
   * @param content - Original chart configuration
   * @returns Chart with translations merged
   */
  public merge(
    internalizationMap: ChartAsCodeLanguageMap['chart'][string],
    content: ChartAsCode
  ): ChartAsCode;
}

/**
 * Internationalization handler for dashboard-as-code configurations
 * Handles dashboard metadata and tile properties
 */
class DashboardAsCodeInternalization extends AsCodeInternalization<
  { type: 'dashboard'; content: DashboardAsCode },
  typeof dashboardAsCodeSchema
> {
  constructor(protected schema?: typeof dashboardAsCodeSchema);

  /**
   * Extracts translatable fields from dashboard configuration
   * Includes name, description, tile titles, and markdown content
   * @param dashboardAsCode - The dashboard as-code configuration
   * @returns Language map with dashboard slug as key
   */
  public getLanguageMap(dashboardAsCode: DashboardAsCode): DashboardAsCodeLanguageMap;

  /**
   * Merges translations into dashboard configuration
   * @param internalizationMap - Translations to apply
   * @param content - Original dashboard configuration
   * @returns Dashboard with translations merged
   */
  public merge(
    internalizationMap: DashboardAsCodeLanguageMap['dashboard'][string],
    content: DashboardAsCode
  ): DashboardAsCode;
}

/**
 * Merges existing translations into content, only replacing fields present in right
 * Performs deep recursive merge while preserving structure
 * @param left - Original content (complete structure)
 * @param right - Translations to merge (partial structure)
 * @returns Merged content with translations applied
 */
function mergeExisting(left: any, right: any): any;

// Type exports
type ChartAsCodeLanguageMap = ReturnType<ChartAsCodeInternalization['getLanguageMap']>;
type DashboardAsCodeLanguageMap = ReturnType<DashboardAsCodeInternalization['getLanguageMap']>;
type LanguageMap = Partial<ChartAsCodeLanguageMap & DashboardAsCodeLanguageMap>;

Usage Examples:

import {
  ChartAsCodeInternalization,
  DashboardAsCodeInternalization,
  mergeExisting,
  type ChartAsCode,
  type DashboardAsCode,
  type LanguageMap,
} from '@lightdash/common';

// Extract translatable content from a chart
const chartI18n = new ChartAsCodeInternalization();
const chartAsCode: ChartAsCode = {
  slug: 'monthly-revenue',
  name: 'Monthly Revenue',
  description: 'Revenue breakdown by month',
  chartConfig: {
    type: 'cartesian',
    config: {
      eChartsConfig: {
        xAxis: [{ name: 'Month' }],
        yAxis: [{ name: 'Revenue (USD)' }],
        series: [
          {
            name: 'Total Revenue',
            markLine: {
              data: [{ name: 'Target' }],
            },
          },
        ],
      },
    },
  },
};

// Extract language map with translatable fields
const languageMap = chartI18n.getLanguageMap(chartAsCode);
console.log(languageMap);
// {
//   chart: {
//     'monthly-revenue': {
//       name: 'Monthly Revenue',
//       description: 'Revenue breakdown by month',
//       chartConfig: {
//         config: {
//           eChartsConfig: {
//             xAxis: [{ name: 'Month' }],
//             yAxis: [{ name: 'Revenue (USD)' }],
//             series: [{ name: 'Total Revenue', markLine: { data: [{ name: 'Target' }] } }]
//           }
//         }
//       }
//     }
//   }
// }

// Apply Spanish translations
const spanishTranslations = {
  name: 'Ingresos Mensuales',
  description: 'Desglose de ingresos por mes',
  chartConfig: {
    config: {
      eChartsConfig: {
        xAxis: [{ name: 'Mes' }],
        yAxis: [{ name: 'Ingresos (USD)' }],
        series: [
          {
            name: 'Ingresos Totales',
            markLine: { data: [{ name: 'Objetivo' }] },
          },
        ],
      },
    },
  },
};

const translatedChart = chartI18n.merge(spanishTranslations, chartAsCode);

// Extract translatable content from a dashboard
const dashboardI18n = new DashboardAsCodeInternalization();
const dashboardAsCode: DashboardAsCode = {
  slug: 'sales-overview',
  name: 'Sales Overview',
  description: 'Key sales metrics',
  tiles: [
    {
      type: 'saved_chart',
      properties: {
        title: 'Revenue Chart',
        chartName: 'monthly-revenue',
      },
    },
    {
      type: 'markdown',
      properties: {
        title: 'Analysis Notes',
        content: '## Key Insights\nRevenue is trending upward',
      },
    },
  ],
};

const dashboardLanguageMap = dashboardI18n.getLanguageMap(dashboardAsCode);

// Apply translations
const frenchTranslations = {
  name: 'Aperçu des Ventes',
  description: 'Métriques clés des ventes',
  tiles: [
    {
      properties: {
        title: 'Graphique des Revenus',
      },
    },
    {
      properties: {
        title: 'Notes d\'Analyse',
        content: '## Informations Clés\nLes revenus sont en hausse',
      },
    },
  ],
};

const translatedDashboard = dashboardI18n.merge(frenchTranslations, dashboardAsCode);

// Using mergeExisting directly for custom merge scenarios
const original = {
  title: 'Original Title',
  config: { color: 'blue', size: 10 },
  metadata: { created: '2024-01-01' },
};

const updates = {
  title: 'Updated Title',
  config: { color: 'red' }, // size preserved
};

const merged = mergeExisting(original, updates);
// Result: { title: 'Updated Title', config: { color: 'red', size: 10 }, metadata: { created: '2024-01-01' } }

SQL Query Result Types

Row Formatting Functions

Functions for formatting raw database rows into structured result rows with both raw and formatted values.

/**
 * Formats a single raw value based on field type (handles dates/timestamps)
 * @param field - Field definition (dimension, metric, table calculation, or custom dimension)
 * @param value - Raw value from the database
 * @returns Formatted value (dates/timestamps converted to UTC strings)
 */
function formatRawValue(
  field: Field | Metric | TableCalculation | CustomDimension | undefined,
  value: AnyType
): AnyType;

/**
 * Formats an array of raw database rows (handles dates/timestamps only)
 * @param rows - Array of raw database rows
 * @param itemsMap - Map of field IDs to field definitions
 * @returns Array of rows with raw values formatted for dates/timestamps
 */
function formatRawRows(
  rows: { [col: string]: AnyType }[],
  itemsMap: ItemsMap
): Record<string, unknown>[];

/**
 * Formats a single database row into a structured result row
 * @param row - Raw database row
 * @param itemsMap - Map of field IDs to field definitions
 * @param pivotValuesColumns - Optional pivot configuration
 * @param parameters - Optional parameter values for formatting
 * @returns Result row with both raw and formatted values
 */
function formatRow(
  row: { [col: string]: AnyType },
  itemsMap: ItemsMap,
  pivotValuesColumns?: Record<string, PivotValuesColumn> | null,
  parameters?: Record<string, unknown>
): ResultRow;

/**
 * Formats an array of database rows into structured result rows
 * @param rows - Array of raw database rows
 * @param itemsMap - Map of field IDs to field definitions
 * @param pivotValuesColumns - Optional pivot configuration
 * @param parameters - Optional parameter values for formatting
 * @returns Array of result rows with both raw and formatted values
 */
function formatRows(
  rows: { [col: string]: AnyType }[],
  itemsMap: ItemsMap,
  pivotValuesColumns?: Record<string, PivotValuesColumn> | null,
  parameters?: Record<string, unknown>
): ResultRow[];

Example:

import {
  formatRawValue,
  formatRawRows,
  formatRow,
  formatRows,
  getItemMap,
} from '@lightdash/common';

// Format a single raw value
const dateField = explore.tables.orders.dimensions.created_at;
const rawDate = new Date('2024-01-15T10:30:00Z');
const formattedDate = formatRawValue(dateField, rawDate);
// Returns: "2024-01-15T10:30:00.000Z" (UTC string)

// Format raw rows (dates/timestamps only)
const rawRows = [
  { customers_name: 'Alice', orders_created_at: new Date('2024-01-15') },
  { customers_name: 'Bob', orders_created_at: new Date('2024-01-16') },
];
const itemsMap = getItemMap(explore);
const formattedRawRows = formatRawRows(rawRows, itemsMap);
// Returns: [
//   { customers_name: 'Alice', orders_created_at: '2024-01-15T00:00:00.000Z' },
//   { customers_name: 'Bob', orders_created_at: '2024-01-16T00:00:00.000Z' },
// ]

// Format a complete result row with both raw and formatted values
const singleRow = {
  customers_name: 'Charlie',
  customers_total_revenue: 15234.56,
  orders_created_at: new Date('2024-01-17'),
};
const resultRow = formatRow(singleRow, itemsMap);
// Returns: {
//   customers_name: {
//     value: { raw: 'Charlie', formatted: 'Charlie' }
//   },
//   customers_total_revenue: {
//     value: { raw: 15234.56, formatted: '$15,234.56' }
//   },
//   orders_created_at: {
//     value: { raw: '2024-01-17T00:00:00.000Z', formatted: '2024-01-17' }
//   }
// }

// Format multiple rows
const multipleRawRows = [
  { product_name: 'Widget', revenue: 1234.5 },
  { product_name: 'Gadget', revenue: 5678.9 },
];
const resultRows = formatRows(multipleRawRows, itemsMap);
// Each row will have both raw and formatted values

Key differences:

  • formatRawValue() - Handles single values, primarily for date/timestamp UTC conversion
  • formatRawRows() - Processes arrays of rows but only formats dates/timestamps
  • formatRow() - Full formatting with both raw and formatted values in ResultRow structure
  • formatRows() - Batch version of formatRow() for multiple rows