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

data.mddocs/api/utilities/

Data Structure Utilities

This module provides utilities for working with data structures including arrays, objects, items (fields, metrics, dimensions), and generic type operations. These utilities help manipulate, transform, and analyze data throughout the Lightdash platform.

Capabilities

This module provides the following functionality:

Array Utilities

function hasIntersection(tags: string[], tags2: string[]): boolean;
function toggleArrayValue<T>(initialArray: T[], value: T): T[];
function replaceStringInArray(
  arrayToUpdate: string[],
  valueToReplace: string,
  newValue: string
): string[];

Example:

import { hasIntersection, toggleArrayValue, replaceStringInArray } from '@lightdash/common';

// Check if arrays have common elements
if (hasIntersection(['tag1', 'tag2'], ['tag2', 'tag3'])) {
  console.log('Arrays intersect');
}

// Toggle value in array
const tags = ['tag1', 'tag2'];
const newTags = toggleArrayValue(tags, 'tag2');
// Returns: ['tag1'] (removed 'tag2')

const withTag = toggleArrayValue(newTags, 'tag3');
// Returns: ['tag1', 'tag3'] (added 'tag3')

// Replace string in array
const dimensions = ['customers.id', 'orders.id', 'customers.name'];
const updated = replaceStringInArray(dimensions, 'customers.id', 'users.id');
// Returns: ['users.id', 'orders.id', 'customers.name']

Object Utilities

function removeEmptyProperties(
  object: Record<string, unknown>
): Record<string, unknown>;

function deepEqual(
  object1: Record<string, unknown>,
  object2: Record<string, unknown>
): boolean;

Example:

import { removeEmptyProperties, deepEqual } from '@lightdash/common';

const cleaned = removeEmptyProperties({
  name: 'test',
  value: undefined,
  count: null,
  enabled: false,
});
// Returns: { name: 'test', enabled: false }

const isEqual = deepEqual(
  { a: 1, b: { c: 2 } },
  { a: 1, b: { c: 2 } }
);
// Returns: true

Safe Accessor Utilities

Functions for safely accessing array and object values with automatic error handling. These utilities throw UnexpectedIndexError when attempting to access undefined values, helping catch programming errors early.

/**
 * Safely gets a value from an array by index
 * Throws UnexpectedIndexError if the array is undefined or the key doesn't exist
 * @param obj - The array to access (can be undefined)
 * @param key - The numeric index to access
 * @param errorMessage - Optional custom error message
 * @returns The value at the specified index
 * @throws UnexpectedIndexError if array is undefined or key doesn't exist
 */
function getArrayValue<T>(
  obj: ArrayLike<T> | undefined,
  key: number,
  errorMessage?: string
): T;

/**
 * Safely gets a value from an object by key
 * Throws UnexpectedIndexError if the object is undefined or the key doesn't exist
 * @param obj - The object to access (can be undefined)
 * @param key - The string or numeric key to access
 * @param errorMessage - Optional custom error message
 * @returns The value at the specified key
 * @throws UnexpectedIndexError if object is undefined or key doesn't exist
 */
function getObjectValue<T>(
  obj: Record<string | number, T> | undefined,
  key: string | number,
  errorMessage?: string
): T;

/**
 * Type guard that checks if an object has a specific property
 * @param obj - The object to check
 * @param property - The property name to check for
 * @returns true if obj is an object containing the property
 */
function hasProperty<T>(
  obj: unknown,
  property: string
): obj is { scope: T };

Example:

import { getArrayValue, getObjectValue, hasProperty } from '@lightdash/common';

// Safe array access
const series = [
  { name: 'Revenue', data: [100, 200, 300] },
  { name: 'Profit', data: [20, 40, 60] }
];

const firstSeries = getArrayValue(series, 0);
// Returns: { name: 'Revenue', data: [100, 200, 300] }

// This would throw UnexpectedIndexError
try {
  const invalidSeries = getArrayValue(series, 10);
} catch (error) {
  console.error('Index out of bounds');
}

// Safe object access
const config = {
  x: { field: 'date', type: 'time' },
  y: { field: 'revenue', type: 'number' }
};

const xConfig = getObjectValue(config, 'x');
// Returns: { field: 'date', type: 'time' }

