Shared TypeScript library for the Lightdash platform containing common types, utilities, and business logic for analytics workflows
Overall
score
72%
Evaluation — 72%
↑ 1.09xAgent success when using this tile
Various utility functions that don't fit into other specialized categories, including boolean conversion, sleep utilities, type utilities, Lightdash mode helpers, and additional miscellaneous functions.
This module provides the following functionality:
/**
* Converts various types of values to boolean, with special handling for string representations
* of boolean values that should be interpreted literally rather than using JavaScript's
* truthy/falsy evaluation
* @param value - The value to convert to boolean
* @returns The boolean representation of the value
*/
function convertToBooleanValue(value: unknown): boolean;Example:
import { convertToBooleanValue } from '@lightdash/common';
// String representations
console.log(convertToBooleanValue('true')); // true
console.log(convertToBooleanValue('false')); // false
console.log(convertToBooleanValue('FALSE')); // false (case-insensitive)
console.log(convertToBooleanValue(' True ')); // true (handles whitespace)
// Native booleans pass through
console.log(convertToBooleanValue(true)); // true
console.log(convertToBooleanValue(false)); // false
// Other types use truthy/falsy evaluation
console.log(convertToBooleanValue(1)); // true
console.log(convertToBooleanValue(0)); // false
console.log(convertToBooleanValue('hello')); // true
console.log(convertToBooleanValue('')); // false
console.log(convertToBooleanValue(null)); // false
console.log(convertToBooleanValue(undefined)); // false
// Use for parsing configuration values
interface AppConfig {
enabled: boolean;
}
function parseConfig(raw: Record<string, unknown>): AppConfig {
return {
enabled: convertToBooleanValue(raw.enabled),
};
}
// Handles string "false" correctly (unlike !!)
const config1 = parseConfig({ enabled: 'false' }); // { enabled: false }
const config2 = parseConfig({ enabled: 'true' }); // { enabled: true }/**
* Sleep for a specified number of milliseconds
* @param ms - Number of milliseconds to sleep
* @returns Promise that resolves after the specified time
*/
function sleep(ms: number): Promise<void>;Example:
import { sleep } from '@lightdash/common';
async function delayedOperation() {
console.log('Starting...');
await sleep(1000); // Wait 1 second
console.log('Done!');
}
// Use in retry logic
async function retryOperation<T>(
operation: () => Promise<T>,
maxRetries: number = 3
): Promise<T> {
for (let i = 0; i < maxRetries; i++) {
try {
return await operation();
} catch (error) {
if (i === maxRetries - 1) throw error;
await sleep(1000 * (i + 1)); // Exponential backoff
}
}
throw new Error('Max retries exceeded');
}/**
* Type guard to check if a string is a valid Lightdash mode
* @param x - String to check
* @returns true if x is a valid LightdashMode
*/
function isLightdashMode(x: string): x is LightdashMode;
/**
* Lightdash installation types for analytics
*/
enum LightdashInstallType {
DOCKER_IMAGE = 'docker_image',
BASH_INSTALL = 'bash_install',
HEROKU = 'heroku',
UNKNOWN = 'unknown',
}Example:
import { isLightdashMode, LightdashMode, LightdashInstallType } from '@lightdash/common';
// Type guard for validating mode strings
const modeString = process.env.LIGHTDASH_MODE;
if (isLightdashMode(modeString)) {
// modeString is now typed as LightdashMode
console.log('Valid mode:', modeString);
}
// Track installation type for telemetry
const installType = LightdashInstallType.DOCKER_IMAGE;
function getInstallType(): LightdashInstallType {
if (process.env.DOCKER_IMAGE) {
return LightdashInstallType.DOCKER_IMAGE;
} else if (process.env.BASH_INSTALL) {
return LightdashInstallType.BASH_INSTALL;
} else if (process.env.HEROKU) {
return LightdashInstallType.HEROKU;
}
return LightdashInstallType.UNKNOWN;
}/**
* Extract argument types from a function
*/
type ArgumentsOf<F extends Function> = F extends (...args: infer A) => any
? A
: never;Example:
import { type ArgumentsOf } from '@lightdash/common';
// Extract argument types
function exampleFunc(name: string, age: number, active: boolean) {
// ...
}
type ExampleArgs = ArgumentsOf<typeof exampleFunc>;
// Type: [name: string, age: number, active: boolean]
// Use with generic function wrappers
function wrapFunction<F extends Function>(
fn: F,
...args: ArgumentsOf<F>
): ReturnType<F> {
console.log('Calling function with args:', args);
return fn(...args);
}/**
* Converts an additional metric to a compiled metric
* @param additionalMetric - Additional metric to convert
* @param table - Table context for the metric
* @returns Compiled metric
*/
function convertAdditionalMetric(
additionalMetric: AdditionalMetric,
table: Table
): CompiledMetric;
/**
* Returns available metric types for a given dimension type
* Different dimension types support different metric aggregations
* @param type - Dimension type
* @returns Array of supported metric types
*/
function getCustomMetricType(type: DimensionType): MetricType[];Example:
import { getCustomMetricType, DimensionType, MetricType } from '@lightdash/common';
// 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]
// Get available metrics for a boolean dimension
const booleanMetrics = getCustomMetricType(DimensionType.BOOLEAN);
// Returns: [COUNT_DISTINCT, COUNT]
// Use when building custom metric UI
function getAvailableAggregations(dimension: Dimension): MetricType[] {
return getCustomMetricType(dimension.type);
}Functions for working with explores, fields, dimensions, metrics, and items maps.
/**
* Gets all fields (dimensions and metrics) from an explore
*/
function getFields(explore: Explore): CompiledField[];
/**
* Gets only dimension fields from an explore
*/
function getDimensions(explore: Explore): CompiledDimension[];
/**
* Gets only metric fields from an explore
*/
function getMetrics(explore: Explore): CompiledMetric[];
/**
* Gets only visible (non-hidden) fields from an explore
* @param explore - The explore containing fields
* @returns Array of compiled fields that are not marked as hidden
*/
function getVisibleFields(explore: Explore): CompiledField[];Usage example:
import { getVisibleFields, type Explore } from '@lightdash/common';
const visibleFields = getVisibleFields(explore);
// Returns only fields where hidden !== true
console.log(`Found ${visibleFields.length} visible fields`);/**
* Finds a field by its ID in an explore
* @param explore - The explore to search within
* @param id - The field ID (format: "tableName_fieldName")
* @returns The matching field, or undefined if not found
*/
function findFieldByIdInExplore(explore: Explore, id: FieldId): Field | undefined;Usage example:
import { findFieldByIdInExplore, getItemId, type Explore } from '@lightdash/common';
const fieldId = 'orders_order_date';
const field = findFieldByIdInExplore(explore, fieldId);
if (field) {
console.log(`Found field: ${field.label}`);
} else {
console.log('Field not found');
}/**
* Creates a map of field IDs to field objects
*/
function getFieldMap(
explore: Explore,
additionalMetrics?: AdditionalMetric[]
): Record<string, CompiledField | AdditionalMetric>;
/**
* Creates a comprehensive items map including fields, table calculations, and custom dimensions
*/
function getItemMap(
explore: Explore,
additionalMetrics?: AdditionalMetric[],
tableCalculations?: TableCalculation[],
customDimensions?: CustomDimension[]
): ItemsMap;
/**
* Extracts only dimensions from an items map
*/
function getDimensionsFromItemsMap(itemsMap: ItemsMap): Record<string, Dimension | CustomDimension>;
/**
* Extracts only filterable dimensions from an items map
*/
function getFilterableDimensionsFromItemsMap(itemsMap: ItemsMap): Record<string, FilterableDimension>;
/**
* Extracts only metrics from an items map with optional filter
*/
function getMetricsFromItemsMap(
itemsMap: ItemsMap,
filter?: (value: ItemsMap[string]) => boolean
): Record<string, Metric>;
/**
* Extracts only table calculations from an items map
*/
function getTableCalculationsFromItemsMap(itemsMap?: ItemsMap): Record<string, TableCalculation>;/**
* Generates a subtotal key from dimension array for subtotal grouping
* @param dimensions - Array of dimension field IDs
* @returns Colon-separated key string (e.g., "dim1:dim2:dim3")
*/
function getSubtotalKey(dimensions: string[]): string;Example:
import { getSubtotalKey } from '@lightdash/common';
// Generate subtotal key for dimension combination
const dimensions = ['customers_region', 'customers_country', 'customers_city'];
const key = getSubtotalKey(dimensions);
// Returns: "customers_region:customers_country:customers_city"
// Use for subtotal row identification
const subtotalRow = {
...row,
[SUBTOTAL_KEY_FIELD]: getSubtotalKey(['customers_region', 'customers_country']),
};const CompleteUserSchema: z.ZodObject<{
organizationName: z.ZodOptional<z.ZodString>;
jobTitle: z.ZodString;
enableEmailDomainAccess: z.ZodDefault<z.ZodBoolean>;
isMarketingOptedIn: z.ZodDefault<z.ZodBoolean>;
isTrackingAnonymized: z.ZodDefault<z.ZodBoolean>;
}>;
type CompleteUserArgs = z.infer<typeof CompleteUserSchema>;Example:
import { CompleteUserSchema, type CompleteUserArgs } from '@lightdash/common';
// Validate user completion data
const userData: CompleteUserArgs = {
organizationName: 'My Company',
jobTitle: 'Data Analyst',
enableEmailDomainAccess: false,
isMarketingOptedIn: true,
isTrackingAnonymized: false,
};
const validated = CompleteUserSchema.parse(userData);
// In onboarding flow
async function completeUserOnboarding(data: unknown) {
try {
const validatedData = CompleteUserSchema.parse(data);
await updateUser(validatedData);
} catch (error) {
console.error('Invalid user data:', error);
}
}/**
* Get organization name validation schema
* @returns Zod schema for organization name validation
*/
function getOrganizationNameSchema(): ZodSchema;// Enterprise edition functionality
export * from './ee/AiAgent';
export * from './ee/commercialFeatureFlags';
export * from './ee/embed';
export * from './ee/scim/errors';
export * from './ee/scim/types';
export * from './ee/serviceAccounts/types';
enum ScimSchemaType {
ERROR = 'urn:ietf:params:scim:api:messages:2.0:Error',
USER = 'urn:ietf:params:scim:schemas:core:2.0:User',
GROUP = 'urn:ietf:params:scim:schemas:core:2.0:Group',
ROLE = 'urn:ietf:params:scim:schemas:extension:2.0:Role',
LIST_RESPONSE = 'urn:ietf:params:scim:api:messages:2.0:ListResponse',
SCHEMA = 'urn:ietf:params:scim:schemas:core:2.0:Schema',
PATCH = 'urn:ietf:params:scim:api:messages:2.0:PatchOp',
LIGHTDASH_USER_EXTENSION = 'urn:lightdash:params:scim:schemas:extension:2.0:User',
SERVICE_PROVIDER_CONFIG = 'urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig',
RESOURCE_TYPE = 'urn:ietf:params:scim:schemas:core:2.0:ResourceType',
}Example:
import { ScimSchemaType } from '@lightdash/common';
// SCIM user provisioning
const scimUser = {
schemas: [ScimSchemaType.USER],
userName: 'john.doe@example.com',
active: true,
};
// Check for enterprise features
import { CommercialFeatureFlags } from '@lightdash/common';
if (organization.features.includes(CommercialFeatureFlags.AI_AGENT)) {
// Enable AI agent features
}class ChangesetUtils {
static applyChangeset<C extends ChangeBase>(
changeset: { changes: C[] },
explores: Record<string, Explore | ExploreError>
): Record<string, Explore | ExploreError>;
}Example:
import { ChangesetUtils } from '@lightdash/common';
const updatedExplores = ChangesetUtils.applyChangeset(
{ changes: [/* array of changes */] },
explores
);/**
* Parses a conditional formatting expression into an AST
* @param expression - The expression string
* @returns Parsed expression AST
*/
function parseConditionalFormatExpression(expression: string): ConditionalFormatExpression;
/**
* Evaluates a conditional formatting expression against a value
* @param expression - The parsed expression
* @param value - The value to evaluate
* @returns true if the condition matches
*/
function evaluateConditionalFormatExpression(
expression: ConditionalFormatExpression,
value: unknown
): boolean;Example:
import {
parseConditionalFormatExpression,
evaluateConditionalFormatExpression,
} from '@lightdash/common';
// Parse an expression
const expr = parseConditionalFormatExpression('value > 100');
// Evaluate against values
const matches = evaluateConditionalFormatExpression(expr, 150); // truetype SqlResultsRow = { [columnName: string]: unknown };
type SqlResultsField = {
name: string;
type: string;
};
type SqlQueryResults = {
fields: SqlResultsField[];
rows: SqlResultsRow[];
};Example:
import { type SqlQueryResults } from '@lightdash/common';
// Raw warehouse results
const sqlResults: SqlQueryResults = {
fields: [
{ name: 'customer_id', type: 'integer' },
{ name: 'total_revenue', type: 'numeric' }
],
rows: [
{ customer_id: 1, total_revenue: 1234.56 },
{ customer_id: 2, total_revenue: 2345.67 }
]
};
// These are then transformed by formatRows() into ResultRow formatimport {
convertToBooleanValue,
sleep,
isLightdashMode,
getCustomMetricType,
getFields,
getItemMap,
getSubtotalKey,
type ArgumentsOf,
type SqlQueryResults,
LightdashInstallType,
} from '@lightdash/common';
// Boolean conversion
const enabled = convertToBooleanValue(process.env.FEATURE_ENABLED);
// Sleep utility
async function performOperation() {
await sleep(1000);
return apiCall();
}
// Mode validation
if (isLightdashMode('default')) {
console.log('Valid Lightdash mode');
}
// Metric types
const numberMetrics = getCustomMetricType(DimensionType.NUMBER);
// Explore utilities
const fields = getFields(explore);
const itemsMap = getItemMap(explore, additionalMetrics, tableCalculations);
// Subtotal key
const key = getSubtotalKey(['region', 'country', 'city']);
// Type utilities
type MyFunctionArgs = ArgumentsOf<typeof myFunction>;
// Installation type
const installType = LightdashInstallType.DOCKER_IMAGE;Install with Tessl CLI
npx tessl i tessl/npm-lightdash--commondocs
evals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9
scenario-10
scenario-11
scenario-12
scenario-13
scenario-14
scenario-15
scenario-16
scenario-17
scenario-18
scenario-19
scenario-20