Comprehensive utilities for validation, transformation, formatting, and data manipulation.
| Category | Functions | Purpose |
|---|---|---|
| Validation | validateEmail, validatePassword, getEmailSchema | Email/password validation with Zod schemas |
| String | snakeCaseName, friendlyName, capitalize | Case conversion and name transformation |
| Array | hasIntersection, toggleArrayValue, replaceStringInArray | Array operations and comparisons |
| Object | removeEmptyProperties, deepEqual | Object manipulation and comparison |
| Color | hexToRGB, isHexCodeColor, getReadableTextColor | Color conversion and validation |
| Type Guards | isNotNull, isLightdashMode, isWeekDay | Runtime type checking |
| Accessors | getArrayValue, getObjectValue, hasProperty | Safe property access with error handling |
| Time | convertWeekDayToMomentWeekDay, getMomentDateWithCustomStartOfWeek | Week configuration and time utilities |
| Formatting | formatItemValue, formatRows, formatDate | Value formatting for display |
| Query Results | getResultValueArray, formatRawValue, itemsInMetricQuery | Result transformation |
| Charts | getDateGroupLabel, getAxisName, sortedItemsForXAxis | Chart configuration helpers |
| Items | getItemId, getItemLabel, isNumericItem, isDateItem | Item identification and type checking |
| Slugs | generateSlug, getLtreePathFromSlug, getDeepestPaths | URL-safe identifiers |
| Filters | getFilterRulesFromGroup, getTotalFilterRules, countTotalFilterRules | Filter extraction |
| Time Frames | getDefaultTimeFrames, validateTimeFrames, sortTimeFrames | Time interval utilities |
| Promises | isFulfilled, isRejected, getFulfilledValues | Promise.allSettled helpers |
| Scheduler | getHumanReadableCronExpression, isValidFrequency | Cron and scheduler utilities |
| i18n | ChartAsCodeInternalization, DashboardAsCodeInternalization | Multi-language support |
| Dependencies | detectCircularDependencies | Graph cycle detection |
| Metrics Explorer | getFieldIdForDateDimension, getMetricExplorerDataPoints | Metrics explorer helpers |
/**
* Validates an email address using a regular expression
* Checks for valid email format and rejects whitespace
* @param email - The email address to validate
* @returns true if the email is valid, false otherwise
*/
function validateEmail(email: string): boolean;
/**
* Returns a Zod schema for email validation
* @returns Zod schema that validates email format and rejects whitespace
*/
function getEmailSchema(): ZodSchema;
/**
* Validates a full email address using a stricter regex pattern
* @param email - The email address to validate
* @returns true if the email address matches the pattern
*/
function isValidEmailAddress(email: string): boolean;
/**
* Extracts the domain portion from an email address
* @param email - The email address
* @returns The domain in lowercase (part after @)
* @throws Error if email contains whitespace or is invalid
*/
function getEmailDomain(email: string): string;
/**
* Validates that a domain string has valid format for email domains
* Checks against VALID_EMAIL_DOMAIN_REGEX pattern
* @param value - The domain to validate
* @returns true if the domain format is valid
*/
function isValidEmailDomain(value: string): boolean;
/**
* Validates organization email domains, rejecting common email providers
* Ensures domains are not consumer email services (gmail.com, yahoo.com, etc.)
* @param domains - Array of domain strings to validate
* @returns Error message string if invalid domains found, undefined if all valid
*/
function validateOrganizationEmailDomains(domains: string[]): string | undefined;Example:
import {
validateEmail,
getEmailSchema,
isValidEmailAddress,
getEmailDomain,
isValidEmailDomain,
validateOrganizationEmailDomains,
} from '@lightdash/common';
// Basic email validation
if (validateEmail('user@example.com')) {
console.log('Valid email');
}
// Use with Zod for form validation
const schema = z.object({
email: getEmailSchema(),
});
// Stricter validation
if (isValidEmailAddress('user@company.example.com')) {
console.log('Valid email address');
}
// Extract domain from email
const domain = getEmailDomain('user@company.com'); // Returns 'company.com'
// Validate domain format
if (isValidEmailDomain('company.com')) {
console.log('Valid domain format');
}
// Validate organization domains (rejects consumer email providers)
const error = validateOrganizationEmailDomains(['company.com', 'gmail.com']);
if (error) {
console.error(error); // "gmail.com is not allowed as organization email"
}
// Valid organization domains
const valid = validateOrganizationEmailDomains(['company.com', 'business.org']);
console.log(valid); // undefined (no errors)function validatePassword(password: string): boolean;
function getPasswordSchema(): ZodSchema;Example:
import { validatePassword, getPasswordSchema } from '@lightdash/common';
if (validatePassword('SecurePass123!')) {
console.log('Valid password');
}
// Use with Zod
const schema = z.object({
password: getPasswordSchema(),
});function getOrganizationNameSchema(): ZodSchema;
const CompleteUserSchema: z.ZodObject;
type CompleteUserArgs = z.infer<typeof CompleteUserSchema>;function snakeCaseName(text: string): string;
function friendlyName(text: string): string;
function capitalize(text: string): string;
function hasSpecialCharacters(text: string): boolean;Examples:
import { snakeCaseName, friendlyName, capitalize, hasSpecialCharacters } from '@lightdash/common';
snakeCaseName('Customer Name'); // "customer_name"
friendlyName('customer_id'); // "Customer id"
capitalize('hello world'); // "Hello world"
hasSpecialCharacters('test@123'); // truefunction hasIntersection(tags: string[], tags2: string[]): boolean;
function toggleArrayValue<T>(arr: T[], value: T): T[];
function replaceStringInArray(arr: string[], oldVal: string, newVal: string): string[];Examples:
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']function removeEmptyProperties(obj: Record<string, unknown>): Record<string, unknown>;
function deepEqual(obj1: Record<string, unknown>, obj2: Record<string, unknown>): boolean;Examples:
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: truefunction hexToRGB(hex: string, alpha?: number): string;
function isHexCodeColor(color: string): boolean;
function getInvalidHexColors(colors: string[]): string[];
function cleanColorArray(colors: string[]): string[];
function getReadableTextColor(backgroundColor: string): string;Examples:
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', '#123']
// Clean color array (removes invalid colors)
const cleaned = cleanColorArray(['#FF5733', 'invalid', '#4C8BF5']);
// Returns: ['#FF5733', '#4C8BF5']
// Get readable text color (white or black) for background
const textColor = getReadableTextColor('#000000');
// Returns: "white"function isNotNull<T>(arg: T): arg is Exclude<T, null>;
function isLightdashMode(x: string): x is LightdashMode;
function isWeekDay(value: unknown): value is WeekDay;
function isFormat(value: string | undefined): value is Format;
function isTimeInterval(value: string): value is TimeFrames;const values: (string | null)[] = ['a', null, 'b'];
const nonNull = values.filter(isNotNull); // string[]Safe property access with error handling.
function getArrayValue<T>(obj: ArrayLike<T> | undefined, key: number, errorMessage?: string): T;
function getObjectValue<T>(obj: Record<string | number, T> | undefined, key: string | number, errorMessage?: string): T;
function hasProperty<T>(obj: unknown, key: string): obj is Record<string, T>;// Throws UnexpectedIndexError if not found
const item = getArrayValue(items, 1);
const value = getObjectValue(config, 'key');function assertUnreachable(value: never, message?: string): never;type Status = 'pending' | 'success' | 'error';
function handleStatus(status: Status) {
switch (status) {
case 'pending': return 'Processing...';
case 'success': return 'Done!';
case 'error': return 'Failed';
default: return assertUnreachable(status); // TypeScript compile error if cases missing
}
}enum WeekDay { MONDAY = 0, TUESDAY = 1, WEDNESDAY = 2, THURSDAY = 3, FRIDAY = 4, SATURDAY = 5, SUNDAY = 6 }
function convertWeekDayToMomentWeekDay(weekDay: WeekDay): number;
function getMomentDateWithCustomStartOfWeek(startOfWeek: WeekDay | null | undefined, inp?: moment.MomentInput): moment.Moment;
// Time frame utilities
const timeFrameConfigs: Record<TimeFrames, TimeFrameConfig>;
function getDefaultTimeFrames(type: DimensionType): TimeFrames[];
function validateTimeFrames(values: string[]): TimeFrames[];
function sortTimeFrames(a: TimeFrames, b: TimeFrames): number;
function getDateDimension(dimensionId: string): { baseDimensionId?: string; newTimeFrame?: TimeFrames };
function getSqlForTruncatedDate(
adapterType: SupportedDbtAdapter,
timeFrame: TimeFrames,
originalSql: string,
type: DimensionType,
startOfWeek?: WeekDay | null
): string;function formatItemValue(item: Field | TableCalculation | undefined, value: unknown, convertToUTC?: boolean): string;
function formatDate(date: Date | string, timeInterval?: TimeFrames, convertToUTC?: boolean): string;
function formatTimestamp(value: Date | string, timeInterval?: TimeFrames, convertToUTC?: boolean): string;
function formatNumberValue(value: number, options?: NumberFormatOptions): string;
function applyCustomFormat(item: Field | Metric, value: unknown, customFormat: CustomFormat): string;function getResultValueArray(
rows: ResultRow[],
preferRaw?: boolean,
calculateMinAndMax?: boolean,
excludeNulls?: boolean
): { results: Record<string, unknown>[]; minsAndMaxes?: Record<string, { min: number; max: number }> };
function formatRawValue(field: Field | undefined, value: unknown): unknown;
function formatRawRows(rows: { [col: string]: unknown }[], itemsMap: ItemsMap): Record<string, unknown>[];
function formatRow(
row: { [col: string]: unknown },
itemsMap: ItemsMap,
pivotValuesColumns?: Record<string, PivotValuesColumn> | null,
parameters?: Record<string, unknown>
): ResultRow;
function formatRows(
rows: { [col: string]: unknown }[],
itemsMap: ItemsMap,
pivotValuesColumns?: Record<string, PivotValuesColumn> | null,
parameters?: Record<string, unknown>
): ResultRow[];
function itemsInMetricQuery(metricQuery?: MetricQuery): string[];// Extract values with min/max calculation
const { results, minsAndMaxes } = getResultValueArray(rows, true, true, false);
// Format database rows
const formatted = formatRows(rawRows, itemsMap, pivotColumns, parameters);
// Get field IDs from query
const fieldIds = itemsInMetricQuery(metricQuery);
// Returns: ['customers_id', 'orders_total', 'calc_profit']function getDateGroupLabel(axisItem: ItemsMap[string]): string | undefined;
function getAxisName(args: {
isAxisTheSameForAllSeries: boolean;
selectedAxisIndex: number;
axisReference: 'yRef' | 'xRef';
axisIndex: number;
axisName?: string;
series?: Series[];
itemsMap: ItemsMap | undefined;
}): string | undefined;
function getDefaultChartConfig(chartKind: ChartKind): ChartConfig;
function isCompleteLayout(layout: Partial<CartesianChartLayout>): boolean;
function isCompleteEchartsConfig(config: Partial<EChartsConfig>): boolean;
function getCustomLabelsFromVizTableConfig(config: VizTableConfig | undefined): Record<string, string>;
function getHiddenFieldsFromVizTableConfig(config: VizTableConfig | undefined): string[];
function getColumnOrderFromVizTableConfig(config: VizTableConfig | undefined): string[];
// Dashboard utilities
function hasChartsInDashboard(dashboard: Dashboard): boolean;
function getDefaultChartTileSize(chartKind: ChartKind): { h: number; w: number };// Identification
function getItemId(item: Item): string;
function getItemLabel(item: Item): string;
function getItemLabelWithoutTableName(item: Item): string;
function getItemType(item: Item): DimensionType | MetricType;
// Type checking
function isNumericItem(item: Item | undefined): boolean;
function isStringDimension(item: Item | undefined): boolean;
function isDateItem(item: Item | undefined): boolean;
// UI helpers
function getItemIcon(item: Item): 'tag' | 'numerical' | 'function';
function getItemColor(item: Item): string;
// Analysis
function isSummable(item: Item | undefined): boolean;
// Chart axis sorting
function sortedItemsForXAxis(itemsMap: ItemsMap | undefined): ItemsMap[string][];
function sortedItemsForYAxis(itemsMap: ItemsMap | undefined): ItemsMap[string][];const itemId = getItemId(item); // "customers_revenue"
const label = getItemLabel(item); // "Customers Revenue"
const shortLabel = getItemLabelWithoutTableName(item); // "Revenue"
if (isNumericItem(item)) {
// Can apply numeric formatting
}
if (isDateItem(item)) {
// Can use date filters
}
const xAxisItems = sortedItemsForXAxis(itemsMap);
// Prioritizes: date dimensions > dimensions > custom dimensions > metricsfunction convertAdditionalMetric(additionalMetric: AdditionalMetric, table: Table): CompiledMetric;
function getCustomMetricType(type: DimensionType): MetricType[];
function canApplyFormattingToCustomMetric(item: Dimension, customMetricType: MetricType): boolean;// Get available metrics for a number dimension
const numberMetrics = getCustomMetricType(DimensionType.NUMBER);
// Returns: [MIN, MAX, SUM, PERCENTILE, MEDIAN, AVERAGE, COUNT_DISTINCT, COUNT]
// Get available metrics for a string dimension
const stringMetrics = getCustomMetricType(DimensionType.STRING);
// Returns: [COUNT_DISTINCT, COUNT, MIN, MAX]function generateSlug(name: string): string;
function getLtreePathFromSlug(slug: string): string;
function getLtreePathFromContentAsCodePath(path: string): string;
function getContentAsCodePathFromLtreePath(path: string): string;
function getDeepestPaths<T extends { path: string }>(items: T[]): T[];
function isSubPath(parentPath: string, childPath: string): boolean;generateSlug('My Dashboard Name!'); // "my-dashboard-name"
getLtreePathFromSlug('parent/child/item'); // "parent.child.item"
isSubPath('a.b', 'a.b.c.d'); // truefunction sanitizeHtml(html: string): string;
function sleep(ms: number): Promise<void>;function isFulfilled<T>(input: PromiseSettledResult<T>): input is PromiseFulfilledResult<T>;
function isRejected(input: PromiseSettledResult<unknown>): input is PromiseRejectedResult;
function getFulfilledValues<T>(results: PromiseSettledResult<T>[]): T[];const results = await Promise.allSettled([fetchUser(1), fetchUser(2), fetchUser(3)]);
const successful = results.filter(isFulfilled);
const failed = results.filter(isRejected);
const values = getFulfilledValues(results);function getTzMinutesOffset(oldTz: string, newTz: string): number;
function formatMinutesOffset(offsetMins: number): string;
function getTimezoneLabel(timezone: string | undefined): string | undefined;
function getHumanReadableCronExpression(cronExpression: string, timezone: string): string;
function isValidFrequency(cronExpression: string): boolean;
function isValidTimezone(timezone: string | undefined): boolean;getHumanReadableCronExpression('0 9 * * *', 'America/New_York');
// Returns: "at 09:00 AM (UTC -05:00)"
getTimezoneLabel('America/Los_Angeles');
// Returns: "(UTC -08:00) America/Los Angeles"
isValidFrequency('0 * * * *'); // true - runs hourlyfunction getFixedWidthBinSelectSql(args: {
binWidth: number;
baseDimensionSql: string;
warehouseSqlBuilder: WarehouseSqlBuilder;
}): string;
function getCustomRangeSelectSql(args: {
binRanges: BinRange[];
baseDimensionSql: string;
warehouseSqlBuilder: WarehouseSqlBuilder;
}): string;// Fixed width bins (age groups: 0-9, 10-19, 20-29, etc.)
const ageGroupSql = getFixedWidthBinSelectSql({
binWidth: 10,
baseDimensionSql: 'customers.age',
warehouseSqlBuilder
});
// Custom ranges (revenue tiers)
const revenueTierSql = getCustomRangeSelectSql({
binRanges: [
{ to: 1000 }, // <1000
{ from: 1000, to: 5000 }, // 1000-5000
{ from: 5000, to: 10000 }, // 5000-10000
{ from: 10000 } // ≥10000
],
baseDimensionSql: 'orders.revenue',
warehouseSqlBuilder
});function assertUnimplementedTimeframe(timeframe: TimeFrames): never;
function getFieldIdForDateDimension(fieldId: string, timeframe: TimeFrames): string;
function getDateCalcUtils(
timeFrame: TimeFrames,
grain?: TimeFrames
): { forward: (date: Date) => Date; back: (date: Date) => Date };
function getDateRangeFromString(dateRange: [string, string], timeFrame: TimeFrames): [Date, Date];
function getGrainForDateRange(dateRange: MetricExplorerDateRange): TimeFrames;
function getMetricsExplorerSegmentFilters(segmentDimension: CompiledDimension | undefined, values: string[]): FilterRule[];
function getMetricExplorerDateRangeFilters(
timeDimensionBaseField: string | undefined,
dateRange: MetricExplorerDateRange,
timeInterval: TimeFrames
): FilterRule[];
function parseMetricValue(value: unknown): number | null;
function getMetricExplorerDataPoints(rows: ResultRow[], query: MetricExplorerQuery): MetricExploreDataPoint[];
function getMetricExplorerDataPointsWithCompare(rows: ResultRow[], query: MetricExplorerQuery): MetricExploreDataPoint[];
function getDefaultDateRangeFromInterval(interval: TimeFrames): MetricExplorerDateRange;
function getDefaultMetricTreeNodeDateRange(interval: TimeFrames): MetricExplorerDateRange;
function getFirstAvailableTimeDimension(tables: Record<string, CompiledTable>): DefaultTimeDimension | undefined;
function getDefaultTimeDimension(tables: Record<string, CompiledTable>): DefaultTimeDimension | undefined;
function getAvailableTimeDimensionsFromTables(tables: Record<string, CompiledTable>): CompiledDimension[];
function getAvailableSegmentDimensions(tables: Record<string, CompiledTable>): CompiledDimension[];
function getAvailableCompareMetrics(
tables: Record<string, CompiledTable>,
currentMetric?: MetricWithAssociatedTimeDimension
): CompiledMetric[];// Get time-granular field ID
const monthlyFieldId = getFieldIdForDateDimension('orders.created_at', TimeFrames.MONTH);
// Returns: 'orders.created_at_month'
// Create date range filters
const filters = getMetricExplorerDateRangeFilters(
'orders.created_at',
{ from: new Date('2024-01-01'), to: new Date('2024-12-31') },
TimeFrames.MONTH
);
// Parse metric values safely
const value = parseMetricValue('123.45'); // 123.45
const invalid = parseMetricValue('invalid'); // nullinterface DependencyNode { name: string; dependencies: string[] }
function detectCircularDependencies(dependencies: DependencyNode[], errorPrefix?: string): void;const dependencies = [
{ name: 'A', dependencies: ['B'] },
{ name: 'B', dependencies: ['C'] },
{ name: 'C', dependencies: ['A'] }
];
detectCircularDependencies(dependencies, 'table calculations');
// Throws: "Circular dependency detected in table calculations: A -> B -> C -> A"class ChartAsCodeInternalization {
constructor(schema?: typeof chartAsCodeSchema);
getLanguageMap(chartAsCode: ChartAsCode): ChartAsCodeLanguageMap;
merge(internalizationMap: ChartAsCodeLanguageMap['chart'][string], content: ChartAsCode): ChartAsCode;
}
class DashboardAsCodeInternalization {
constructor(schema?: typeof dashboardAsCodeSchema);
getLanguageMap(dashboardAsCode: DashboardAsCode): DashboardAsCodeLanguageMap;
merge(internalizationMap: DashboardAsCodeLanguageMap['dashboard'][string], content: DashboardAsCode): DashboardAsCode;
}
function mergeExisting(left: any, right: any): any;
type ChartAsCodeLanguageMap = ReturnType<ChartAsCodeInternalization['getLanguageMap']>;
type DashboardAsCodeLanguageMap = ReturnType<DashboardAsCodeInternalization['getLanguageMap']>;
type LanguageMap = Partial<ChartAsCodeLanguageMap & DashboardAsCodeLanguageMap>;// Extract translatable content from a chart
const chartI18n = new ChartAsCodeInternalization();
const languageMap = chartI18n.getLanguageMap(chartAsCode);
// Apply Spanish translations
const translatedChart = chartI18n.merge(spanishTranslations, chartAsCode);function getProjectDirectory(dbtConnection?: DbtProjectConfig): string | undefined;
function loadLightdashProjectConfig(yamlFileContents: string, onLoaded?: (config: LightdashProjectConfig) => Promise<void>): Promise<LightdashProjectConfig>;
function convertOrganizationRoleToProjectRole(organizationRole: OrganizationMemberRole): ProjectMemberRole | undefined;
function convertSpaceRoleToProjectRole(spaceRole: SpaceMemberRole): ProjectMemberRole | undefined;
function getHighestProjectRole(inheritedRoles: Array<OrganizationRole | ProjectRole | GroupRole | SpaceGroupAccessRole>): InheritedProjectRole | undefined;const chartAsCodeSchema: object;
const dashboardAsCodeSchema: object;
const lightdashDbtYamlSchema: object;
const lightdashProjectConfigSchema: object;
const modelAsCodeSchema: object;// Session storage
enum SessionStorageKeys {
SEND_NOW_SCHEDULER_FILTERS = 'sendNowSchedulerFilters',
SEND_NOW_SCHEDULER_PARAMETERS = 'sendNowSchedulerParameters'
}
// SQL Runner
const MAX_SAFE_INTEGER: number; // 2_147_483_647
// Metrics Explorer
const METRICS_EXPLORER_DATE_FORMAT: 'YYYY-MM-DD';
const MAX_SEGMENT_DIMENSION_UNIQUE_VALUES: 10;
const DEFAULT_METRICS_EXPLORER_TIME_INTERVAL: TimeFrames.MONTH;
const MAX_METRICS_TREE_NODE_COUNT: number;import {
validateEmail,
snakeCaseName,
toggleArrayValue,
hexToRGB,
isNotNull,
sleep,
getItemMap,
formatRows,
itemsInMetricQuery,
getHumanReadableCronExpression,
} from '@lightdash/common';
// Validation
if (validateEmail('user@example.com')) {
console.log('Valid email');
}
// String transformations
const snake = snakeCaseName('Customer Name'); // "customer_name"
// Array utilities
const tags = ['tag1', 'tag2'];
const newTags = toggleArrayValue(tags, 'tag3'); // ['tag1', 'tag2', 'tag3']
// Type guards
const values: (string | null)[] = ['a', null, 'b'];
const nonNull = values.filter(isNotNull); // string[]
// Color utilities
const rgb = hexToRGB('#4C8BF5', 0.5); // "rgba(76, 139, 245, 0.5)"
// Async utilities
await sleep(1000);
// Query result formatting
const itemsMap = getItemMap(explore, additionalMetrics, tableCalculations, customDimensions);
const formattedRows = formatRows(rawRows, itemsMap);
// Scheduler
const readable = getHumanReadableCronExpression('0 9 * * *', 'America/New_York');
// "at 09:00 AM (UTC -05:00)"