// With custom error message
const zConfig = getObjectValue(
  config,
  'z',
  'Z-axis configuration is required but missing'
);
// Throws: UnexpectedIndexError with custom message

// Property checking
const obj: unknown = { scope: 'read:dashboard' };
if (hasProperty<string>(obj, 'scope')) {
  // TypeScript now knows obj has a 'scope' property of type string
  console.log(obj.scope);
}

Item Utilities

Functions for working with individual items (fields, metrics, dimensions, table calculations, custom dimensions) including ID generation, labeling, type checking, and metadata access.

/**
 * Gets the unique identifier for an item
 * For custom dimensions: returns item.id
 * For table calculations: returns item.name
 * For fields/metrics: returns `${table}_${name}` format
 *
 * IMPORTANT: Performs dot-to-underscore transformation:
 * - Single dots become single underscores: "customers.customer_id" → "customers_customer_id"
 * - Field names with dots get double underscores: "customer.id" field → "customers_customer__id"
 *
 * @param item - The item to get ID for
 * @returns The unique identifier string in underscore format
 */
function getItemId(
  item: ItemsMap[string] | AdditionalMetric | Pick<Field, 'name' | 'table'>
): string;

/**
 * Gets the display label for an item without the table name prefix
 * @param item - The item to get label for
 * @returns The label or display name of the item
 */
function getItemLabelWithoutTableName(item: Item): string;

/**
 * Gets the full display label for an item including table name for fields
 * @param item - The item to get label for
 * @returns The full label with table prefix if applicable
 */
function getItemLabel(item: Item): string;

/**
 * Gets the type of an item (dimension, metric, or table calculation type)
 * @param item - The item to get type for
 * @returns The dimension, metric, or table calculation type
 */
function getItemType(
  item: ItemsMap[string] | AdditionalMetric
): DimensionType | MetricType | TableCalculationType;

/**
 * Checks if an item is numeric (number, count, sum, average, etc.)
 * @param item - The item to check
 * @returns true if the item has a numeric type
 */
function isNumericItem(
  item: Field | AdditionalMetric | TableCalculation | CustomDimension | undefined
): boolean;

/**
 * Checks if an item is a string dimension
 * @param item - The item to check
 * @returns true if the item is a dimension with string type
 */
function isStringDimension(
  item: Field | AdditionalMetric | TableCalculation | CustomDimension | undefined
): boolean;

/**
 * Checks if an item is a date or timestamp item
 * @param item - The item to check
 * @returns true if the item has a date or timestamp type
 */
function isDateItem(
  item: Field | AdditionalMetric | TableCalculation | CustomSqlDimension | undefined
): boolean;

/**
 * Gets the icon name for an item based on its type
 * Dimensions return 'tag', metrics return 'numerical', calculations return 'function'
 * @param item - The item to get icon for
 * @returns The icon identifier string
 */
function getItemIcon(
  item: Field | TableCalculation | AdditionalMetric | CustomDimension
): string;

/**
 * Gets the color hex code for an item based on its type
 * Dimensions: #0E5A8A (blue), Metrics: #A66321 (orange), Calculations: #0A6640 (green)
 * @param item - The item to get color for
 * @returns The hex color code string
 */
function getItemColor(
  item: Field | TableCalculation | AdditionalMetric | CustomDimension
): string;

/**
 * Checks if a type is numeric (includes number dimensions, all numeric metrics)
 * @param type - The dimension, metric, or table calculation type
 * @returns true if the type is numeric
 */
function isNumericType(
  type: DimensionType | MetricType | TableCalculationType
): boolean;

/**
 * Replaces a dimension in an explore with an updated version
 * @param explore - The explore to update
 * @param dimension - The dimension to replace
 * @returns A new explore with the dimension replaced
 */
function replaceDimensionInExplore(
  explore: Explore,
  dimension: CompiledDimension
): Explore;

/**
 * Checks if formatting can be applied to a custom metric based on the base dimension
 * Returns true if the dimension is numeric or the custom metric is a count
 * @param item - The base dimension
 * @param customMetricType - The custom metric type
 * @returns true if formatting can be applied
 */
function canApplyFormattingToCustomMetric(
  item: Dimension,
  customMetricType: MetricType
): boolean;

/**
 * Sorts items for use on the X axis, prioritizing date dimensions, then other dimensions
 * Used in chart configuration to suggest appropriate x-axis fields
 * Priority: Date dimensions > Dimensions > Custom dimensions > Metrics > Other
 * @param itemsMap - Map of all available items
 * @returns Sorted array of items
 */
function sortedItemsForXAxis(itemsMap: ItemsMap | undefined): ItemsMap[string][];

/**
 * Sorts items for use on the Y axis, prioritizing numeric metrics
 * Used in chart configuration to suggest appropriate y-axis fields
 * Priority: Numeric metrics > Other metrics > Table calculations > Other
 * @param itemsMap - Map of all available items
 * @returns Sorted array of items
 */
function sortedItemsForYAxis(itemsMap: ItemsMap | undefined): ItemsMap[string][];

Example:

import {
  getItemId,
  getItemLabel,
  getItemLabelWithoutTableName,
  getItemType,
  isNumericItem,
  isDateItem,
  getItemIcon,
  getItemColor,
  sortedItemsForXAxis,
  sortedItemsForYAxis,
  type Explore,
  type ItemsMap,
} from '@lightdash/common';

// Get unique item ID for a field
const field = explore.tables['customers'].dimensions['customer_id'];
const itemId = getItemId(field); // Returns 'customers_customer_id'

// Get display labels
const fullLabel = getItemLabel(field); // Returns 'Customers Customer ID'
const shortLabel = getItemLabelWithoutTableName(field); // Returns 'Customer ID'

// Check item types
const metric = explore.tables['orders'].metrics['total_revenue'];
if (isNumericItem(metric)) {
  console.log('Can format as number');
}

const dateField = explore.tables['orders'].dimensions['created_at'];
if (isDateItem(dateField)) {
  console.log('Can apply time granularity');
}

// Get visual properties for UI
const icon = getItemIcon(metric); // Returns 'numerical'
const color = getItemColor(metric); // Returns '#A66321' (orange)

// Sort items for chart axes
const itemsMap = getItemMap(explore);
const xAxisOptions = sortedItemsForXAxis(itemsMap);
const yAxisOptions = sortedItemsForYAxis(itemsMap);

console.log('Suggested X-axis:', xAxisOptions[0]);
console.log('Suggested Y-axis:', yAxisOptions[0]);

// Get item type
const type = getItemType(metric);
console.log('Metric type:', type); // e.g., 'sum', 'average', 'count'

Type Guard Utilities

/**
 * Type guard that filters out null values
 * Useful with Array.filter() to remove nulls while preserving type safety
 * @param arg - The value to check
 * @returns true if the value is not null
 */
function isNotNull<T>(arg: T): arg is Exclude<T, null>;

Example:

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

// Filter out null values from array
const items = ['item1', null, 'item2', null, 'item3'];
const nonNullItems = items.filter(isNotNull);
// Type: string[] (nulls removed)
// Value: ['item1', 'item2', 'item3']

// Works with complex types
type User = { id: string; name: string } | null;
const users: User[] = [
  { id: '1', name: 'Alice' },
  null,
  { id: '2', name: 'Bob' },
];

const validUsers = users.filter(isNotNull);
// Type: { id: string; name: string }[]
// Value: [{ id: '1', name: 'Alice' }, { id: '2', name: 'Bob' }]

Generic Type Utilities

type ArgumentsOf<F extends Function> = F extends (...args: infer A) => AnyType
  ? A
  : never;

Utility type that extracts the argument types from a function type.

Usage:

import { type ArgumentsOf } from '@lightdash/common';

function exampleFunction(name: string, age: number, active: boolean) {
  // ...
}

// Extract argument types
type ExampleArgs = ArgumentsOf<typeof exampleFunction>;
// Type: [string, number, boolean]

// Use in generic functions
function logFunctionCall<F extends Function>(
  fn: F,
  ...args: ArgumentsOf<F>
): void {
  console.log('Calling function with:', args);
}

ItemsMap Construction and Extraction

Functions for building and extracting specific item types from ItemsMap structures. These are essential for working with explore data and query results.

/**
 * Creates a complete ItemsMap from an explore including additional metrics,
 * table calculations, and custom dimensions
 * @param explore - The explore to extract items from
 * @param additionalMetrics - Optional array of additional metrics to include (defaults to [])
 * @param tableCalculations - Optional array of table calculations to include (defaults to [])
 * @param customDimensions - Optional array of custom dimensions to include (defaults to [])
 * @returns Complete map of all items keyed by item ID
 */
function getItemMap(
  explore: Explore,
  additionalMetrics: AdditionalMetric[] = [],
  tableCalculations: TableCalculation[] = [],
  customDimensions: CustomDimension[] = []
): ItemsMap;

/**
 * Creates a field map from an explore with additional metrics
 * Similar to getItemMap but only includes fields and additional metrics
 * @param explore - The explore to extract fields from
 * @param additionalMetrics - Optional array of additional metrics to include (defaults to [])
 * @returns Map of fields and additional metrics keyed by item ID
 */
function getFieldMap(
  explore: Explore,
  additionalMetrics: AdditionalMetric[] = []
): Record<string, CompiledField | AdditionalMetric>;

/**
 * Extracts all dimension items from an ItemsMap
 * @param itemsMap - The items map to extract from
 * @returns Record of dimensions and custom dimensions keyed by item ID
 */
function getDimensionsFromItemsMap(
  itemsMap: ItemsMap
): Record<string, Dimension | CustomDimension>;

/**
 * Extracts filterable dimensions from an ItemsMap
 * Excludes dimensions that cannot be used in filters
 * @param itemsMap - The items map to extract from
 * @returns Record of filterable dimensions keyed by item ID
 */
function getFilterableDimensionsFromItemsMap(
  itemsMap: ItemsMap
): Record<string, FilterableDimension>;

/**
 * Extracts all metric items from an ItemsMap
 * @param itemsMap - The items map to extract from
 * @param filter - Optional predicate to filter specific metrics
 * @returns Record of metrics keyed by item ID
 */
function getMetricsFromItemsMap(
  itemsMap: ItemsMap,
  filter?: (value: ItemsMap[string]) => boolean
): Record<string, Metric>;

/**
 * Extracts table calculation items from an ItemsMap
 * @param itemsMap - Optional items map to extract from
 * @returns Record of table calculations keyed by item ID
 */
function getTableCalculationsFromItemsMap(
  itemsMap?: ItemsMap
): Record<string, TableCalculation>;

/**
 * Gets a set of all field IDs used in a metric query
 * Includes dimensions, metrics, and table calculation names
 * @param metricQuery - The metric query to analyze
 * @returns Array of field ID strings
 */
function itemsInMetricQuery(metricQuery: MetricQuery | undefined): string[];

Example:

import {
  getItemMap,
  getFieldMap,
  getDimensionsFromItemsMap,
  getMetricsFromItemsMap,
  getTableCalculationsFromItemsMap,
  itemsInMetricQuery,
  type Explore,
  type AdditionalMetric,
  type TableCalculation,
} from '@lightdash/common';

// Build complete items map with additional metrics
const additionalMetrics: AdditionalMetric[] = [
  {
    name: 'profit_margin',
    label: 'Profit Margin',
    table: 'orders',
    sql: '${orders.profit} / ${orders.revenue}',
    type: MetricType.NUMBER,
    formatOptions: { type: CustomFormatType.PERCENT, round: 2 },
  },
];

const itemsMap = getItemMap(explore, additionalMetrics, tableCalculations);

// Extract only dimensions
const dimensions = getDimensionsFromItemsMap(itemsMap);
console.log('Available dimensions:', Object.keys(dimensions));

// Extract only metrics
const metrics = getMetricsFromItemsMap(itemsMap);
console.log('Available metrics:', Object.keys(metrics));

// Extract filterable dimensions (excludes bins, time intervals)
const filterableDims = getFilterableDimensionsFromItemsMap(itemsMap);

// Get field map (fields + additional metrics only)
const fieldMap = getFieldMap(explore, additionalMetrics);

// Get fields used in a query
const query: MetricQuery = {
  exploreName: 'orders',
  dimensions: ['orders.created_date', 'orders.status'],
  metrics: ['orders.total_revenue', 'orders.profit_margin'],
  tableCalculations: [{ name: 'growth_rate', displayName: 'Growth Rate', sql: '...' }],
  // ... other query properties
};

const usedFields = itemsInMetricQuery(query);
console.log('Fields in query:', usedFields);
// Output: ['orders.total_revenue', 'orders.profit_margin', 'orders.created_date', 'orders.status', 'growth_rate']

Dependency Graph Utilities